2019-08-09 23:45:49 +00:00
|
|
|
package com.bytezone.diskbrowser.applefile;
|
|
|
|
|
|
|
|
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;
|
2020-06-26 03:29:46 +00:00
|
|
|
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
|
|
|
{
|
|
|
|
private final List<SourceLine> sourceLines = new ArrayList<> ();
|
|
|
|
private final int endPtr;
|
2020-12-30 03:06:50 +00:00
|
|
|
final Map<Integer, List<Integer>> gotoLines = new TreeMap<> ();
|
|
|
|
final Map<Integer, List<Integer>> gosubLines = new TreeMap<> ();
|
|
|
|
final Map<String, List<Integer>> symbolLines = new TreeMap<> ();
|
|
|
|
final Map<String, List<String>> uniqueSymbols = new TreeMap<> ();
|
|
|
|
final List<Integer> stringsLine = new ArrayList<> ();
|
|
|
|
final List<String> stringsText = new ArrayList<> ();
|
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 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;
|
|
|
|
int prevOffset = 0;
|
|
|
|
|
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);
|
|
|
|
if (nextAddress <= prevOffset) // usually zero
|
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-11-21 11:15:08 +00:00
|
|
|
prevOffset = nextAddress;
|
2019-08-09 23:45:49 +00:00
|
|
|
}
|
|
|
|
endPtr = ptr;
|
|
|
|
}
|
|
|
|
|
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
|
|
|
{
|
2020-12-20 21:50:51 +00:00
|
|
|
int indentSize = 2;
|
2020-12-21 05:33:03 +00:00
|
|
|
boolean insertBlankLine = false;
|
2020-12-20 21:50:51 +00:00
|
|
|
|
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<> ();
|
2019-08-09 23:45:49 +00:00
|
|
|
if (basicPreferences.showHeader)
|
|
|
|
addHeader (fullText);
|
|
|
|
int alignPos = 0;
|
|
|
|
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
|
2020-12-30 03:06:50 +00:00
|
|
|
if (subline.is (ApplesoftConstants.TOKEN_REM) && subline.containsToken ())
|
2019-08-09 23:45:49 +00:00
|
|
|
{
|
|
|
|
int address = subline.getAddress () + 1; // skip the REM token
|
|
|
|
fullText.append (text + String.format ("REM - Inline assembler @ $%02X (%d)%n",
|
|
|
|
address, address));
|
|
|
|
String padding = " ".substring (0, text.length () + 2);
|
|
|
|
for (String asm : subline.getAssembler ())
|
|
|
|
fullText.append (padding + asm + "\n");
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-11-22 10:23:35 +00:00
|
|
|
// Beagle Bros often have multiline REM statements
|
2020-12-30 03:06:50 +00:00
|
|
|
if (subline.is (ApplesoftConstants.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
|
2020-12-30 03:06:50 +00:00
|
|
|
if (subline.is (ApplesoftConstants.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 ())
|
2020-12-30 03:06:50 +00:00
|
|
|
if (subline.is (ApplesoftConstants.TOKEN_GOSUB)
|
|
|
|
|| (subline.is (ApplesoftConstants.TOKEN_ON)
|
|
|
|
&& subline.has (ApplesoftConstants.TOKEN_GOSUB)))
|
2019-08-09 23:45:49 +00:00
|
|
|
text.append ("<<--");
|
2020-12-30 03:06:50 +00:00
|
|
|
else if (subline.is (ApplesoftConstants.TOKEN_GOTO)
|
|
|
|
|| subline.isImpliedGoto () || (subline.is (ApplesoftConstants.TOKEN_ON)
|
|
|
|
&& subline.has (ApplesoftConstants.TOKEN_GOTO)))
|
2019-08-09 23:45:49 +00:00
|
|
|
text.append (" <--");
|
|
|
|
|
|
|
|
// Align assign statements if required
|
|
|
|
if (basicPreferences.alignAssign)
|
|
|
|
alignPos = alignEqualsPosition (subline, alignPos);
|
|
|
|
|
2020-12-20 21:50:51 +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
|
2020-12-30 03:06:50 +00:00
|
|
|
int pos = subline.is (ApplesoftConstants.TOKEN_REM) ? 0 : alignPos;
|
2019-08-09 23:45:49 +00:00
|
|
|
String lineText = subline.getAlignedText (pos);
|
|
|
|
|
2020-12-30 03:06:50 +00:00
|
|
|
if (subline.is (ApplesoftConstants.TOKEN_REM)
|
|
|
|
&& basicPreferences.deleteExtraRemSpace)
|
2020-12-21 05:33:03 +00:00
|
|
|
lineText = lineText.replaceFirst ("REM ", "REM ");
|
|
|
|
|
2020-12-30 03:06:50 +00:00
|
|
|
if (subline.is (ApplesoftConstants.TOKEN_DATA)
|
|
|
|
&& basicPreferences.deleteExtraDataSpace)
|
2020-12-21 06:10:07 +00:00
|
|
|
lineText = lineText.replaceFirst ("DATA ", "DATA ");
|
|
|
|
|
2019-08-09 23:45:49 +00:00
|
|
|
// Check for a wrappable REM statement
|
|
|
|
// (see SEA BATTLE on DISK283.DSK)
|
2020-12-25 04:30:26 +00:00
|
|
|
int inset = Math.max (text.length (), getIndent (fullText)) + 1;
|
2020-12-30 03:06:50 +00:00
|
|
|
if (subline.is (ApplesoftConstants.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
|
|
|
}
|
2020-12-30 03:06:50 +00:00
|
|
|
else if (subline.is (ApplesoftConstants.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
|
|
|
}
|
2020-12-30 03:06:50 +00:00
|
|
|
else if (subline.is (ApplesoftConstants.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);
|
|
|
|
|
|
|
|
// Check for a wrappable PRINT statement
|
|
|
|
// (see FROM MACHINE LANGUAGE TO BASIC on DOSToolkit2eB.dsk)
|
|
|
|
if (basicPreferences.wrapPrintAt > 0 //
|
2020-12-30 03:06:50 +00:00
|
|
|
&& (subline.is (ApplesoftConstants.TOKEN_PRINT)
|
|
|
|
|| subline.is (ApplesoftConstants.TOKEN_INPUT))
|
|
|
|
&& countChars (text, Utility.ASCII_QUOTE) == 2 // just start and end quotes
|
|
|
|
&& countChars (text, Utility.ASCII_CARET) == 0) // no control characters
|
2019-08-09 23:45:49 +00:00
|
|
|
// && countChars (text, ASCII_SEMI_COLON) == 0)
|
|
|
|
{
|
|
|
|
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");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
fullText.append (text + "\n");
|
|
|
|
|
|
|
|
text.setLength (0);
|
|
|
|
|
|
|
|
// Calculate indent changes that take effect after the current subline
|
2020-12-30 03:06:50 +00:00
|
|
|
if (subline.is (ApplesoftConstants.TOKEN_IF))
|
2019-08-09 23:45:49 +00:00
|
|
|
ifIndent = ++indent;
|
2020-12-30 03:06:50 +00:00
|
|
|
else if (subline.is (ApplesoftConstants.TOKEN_FOR))
|
2019-08-09 23:45:49 +00:00
|
|
|
{
|
|
|
|
loopVariables.push (subline.forVariable);
|
|
|
|
++indent;
|
|
|
|
}
|
2020-12-30 03:06:50 +00:00
|
|
|
else if (basicPreferences.blankAfterReturn
|
|
|
|
&& subline.is (ApplesoftConstants.TOKEN_RETURN))
|
2020-12-21 05:33:03 +00:00
|
|
|
insertBlankLine = true;
|
2020-12-20 21:50:51 +00:00
|
|
|
}
|
|
|
|
|
2020-12-21 05:33:03 +00:00
|
|
|
if (insertBlankLine)
|
2020-12-20 21:50:51 +00:00
|
|
|
{
|
|
|
|
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)
|
|
|
|
alignPos = 0;
|
|
|
|
}
|
|
|
|
|
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
|
|
|
{
|
2020-06-26 03:29:46 +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-23 10:50:26 +00:00
|
|
|
if (basicPreferences.showXref && !gosubLines.isEmpty ())
|
2020-12-26 08:32:40 +00:00
|
|
|
showLines (fullText, gosubLines, "GOSUB:\n");
|
2020-12-23 10:50:26 +00:00
|
|
|
|
|
|
|
if (basicPreferences.showXref && !gotoLines.isEmpty ())
|
2020-12-26 08:32:40 +00:00
|
|
|
showLines (fullText, gotoLines, "GOTO:\n");
|
|
|
|
|
|
|
|
if (basicPreferences.showSymbols && !symbolLines.isEmpty ())
|
2020-12-23 10:50:26 +00:00
|
|
|
{
|
|
|
|
if (fullText.charAt (fullText.length () - 2) != '\n')
|
|
|
|
fullText.append ("\n");
|
2020-12-26 08:32:40 +00:00
|
|
|
|
|
|
|
fullText.append ("Variables:\n");
|
|
|
|
|
2020-12-30 03:06:50 +00:00
|
|
|
int longestVarName = getLongestVarName ();
|
|
|
|
String format = longestVarName > 6 ? "%" + longestVarName + "s %s%n" : "%6s %s%n";
|
|
|
|
|
2020-12-26 08:32:40 +00:00
|
|
|
for (String symbol : symbolLines.keySet ())
|
2020-12-30 03:06:50 +00:00
|
|
|
fullText.append (String.format (format, symbol, symbolLines.get (symbol)));
|
2020-12-29 04:52:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (basicPreferences.showDuplicateSymbols && !uniqueSymbols.isEmpty ())
|
|
|
|
{
|
|
|
|
if (fullText.charAt (fullText.length () - 2) != '\n')
|
|
|
|
fullText.append ("\n");
|
2020-12-29 04:39:29 +00:00
|
|
|
|
|
|
|
boolean headingShown = false;
|
|
|
|
for (String key : uniqueSymbols.keySet ())
|
|
|
|
{
|
|
|
|
List<String> usage = uniqueSymbols.get (key);
|
|
|
|
if (usage.size () > 1)
|
|
|
|
{
|
|
|
|
if (!headingShown)
|
|
|
|
{
|
|
|
|
headingShown = true;
|
2020-12-29 04:52:32 +00:00
|
|
|
fullText.append ("Duplicate Variable Names:\n");
|
2020-12-29 04:39:29 +00:00
|
|
|
}
|
|
|
|
fullText.append (String.format ("%6s %s%n", key, usage));
|
|
|
|
}
|
|
|
|
}
|
2020-12-23 10:34:18 +00:00
|
|
|
}
|
|
|
|
|
2020-12-24 02:03:00 +00:00
|
|
|
if (basicPreferences.listStrings && stringsLine.size () > 0)
|
|
|
|
{
|
|
|
|
if (fullText.charAt (fullText.length () - 2) != '\n')
|
|
|
|
fullText.append ("\n");
|
|
|
|
fullText.append ("Strings:\n");
|
|
|
|
for (int i = 0; i < stringsLine.size (); i++)
|
|
|
|
{
|
|
|
|
fullText.append (
|
|
|
|
String.format (" %5s %s%n", stringsLine.get (i), stringsText.get (i)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-11 21:49:23 +00:00
|
|
|
if (fullText.length () > 0)
|
2020-12-20 21:50:51 +00:00
|
|
|
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 ();
|
|
|
|
}
|
|
|
|
|
2020-12-30 03:06:50 +00:00
|
|
|
// ---------------------------------------------------------------------------------//
|
|
|
|
private int getLongestVarName ()
|
|
|
|
// ---------------------------------------------------------------------------------//
|
|
|
|
{
|
|
|
|
int longestName = 0;
|
|
|
|
for (String symbol : symbolLines.keySet ())
|
|
|
|
if (symbol.length () > longestName)
|
|
|
|
longestName = symbol.length ();
|
|
|
|
return longestName;
|
|
|
|
}
|
|
|
|
|
2020-12-26 08:32:40 +00:00
|
|
|
// ---------------------------------------------------------------------------------//
|
|
|
|
private void showLines (StringBuilder fullText, Map<Integer, List<Integer>> lines,
|
|
|
|
String heading)
|
|
|
|
// ---------------------------------------------------------------------------------//
|
|
|
|
{
|
|
|
|
if (fullText.charAt (fullText.length () - 2) != '\n')
|
|
|
|
fullText.append ("\n");
|
|
|
|
fullText.append (heading);
|
|
|
|
for (Integer line : lines.keySet ())
|
|
|
|
fullText.append (String.format (" %5s %s%n", line, lines.get (line)));
|
|
|
|
}
|
|
|
|
|
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);
|
|
|
|
return lines;
|
|
|
|
}
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------------//
|
|
|
|
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);
|
2020-11-11 09:13:07 +00:00
|
|
|
|
2019-08-09 23:45:49 +00:00
|
|
|
if (!basicPreferences.showTargets)
|
2020-11-11 09:13:07 +00:00
|
|
|
{
|
|
|
|
if (!isTarget && basicPreferences.onlyShowTargetLineNumbers)
|
|
|
|
return " ";
|
2019-08-09 23:45:49 +00:00
|
|
|
return String.format (" %5d", line.lineNumber);
|
2020-11-11 09:13:07 +00:00
|
|
|
}
|
2019-08-09 23:45:49 +00:00
|
|
|
|
|
|
|
String lineNumberText = String.format ("%5d", line.lineNumber);
|
|
|
|
SubLine subline = line.sublines.get (0);
|
|
|
|
String c1 = " ", c2 = " ";
|
2020-11-11 09:13:07 +00:00
|
|
|
|
2020-12-30 03:06:50 +00:00
|
|
|
if (subline.is (ApplesoftConstants.TOKEN_GOSUB)
|
|
|
|
|| (subline.is (ApplesoftConstants.TOKEN_ON)
|
|
|
|
&& subline.has (ApplesoftConstants.TOKEN_GOSUB)))
|
2019-08-09 23:45:49 +00:00
|
|
|
c1 = "<<";
|
2020-12-30 03:06:50 +00:00
|
|
|
else if (subline.is (ApplesoftConstants.TOKEN_GOTO)
|
|
|
|
|| (subline.is (ApplesoftConstants.TOKEN_ON)
|
|
|
|
&& subline.has (ApplesoftConstants.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 = "--";
|
2020-11-11 09:13:07 +00:00
|
|
|
|
|
|
|
if (!isTarget && basicPreferences.onlyShowTargetLineNumbers)
|
2019-08-09 23:45:49 +00:00
|
|
|
lineNumberText = "";
|
2020-11-11 09:13:07 +00:00
|
|
|
|
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
|
|
|
{
|
|
|
|
if (subline.assignEqualPos > 0) // does the line have an equals sign?
|
|
|
|
{
|
|
|
|
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;
|
|
|
|
int highestAssign = startSubline.assignEqualPos;
|
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.
|
|
|
|
if (subline.assignEqualPos == 0
|
|
|
|
// && (splitRem || !subline.is (TOKEN_REM) || subline.isFirst ()))
|
|
|
|
&& (basicPreferences.splitRem || !subline.isJoinableRem ()))
|
|
|
|
break fast; // of champions
|
|
|
|
|
|
|
|
if (subline.assignEqualPos > highestAssign)
|
|
|
|
highestAssign = subline.assignEqualPos;
|
|
|
|
}
|
|
|
|
else if (subline == startSubline)
|
|
|
|
started = true;
|
2020-12-30 03:06:50 +00:00
|
|
|
else if (subline.is (ApplesoftConstants.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;
|
2020-06-26 03:29:46 +00:00
|
|
|
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 ();
|
|
|
|
}
|
|
|
|
|
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
|
|
|
{
|
2020-06-26 03:29:46 +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
|
|
|
{
|
|
|
|
if (v1.equals (v2))
|
|
|
|
return true;
|
|
|
|
if (v1.length () >= 2 && v2.length () >= 2 && v1.charAt (0) == v2.charAt (0)
|
|
|
|
&& v1.charAt (1) == v2.charAt (1))
|
|
|
|
return true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|