Extracted SourceLine and SubLine

This commit is contained in:
Denis Molony 2020-12-30 13:06:50 +10:00
parent 5815bf5048
commit dcecfb1399
6 changed files with 690 additions and 543 deletions

View File

@ -15,30 +15,14 @@ import com.bytezone.diskbrowser.utilities.Utility;
public class ApplesoftBasicProgram extends BasicProgram public class ApplesoftBasicProgram extends BasicProgram
// -----------------------------------------------------------------------------------// // -----------------------------------------------------------------------------------//
{ {
private static final byte TOKEN_FOR = (byte) 0x81;
private static final byte TOKEN_NEXT = (byte) 0x82;
private static final byte TOKEN_DATA = (byte) 0x83;
private static final byte TOKEN_INPUT = (byte) 0x84;
private static final byte TOKEN_DIM = (byte) 0x86;
private static final byte TOKEN_LET = (byte) 0xAA;
private static final byte TOKEN_GOTO = (byte) 0xAB;
private static final byte TOKEN_IF = (byte) 0xAD;
private static final byte TOKEN_GOSUB = (byte) 0xB0;
private static final byte TOKEN_RETURN = (byte) 0xB1;
private static final byte TOKEN_REM = (byte) 0xB2;
private static final byte TOKEN_ON = (byte) 0xB4;
private static final byte TOKEN_PRINT = (byte) 0xBA;
private static final byte TOKEN_THEN = (byte) 0xC4;
private static final byte TOKEN_EQUALS = (byte) 0xD0;
private final List<SourceLine> sourceLines = new ArrayList<> (); private final List<SourceLine> sourceLines = new ArrayList<> ();
private final int endPtr; private final int endPtr;
private final Map<Integer, List<Integer>> gotoLines = new TreeMap<> (); final Map<Integer, List<Integer>> gotoLines = new TreeMap<> ();
private final Map<Integer, List<Integer>> gosubLines = new TreeMap<> (); final Map<Integer, List<Integer>> gosubLines = new TreeMap<> ();
private final Map<String, List<Integer>> symbolLines = new TreeMap<> (); final Map<String, List<Integer>> symbolLines = new TreeMap<> ();
private final Map<String, List<String>> uniqueSymbols = new TreeMap<> (); final Map<String, List<String>> uniqueSymbols = new TreeMap<> ();
private final List<Integer> stringsLine = new ArrayList<> (); final List<Integer> stringsLine = new ArrayList<> ();
private final List<String> stringsText = new ArrayList<> (); final List<String> stringsText = new ArrayList<> ();
// ---------------------------------------------------------------------------------// // ---------------------------------------------------------------------------------//
public ApplesoftBasicProgram (String name, byte[] buffer) public ApplesoftBasicProgram (String name, byte[] buffer)
@ -56,7 +40,7 @@ public class ApplesoftBasicProgram extends BasicProgram
if (nextAddress <= prevOffset) // usually zero if (nextAddress <= prevOffset) // usually zero
break; break;
SourceLine line = new SourceLine (ptr); SourceLine line = new SourceLine (this, buffer, ptr);
sourceLines.add (line); sourceLines.add (line);
ptr += line.length; ptr += line.length;
prevOffset = nextAddress; prevOffset = nextAddress;
@ -102,7 +86,7 @@ public class ApplesoftBasicProgram extends BasicProgram
// A REM statement might conceal an assembler routine // A REM statement might conceal an assembler routine
// - see P.CREATE on Diags2E.DSK // - see P.CREATE on Diags2E.DSK
if (subline.is (TOKEN_REM) && subline.containsToken ()) if (subline.is (ApplesoftConstants.TOKEN_REM) && subline.containsToken ())
{ {
int address = subline.getAddress () + 1; // skip the REM token int address = subline.getAddress () + 1; // skip the REM token
fullText.append (text + String.format ("REM - Inline assembler @ $%02X (%d)%n", fullText.append (text + String.format ("REM - Inline assembler @ $%02X (%d)%n",
@ -114,7 +98,7 @@ public class ApplesoftBasicProgram extends BasicProgram
} }
// Beagle Bros often have multiline REM statements // Beagle Bros often have multiline REM statements
if (subline.is (TOKEN_REM) && subline.containsControlChars ()) if (subline.is (ApplesoftConstants.TOKEN_REM) && subline.containsControlChars ())
{ {
subline.addFormattedRem (text); subline.addFormattedRem (text);
fullText.append (text + "\n"); fullText.append (text + "\n");
@ -122,7 +106,7 @@ public class ApplesoftBasicProgram extends BasicProgram
} }
// Reduce the indent by each NEXT, but only as far as the IF indent allows // Reduce the indent by each NEXT, but only as far as the IF indent allows
if (subline.is (TOKEN_NEXT)) if (subline.is (ApplesoftConstants.TOKEN_NEXT))
{ {
popLoopVariables (loopVariables, subline); popLoopVariables (loopVariables, subline);
indent = Math.max (ifIndent, loopVariables.size ()); indent = Math.max (ifIndent, loopVariables.size ());
@ -139,11 +123,13 @@ public class ApplesoftBasicProgram extends BasicProgram
{ {
// Prepare target indicators for subsequent sublines (ie no line number) // Prepare target indicators for subsequent sublines (ie no line number)
if (basicPreferences.showTargets && !subline.isFirst ()) if (basicPreferences.showTargets && !subline.isFirst ())
if (subline.is (TOKEN_GOSUB) if (subline.is (ApplesoftConstants.TOKEN_GOSUB)
|| (subline.is (TOKEN_ON) && subline.has (TOKEN_GOSUB))) || (subline.is (ApplesoftConstants.TOKEN_ON)
&& subline.has (ApplesoftConstants.TOKEN_GOSUB)))
text.append ("<<--"); text.append ("<<--");
else if (subline.is (TOKEN_GOTO) || subline.isImpliedGoto () else if (subline.is (ApplesoftConstants.TOKEN_GOTO)
|| (subline.is (TOKEN_ON) && subline.has (TOKEN_GOTO))) || subline.isImpliedGoto () || (subline.is (ApplesoftConstants.TOKEN_ON)
&& subline.has (ApplesoftConstants.TOKEN_GOTO)))
text.append (" <--"); text.append (" <--");
// Align assign statements if required // Align assign statements if required
@ -156,30 +142,33 @@ public class ApplesoftBasicProgram extends BasicProgram
} }
// Add the current text, then reset it // Add the current text, then reset it
int pos = subline.is (TOKEN_REM) ? 0 : alignPos; int pos = subline.is (ApplesoftConstants.TOKEN_REM) ? 0 : alignPos;
String lineText = subline.getAlignedText (pos); String lineText = subline.getAlignedText (pos);
if (subline.is (TOKEN_REM) && basicPreferences.deleteExtraRemSpace) if (subline.is (ApplesoftConstants.TOKEN_REM)
&& basicPreferences.deleteExtraRemSpace)
lineText = lineText.replaceFirst ("REM ", "REM "); lineText = lineText.replaceFirst ("REM ", "REM ");
if (subline.is (TOKEN_DATA) && basicPreferences.deleteExtraDataSpace) if (subline.is (ApplesoftConstants.TOKEN_DATA)
&& basicPreferences.deleteExtraDataSpace)
lineText = lineText.replaceFirst ("DATA ", "DATA "); lineText = lineText.replaceFirst ("DATA ", "DATA ");
// Check for a wrappable REM statement // Check for a wrappable REM statement
// (see SEA BATTLE on DISK283.DSK) // (see SEA BATTLE on DISK283.DSK)
int inset = Math.max (text.length (), getIndent (fullText)) + 1; int inset = Math.max (text.length (), getIndent (fullText)) + 1;
if (subline.is (TOKEN_REM) && lineText.length () > basicPreferences.wrapRemAt) if (subline.is (ApplesoftConstants.TOKEN_REM)
&& lineText.length () > basicPreferences.wrapRemAt)
{ {
List<String> lines = splitLine (lineText, basicPreferences.wrapRemAt, ' '); List<String> lines = splitLine (lineText, basicPreferences.wrapRemAt, ' ');
addSplitLines (lines, text, inset); addSplitLines (lines, text, inset);
} }
else if (subline.is (TOKEN_DATA) else if (subline.is (ApplesoftConstants.TOKEN_DATA)
&& lineText.length () > basicPreferences.wrapDataAt) && lineText.length () > basicPreferences.wrapDataAt)
{ {
List<String> lines = splitLine (lineText, basicPreferences.wrapDataAt, ','); List<String> lines = splitLine (lineText, basicPreferences.wrapDataAt, ',');
addSplitLines (lines, text, inset); addSplitLines (lines, text, inset);
} }
else if (subline.is (TOKEN_DIM) && basicPreferences.splitDim) else if (subline.is (ApplesoftConstants.TOKEN_DIM) && basicPreferences.splitDim)
{ {
List<String> lines = splitDim (lineText); List<String> lines = splitDim (lineText);
addSplitLines (lines, text, inset); addSplitLines (lines, text, inset);
@ -190,9 +179,10 @@ public class ApplesoftBasicProgram extends BasicProgram
// Check for a wrappable PRINT statement // Check for a wrappable PRINT statement
// (see FROM MACHINE LANGUAGE TO BASIC on DOSToolkit2eB.dsk) // (see FROM MACHINE LANGUAGE TO BASIC on DOSToolkit2eB.dsk)
if (basicPreferences.wrapPrintAt > 0 // if (basicPreferences.wrapPrintAt > 0 //
&& (subline.is (TOKEN_PRINT) || subline.is (TOKEN_INPUT)) && (subline.is (ApplesoftConstants.TOKEN_PRINT)
&& countChars (text, ASCII_QUOTE) == 2 // just start and end quotes || subline.is (ApplesoftConstants.TOKEN_INPUT))
&& countChars (text, ASCII_CARET) == 0) // no control characters && countChars (text, Utility.ASCII_QUOTE) == 2 // just start and end quotes
&& countChars (text, Utility.ASCII_CARET) == 0) // no control characters
// && countChars (text, ASCII_SEMI_COLON) == 0) // && countChars (text, ASCII_SEMI_COLON) == 0)
{ {
if (true) // new method if (true) // new method
@ -238,14 +228,15 @@ public class ApplesoftBasicProgram extends BasicProgram
text.setLength (0); text.setLength (0);
// Calculate indent changes that take effect after the current subline // Calculate indent changes that take effect after the current subline
if (subline.is (TOKEN_IF)) if (subline.is (ApplesoftConstants.TOKEN_IF))
ifIndent = ++indent; ifIndent = ++indent;
else if (subline.is (TOKEN_FOR)) else if (subline.is (ApplesoftConstants.TOKEN_FOR))
{ {
loopVariables.push (subline.forVariable); loopVariables.push (subline.forVariable);
++indent; ++indent;
} }
else if (basicPreferences.blankAfterReturn && subline.is (TOKEN_RETURN)) else if (basicPreferences.blankAfterReturn
&& subline.is (ApplesoftConstants.TOKEN_RETURN))
insertBlankLine = true; insertBlankLine = true;
} }
@ -284,8 +275,11 @@ public class ApplesoftBasicProgram extends BasicProgram
fullText.append ("Variables:\n"); fullText.append ("Variables:\n");
int longestVarName = getLongestVarName ();
String format = longestVarName > 6 ? "%" + longestVarName + "s %s%n" : "%6s %s%n";
for (String symbol : symbolLines.keySet ()) for (String symbol : symbolLines.keySet ())
fullText.append (String.format ("%6s %s%n", symbol, symbolLines.get (symbol))); fullText.append (String.format (format, symbol, symbolLines.get (symbol)));
} }
if (basicPreferences.showDuplicateSymbols && !uniqueSymbols.isEmpty ()) if (basicPreferences.showDuplicateSymbols && !uniqueSymbols.isEmpty ())
@ -328,6 +322,17 @@ public class ApplesoftBasicProgram extends BasicProgram
return fullText.toString (); return fullText.toString ();
} }
// ---------------------------------------------------------------------------------//
private int getLongestVarName ()
// ---------------------------------------------------------------------------------//
{
int longestName = 0;
for (String symbol : symbolLines.keySet ())
if (symbol.length () > longestName)
longestName = symbol.length ();
return longestName;
}
// ---------------------------------------------------------------------------------// // ---------------------------------------------------------------------------------//
private void showLines (StringBuilder fullText, Map<Integer, List<Integer>> lines, private void showLines (StringBuilder fullText, Map<Integer, List<Integer>> lines,
String heading) String heading)
@ -489,10 +494,13 @@ public class ApplesoftBasicProgram extends BasicProgram
SubLine subline = line.sublines.get (0); SubLine subline = line.sublines.get (0);
String c1 = " ", c2 = " "; String c1 = " ", c2 = " ";
if (subline.is (TOKEN_GOSUB) || (subline.is (TOKEN_ON) && subline.has (TOKEN_GOSUB))) if (subline.is (ApplesoftConstants.TOKEN_GOSUB)
|| (subline.is (ApplesoftConstants.TOKEN_ON)
&& subline.has (ApplesoftConstants.TOKEN_GOSUB)))
c1 = "<<"; c1 = "<<";
else if (subline.is (TOKEN_GOTO) else if (subline.is (ApplesoftConstants.TOKEN_GOTO)
|| (subline.is (TOKEN_ON) && subline.has (TOKEN_GOTO))) || (subline.is (ApplesoftConstants.TOKEN_ON)
&& subline.has (ApplesoftConstants.TOKEN_GOTO)))
c1 = " <"; c1 = " <";
if (gotoLines.containsKey (line.lineNumber)) if (gotoLines.containsKey (line.lineNumber))
@ -553,7 +561,7 @@ public class ApplesoftBasicProgram extends BasicProgram
} }
else if (subline == startSubline) else if (subline == startSubline)
started = true; started = true;
else if (subline.is (TOKEN_IF)) else if (subline.is (ApplesoftConstants.TOKEN_IF))
inIf = true; inIf = true;
} }
if (started && inIf) if (started && inIf)
@ -616,7 +624,7 @@ public class ApplesoftBasicProgram extends BasicProgram
} }
// ---------------------------------------------------------------------------------// // ---------------------------------------------------------------------------------//
private int getLoadAddress () int getLoadAddress ()
// ---------------------------------------------------------------------------------// // ---------------------------------------------------------------------------------//
{ {
int programLoadAddress = 0; int programLoadAddress = 0;
@ -671,489 +679,4 @@ public class ApplesoftBasicProgram extends BasicProgram
return true; return true;
return false; return false;
} }
// ---------------------------------------------------------------------------------//
private class SourceLine
// ---------------------------------------------------------------------------------//
{
List<SubLine> sublines = new ArrayList<> ();
int lineNumber;
int linePtr;
int length;
SourceLine (int ptr)
{
linePtr = ptr;
lineNumber = Utility.unsignedShort (buffer, ptr + 2);
int startPtr = ptr += 4;
boolean inString = false; // can toggle
boolean inRemark = false; // can only go false -> true
byte b;
int stringPtr = 0;
while (ptr < buffer.length && (b = buffer[ptr++]) != 0)
{
if (inRemark) // cannot terminate a REM
continue;
if (inString)
{
if (b == ASCII_QUOTE) // terminate string
{
inString = false;
String s = new String (buffer, stringPtr - 1, ptr - stringPtr + 1);
stringsText.add (s);
stringsLine.add (lineNumber);
}
continue;
}
switch (b)
{
// break IF statements into two sublines (allows for easier line indenting)
case TOKEN_IF:
// skip to THEN or GOTO - if not found then it's an error
while (buffer[ptr] != TOKEN_THEN && buffer[ptr] != TOKEN_GOTO
&& buffer[ptr] != 0)
ptr++;
// keep THEN with the IF
if (buffer[ptr] == TOKEN_THEN)
++ptr;
// create subline from the condition (and THEN if it exists)
sublines.add (new SubLine (this, startPtr, ptr - startPtr));
startPtr = ptr;
break;
// end of subline, so add it, advance startPtr and continue
case ASCII_COLON:
sublines.add (new SubLine (this, startPtr, ptr - startPtr));
startPtr = ptr;
break;
case TOKEN_REM:
if (ptr != startPtr + 1) // REM appears mid-line (should follow a colon)
{
System.out.println ("mid-line REM token");
// System.out.println (HexFormatter.format (buffer, startPtr, 10));
sublines.add (new SubLine (this, startPtr, (ptr - startPtr) - 1));
startPtr = ptr - 1;
}
else
inRemark = true;
break;
case ASCII_QUOTE:
inString = true;
stringPtr = ptr;
break;
}
}
// add whatever is left
sublines.add (new SubLine (this, startPtr, ptr - startPtr));
this.length = ptr - linePtr;
}
}
// ---------------------------------------------------------------------------------//
private class SubLine
// ---------------------------------------------------------------------------------//
{
SourceLine parent;
int startPtr;
int length;
String[] nextVariables;
String forVariable = "";
String onExpression = "";
int assignEqualPos; // used for aligning the equals sign
SubLine (SourceLine parent, int startPtr, int length)
{
this.parent = parent;
this.startPtr = startPtr;
this.length = length;
byte firstByte = buffer[startPtr];
if (isHighBitSet (firstByte))
doToken (firstByte);
else if (isDigit (firstByte))
doDigit ();
else
doAlpha ();
if (is (TOKEN_REM) || is (TOKEN_DATA))
return;
int ptr = startPtr;
length--;
String var = "";
boolean inQuote = false;
while (length-- > 0)
{
byte b = buffer[ptr++];
if (inQuote && b != ASCII_QUOTE)
continue;
if (isPossibleVariable (b))
var += (char) b;
else
{
checkVar (var, b);
var = "";
if (b == ASCII_QUOTE)
inQuote = !inQuote;
}
}
checkVar (var, (byte) 0);
}
private void checkVar (String var, byte term)
{
if (var.length () == 0)
return;
if (term == ASCII_LEFT_BRACKET)
var += "(";
if (isLetter ((byte) var.charAt (0)))
{
List<Integer> lines = symbolLines.get (var);
if (lines == null)
{
lines = new ArrayList<> ();
symbolLines.put (var, lines);
}
if (lines.size () == 0)
lines.add (parent.lineNumber);
else
{
int lastLine = lines.get (lines.size () - 1);
if (lastLine != parent.lineNumber)
lines.add (parent.lineNumber);
}
checkUniqueName (var);
}
}
private void doToken (byte b)
{
switch (b)
{
case TOKEN_FOR:
int p = startPtr + 1;
while (buffer[p] != TOKEN_EQUALS)
forVariable += (char) buffer[p++];
break;
case TOKEN_NEXT:
if (length == 2) // no variables
nextVariables = new String[0];
else
{
String varList = new String (buffer, startPtr + 1, length - 2);
nextVariables = varList.split (",");
}
break;
case TOKEN_LET:
recordEqualsPosition ();
break;
case TOKEN_GOTO:
int targetLine = getLineNumber (buffer, startPtr + 1);
addXref (targetLine, gotoLines);
break;
case TOKEN_GOSUB:
targetLine = getLineNumber (buffer, startPtr + 1);
addXref (targetLine, gosubLines);
break;
case TOKEN_ON:
p = startPtr + 1;
int max = startPtr + length - 1;
while (p < max && buffer[p] != TOKEN_GOTO && buffer[p] != TOKEN_GOSUB)
{
if (isHighBitSet (buffer[p]))
{
int val = buffer[p] & 0x7F;
if (val < ApplesoftConstants.tokens.length)
onExpression += " " + ApplesoftConstants.tokens[val];
}
else
onExpression += (char) (buffer[p]);
p++;
}
switch (buffer[p++])
{
case TOKEN_GOSUB:
for (int destLine : getLineNumbers (buffer, p))
addXref (destLine, gosubLines);
break;
case TOKEN_GOTO:
for (int destLine : getLineNumbers (buffer, p))
addXref (destLine, gotoLines);
break;
default:
System.out.println ("GOTO / GOSUB not found");
}
break;
}
}
private void checkUniqueName (String symbol)
{
int ptr = symbol.length () - 1;
if (symbol.charAt (ptr) == ASCII_LEFT_BRACKET) // array
ptr--;
if (symbol.charAt (ptr) == ASCII_DOLLAR || symbol.charAt (ptr) == ASCII_PERCENT)
ptr--;
String unique =
(ptr <= 1) ? symbol : symbol.substring (0, 2) + symbol.substring (ptr + 1);
List<String> usage = uniqueSymbols.get (unique);
if (usage == null)
{
usage = new ArrayList<> ();
uniqueSymbols.put (unique, usage);
}
if (!usage.contains (symbol))
usage.add (symbol);
}
private void doDigit ()
{
int targetLine = getLineNumber (buffer, startPtr);
addXref (targetLine, gotoLines);
}
private void doAlpha ()
{
recordEqualsPosition ();
}
private List<Integer> getLineNumbers (byte[] buffer, int ptr)
{
List<Integer> lineNumbers = new ArrayList<> ();
int start = ptr;
while (ptr < buffer.length && buffer[ptr] != 0 && buffer[ptr] != ASCII_COLON)
ptr++;
String s = new String (buffer, start, ptr - start);
String[] chunks = s.split (",");
try
{
for (String chunk : chunks)
lineNumbers.add (Integer.parseInt (chunk));
}
catch (NumberFormatException e)
{
System.out.printf ("NFE: %s%n", s);
}
return lineNumbers;
}
private int getLineNumber (byte[] buffer, int ptr)
{
int lineNumber = 0;
while (ptr < buffer.length)
{
int b = (buffer[ptr++] & 0xFF) - 0x30;
if (b < 0 || b > 9)
break;
lineNumber = lineNumber * 10 + b;
}
return lineNumber;
}
private void addXref (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 (parent.lineNumber);
}
private boolean isImpliedGoto ()
{
byte b = buffer[startPtr];
if (isHighBitSet (b))
return false;
return (isDigit (b));
}
// Record the position of the equals sign so it can be aligned with adjacent lines.
private void recordEqualsPosition ()
{
int p = startPtr + 1;
int max = startPtr + length;
while (buffer[p] != TOKEN_EQUALS && p < max)
p++;
if (buffer[p] == TOKEN_EQUALS)
assignEqualPos = toString ().indexOf ('='); // use expanded line
}
private boolean isJoinableRem ()
{
return is (TOKEN_REM) && !isFirst ();
}
boolean isFirst ()
{
return (parent.linePtr + 4) == startPtr;
}
boolean is (byte token)
{
return buffer[startPtr] == token;
}
boolean has (byte token)
{
int ptr = startPtr + 1;
int max = startPtr + length;
while (ptr < max)
{
if (buffer[ptr++] == token)
return true;
}
return false;
}
boolean isEmpty ()
{
return length == 1 && buffer[startPtr] == 0;
}
boolean containsToken ()
{
// ignore first byte, check the rest for tokens
for (int p = startPtr + 1, max = startPtr + length; p < max; p++)
if (isHighBitSet (buffer[p]))
return true;
return false;
}
boolean containsControlChars ()
{
for (int p = startPtr + 1, max = startPtr + length; p < max; p++)
{
int c = buffer[p] & 0xFF;
if (c == 0)
break;
if (c < 32)
return true;
}
return false;
}
void addFormattedRem (StringBuilder text)
{
int ptr = startPtr + 1;
int max = startPtr + length - 2;
while (ptr <= max)
{
int c = buffer[ptr] & 0xFF;
// System.out.printf ("%02X %s%n", c, (char) c);
if (c == 0x08 && text.length () > 0)
text.deleteCharAt (text.length () - 1);
else if (c == 0x0D)
text.append ("\n");
else
text.append ((char) c);
ptr++;
}
}
public int getAddress ()
{
return getLoadAddress () + startPtr;
}
public String getAlignedText (int alignPosition)
{
StringBuilder line = toStringBuilder ();
while (alignPosition-- > assignEqualPos)
line.insert (assignEqualPos, ' ');
return line.toString ();
}
// A REM statement might conceal an assembler routine
public String[] getAssembler ()
{
byte[] buffer2 = new byte[length - 1];
System.arraycopy (buffer, startPtr + 1, buffer2, 0, buffer2.length);
AssemblerProgram program =
new AssemblerProgram ("REM assembler", buffer2, getAddress () + 1);
return program.getAssembler ().split ("\n");
}
@Override
public String toString ()
{
return toStringBuilder ().toString ();
}
public StringBuilder toStringBuilder ()
{
StringBuilder line = new StringBuilder ();
// All sublines end with 0 or : except IF lines that are split into two
int max = startPtr + length - 1;
if (buffer[max] == 0)
--max;
if (isImpliedGoto () && !basicPreferences.showThen)
line.append ("GOTO ");
for (int p = startPtr; p <= max; p++)
{
byte b = buffer[p];
if (isHighBitSet (b))
{
if (line.length () > 0 && line.charAt (line.length () - 1) != ' ')
line.append (' ');
int val = b & 0x7F;
if (val < ApplesoftConstants.tokens.length)
{
if (b != TOKEN_THEN || basicPreferences.showThen)
line.append (ApplesoftConstants.tokens[val]);
}
}
else if (isControlCharacter (b))
line.append (basicPreferences.showCaret ? "^" + (char) (b + 64) : "");
else
line.append ((char) b);
}
return line;
}
}
} }

View File

@ -39,6 +39,22 @@ public interface ApplesoftConstants
"", "", "", "", // 0xFC - 0xFF "", "", "", "", // 0xFC - 0xFF
}; };
static final byte TOKEN_FOR = (byte) 0x81;
static final byte TOKEN_NEXT = (byte) 0x82;
static final byte TOKEN_DATA = (byte) 0x83;
static final byte TOKEN_INPUT = (byte) 0x84;
static final byte TOKEN_DIM = (byte) 0x86;
static final byte TOKEN_LET = (byte) 0xAA;
static final byte TOKEN_GOTO = (byte) 0xAB;
static final byte TOKEN_IF = (byte) 0xAD;
static final byte TOKEN_GOSUB = (byte) 0xB0;
static final byte TOKEN_RETURN = (byte) 0xB1;
static final byte TOKEN_REM = (byte) 0xB2;
static final byte TOKEN_ON = (byte) 0xB4;
static final byte TOKEN_PRINT = (byte) 0xBA;
static final byte TOKEN_THEN = (byte) 0xC4;
static final byte TOKEN_EQUALS = (byte) 0xD0;
int[] tokenAddresses = int[] tokenAddresses =
{ 0xD870, 0xD766, 0xDCF9, 0xD995, 0xDBB2, 0xF331, 0xDFD9, 0xDBE2, 0xF390, 0xF399, { 0xD870, 0xD766, 0xDCF9, 0xD995, 0xDBB2, 0xF331, 0xDFD9, 0xDBE2, 0xF390, 0xF399,
0xF1E5, 0xF1DE, 0xF1D5, 0xF225, 0xF232, 0xF241, 0xF3D8, 0xF3E2, 0xF6E9, 0xF6FE, 0xF1E5, 0xF1DE, 0xF1D5, 0xF225, 0xF232, 0xF241, 0xF3D8, 0xF3E2, 0xF6E9, 0xF6FE,

View File

@ -1,19 +1,12 @@
package com.bytezone.diskbrowser.applefile; package com.bytezone.diskbrowser.applefile;
import com.bytezone.diskbrowser.gui.BasicPreferences; import com.bytezone.diskbrowser.gui.BasicPreferences;
import com.bytezone.diskbrowser.utilities.Utility;
// -----------------------------------------------------------------------------------// // -----------------------------------------------------------------------------------//
public abstract class BasicProgram extends AbstractFile public abstract class BasicProgram extends AbstractFile
// -----------------------------------------------------------------------------------// // -----------------------------------------------------------------------------------//
{ {
static final byte ASCII_QUOTE = 0x22;
static final byte ASCII_DOLLAR = 0x24;
static final byte ASCII_PERCENT = 0x25;
static final byte ASCII_LEFT_BRACKET = 0x28;
static final byte ASCII_COLON = 0x3A;
static final byte ASCII_SEMI_COLON = 0x3B;
static final byte ASCII_CARET = 0x5E;
static BasicPreferences basicPreferences; // set by MenuHandler static BasicPreferences basicPreferences; // set by MenuHandler
// ---------------------------------------------------------------------------------// // ---------------------------------------------------------------------------------//
@ -63,7 +56,7 @@ public abstract class BasicProgram extends AbstractFile
boolean isPossibleVariable (byte value) boolean isPossibleVariable (byte value)
// ---------------------------------------------------------------------------------// // ---------------------------------------------------------------------------------//
{ {
return isDigit (value) || isLetter (value) || value == ASCII_DOLLAR return isDigit (value) || isLetter (value) || value == Utility.ASCII_DOLLAR
|| value == ASCII_PERCENT; || value == Utility.ASCII_PERCENT;
} }
} }

View File

@ -0,0 +1,100 @@
package com.bytezone.diskbrowser.applefile;
import java.util.ArrayList;
import java.util.List;
import com.bytezone.diskbrowser.utilities.Utility;
// -----------------------------------------------------------------------------------//
public class SourceLine
// -----------------------------------------------------------------------------------//
{
List<SubLine> sublines = new ArrayList<> ();
int lineNumber;
int linePtr;
int length;
byte[] buffer;
ApplesoftBasicProgram parent;
// ---------------------------------------------------------------------------------//
SourceLine (ApplesoftBasicProgram parent, byte[] buffer, int ptr)
// ---------------------------------------------------------------------------------//
{
this.parent = parent;
this.buffer = buffer;
linePtr = ptr;
lineNumber = Utility.unsignedShort (buffer, ptr + 2);
int startPtr = ptr += 4;
boolean inString = false; // can toggle
boolean inRemark = false; // can only go false -> true
byte b;
int stringPtr = 0;
while (ptr < buffer.length && (b = buffer[ptr++]) != 0)
{
if (inRemark) // cannot terminate a REM
continue;
if (inString)
{
if (b == Utility.ASCII_QUOTE) // terminate string
{
inString = false;
String s = new String (buffer, stringPtr - 1, ptr - stringPtr + 1);
parent.stringsText.add (s);
parent.stringsLine.add (lineNumber);
}
continue;
}
switch (b)
{
// break IF statements into two sublines (allows for easier line indenting)
case ApplesoftConstants.TOKEN_IF:
// skip to THEN or GOTO - if not found then it's an error
while (buffer[ptr] != ApplesoftConstants.TOKEN_THEN
&& buffer[ptr] != ApplesoftConstants.TOKEN_GOTO && buffer[ptr] != 0)
ptr++;
// keep THEN with the IF
if (buffer[ptr] == ApplesoftConstants.TOKEN_THEN)
++ptr;
// create subline from the condition (and THEN if it exists)
sublines.add (new SubLine (this, startPtr, ptr - startPtr));
startPtr = ptr;
break;
// end of subline, so add it, advance startPtr and continue
case Utility.ASCII_COLON:
sublines.add (new SubLine (this, startPtr, ptr - startPtr));
startPtr = ptr;
break;
case ApplesoftConstants.TOKEN_REM:
if (ptr != startPtr + 1) // REM appears mid-line (should follow a colon)
{
System.out.println ("mid-line REM token");
// System.out.println (HexFormatter.format (buffer, startPtr, 10));
sublines.add (new SubLine (this, startPtr, (ptr - startPtr) - 1));
startPtr = ptr - 1;
}
else
inRemark = true;
break;
case Utility.ASCII_QUOTE:
inString = true;
stringPtr = ptr;
break;
}
}
// add whatever is left
sublines.add (new SubLine (this, startPtr, ptr - startPtr));
this.length = ptr - linePtr;
}
}

View File

@ -0,0 +1,470 @@
package com.bytezone.diskbrowser.applefile;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import com.bytezone.diskbrowser.utilities.Utility;
// -----------------------------------------------------------------------------------//
public class SubLine
// -----------------------------------------------------------------------------------//
{
SourceLine parent;
ApplesoftBasicProgram program;
int startPtr;
int length;
String[] nextVariables;
String forVariable = "";
String onExpression = "";
int assignEqualPos; // used for aligning the equals sign
byte[] buffer;
private final Map<Integer, List<Integer>> gotoLines;
private final Map<Integer, List<Integer>> gosubLines;
private final Map<String, List<Integer>> symbolLines;
private final Map<String, List<String>> uniqueSymbols;
// ---------------------------------------------------------------------------------//
SubLine (SourceLine parent, int startPtr, int length)
// ---------------------------------------------------------------------------------//
{
this.parent = parent;
this.startPtr = startPtr;
this.length = length;
program = parent.parent;
this.gotoLines = program.gotoLines;
this.gosubLines = program.gosubLines;
this.symbolLines = program.symbolLines;
this.uniqueSymbols = program.uniqueSymbols;
this.buffer = parent.buffer;
byte firstByte = parent.buffer[startPtr];
if (Utility.isHighBitSet (firstByte))
doToken (firstByte);
else if (Utility.isDigit (firstByte))
doDigit ();
else
doAlpha ();
if (is (ApplesoftConstants.TOKEN_REM) || is (ApplesoftConstants.TOKEN_DATA))
return;
int ptr = startPtr;
length--;
String var = "";
boolean inQuote = false;
while (length-- > 0)
{
byte b = parent.buffer[ptr++];
if (inQuote && b != Utility.ASCII_QUOTE)
continue;
if (Utility.isPossibleVariable (b))
var += (char) b;
else
{
checkVar (var, b);
var = "";
if (b == Utility.ASCII_QUOTE)
inQuote = !inQuote;
}
}
checkVar (var, (byte) 0);
}
// ---------------------------------------------------------------------------------//
private void checkVar (String var, byte term)
// ---------------------------------------------------------------------------------//
{
if (var.length () == 0)
return;
if (term == Utility.ASCII_LEFT_BRACKET)
var += "(";
if (Utility.isLetter ((byte) var.charAt (0)))
{
List<Integer> lines = symbolLines.get (var);
if (lines == null)
{
lines = new ArrayList<> ();
symbolLines.put (var, lines);
}
if (lines.size () == 0)
lines.add (parent.lineNumber);
else
{
int lastLine = lines.get (lines.size () - 1);
if (lastLine != parent.lineNumber)
lines.add (parent.lineNumber);
}
checkUniqueName (var);
}
}
// ---------------------------------------------------------------------------------//
private void doToken (byte b)
// ---------------------------------------------------------------------------------//
{
switch (b)
{
case ApplesoftConstants.TOKEN_FOR:
int p = startPtr + 1;
while (parent.buffer[p] != ApplesoftConstants.TOKEN_EQUALS)
forVariable += (char) parent.buffer[p++];
break;
case ApplesoftConstants.TOKEN_NEXT:
if (length == 2) // no variables
nextVariables = new String[0];
else
{
String varList = new String (parent.buffer, startPtr + 1, length - 2);
nextVariables = varList.split (",");
}
break;
case ApplesoftConstants.TOKEN_LET:
recordEqualsPosition ();
break;
case ApplesoftConstants.TOKEN_GOTO:
int targetLine = getLineNumber (parent.buffer, startPtr + 1);
addXref (targetLine, gotoLines);
break;
case ApplesoftConstants.TOKEN_GOSUB:
targetLine = getLineNumber (parent.buffer, startPtr + 1);
addXref (targetLine, gosubLines);
break;
case ApplesoftConstants.TOKEN_ON:
p = startPtr + 1;
int max = startPtr + length - 1;
while (p < max && parent.buffer[p] != ApplesoftConstants.TOKEN_GOTO
&& parent.buffer[p] != ApplesoftConstants.TOKEN_GOSUB)
{
if (Utility.isHighBitSet (parent.buffer[p]))
{
int val = parent.buffer[p] & 0x7F;
if (val < ApplesoftConstants.tokens.length)
onExpression += " " + ApplesoftConstants.tokens[val];
}
else
onExpression += (char) (parent.buffer[p]);
p++;
}
switch (parent.buffer[p++])
{
case ApplesoftConstants.TOKEN_GOSUB:
for (int destLine : getLineNumbers (parent.buffer, p))
addXref (destLine, gosubLines);
break;
case ApplesoftConstants.TOKEN_GOTO:
for (int destLine : getLineNumbers (parent.buffer, p))
addXref (destLine, gotoLines);
break;
default:
System.out.println ("GOTO / GOSUB not found");
}
break;
}
}
// ---------------------------------------------------------------------------------//
private void checkUniqueName (String symbol)
// ---------------------------------------------------------------------------------//
{
int ptr = symbol.length () - 1;
if (symbol.charAt (ptr) == Utility.ASCII_LEFT_BRACKET) // array
ptr--;
if (symbol.charAt (ptr) == Utility.ASCII_DOLLAR
|| symbol.charAt (ptr) == Utility.ASCII_PERCENT)
ptr--;
String unique =
(ptr <= 1) ? symbol : symbol.substring (0, 2) + symbol.substring (ptr + 1);
List<String> usage = uniqueSymbols.get (unique);
if (usage == null)
{
usage = new ArrayList<> ();
uniqueSymbols.put (unique, usage);
}
if (!usage.contains (symbol))
usage.add (symbol);
}
// ---------------------------------------------------------------------------------//
private void doDigit ()
// ---------------------------------------------------------------------------------//
{
int targetLine = getLineNumber (parent.buffer, startPtr);
addXref (targetLine, gotoLines);
}
// ---------------------------------------------------------------------------------//
private void doAlpha ()
// ---------------------------------------------------------------------------------//
{
recordEqualsPosition ();
}
// ---------------------------------------------------------------------------------//
private List<Integer> getLineNumbers (byte[] buffer, int ptr)
// ---------------------------------------------------------------------------------//
{
List<Integer> lineNumbers = new ArrayList<> ();
int start = ptr;
while (ptr < buffer.length && buffer[ptr] != 0 && buffer[ptr] != Utility.ASCII_COLON)
ptr++;
String s = new String (buffer, start, ptr - start);
String[] chunks = s.split (",");
try
{
for (String chunk : chunks)
lineNumbers.add (Integer.parseInt (chunk));
}
catch (NumberFormatException e)
{
System.out.printf ("NFE: %s%n", s);
}
return lineNumbers;
}
// ---------------------------------------------------------------------------------//
private int getLineNumber (byte[] buffer, int ptr)
// ---------------------------------------------------------------------------------//
{
int lineNumber = 0;
while (ptr < buffer.length)
{
int b = (buffer[ptr++] & 0xFF) - 0x30;
if (b < 0 || b > 9)
break;
lineNumber = lineNumber * 10 + b;
}
return lineNumber;
}
// ---------------------------------------------------------------------------------//
private void addXref (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 (parent.lineNumber);
}
// ---------------------------------------------------------------------------------//
boolean isImpliedGoto ()
// ---------------------------------------------------------------------------------//
{
byte b = parent.buffer[startPtr];
if (Utility.isHighBitSet (b))
return false;
return (Utility.isDigit (b));
}
// Record the position of the equals sign so it can be aligned with adjacent lines.
// ---------------------------------------------------------------------------------//
private void recordEqualsPosition ()
// ---------------------------------------------------------------------------------//
{
int p = startPtr + 1;
int max = startPtr + length;
while (parent.buffer[p] != ApplesoftConstants.TOKEN_EQUALS && p < max)
p++;
if (buffer[p] == ApplesoftConstants.TOKEN_EQUALS)
assignEqualPos = toString ().indexOf ('='); // use expanded line
}
// ---------------------------------------------------------------------------------//
boolean isJoinableRem ()
// ---------------------------------------------------------------------------------//
{
return is (ApplesoftConstants.TOKEN_REM) && !isFirst ();
}
// ---------------------------------------------------------------------------------//
boolean isFirst ()
// ---------------------------------------------------------------------------------//
{
return (parent.linePtr + 4) == startPtr;
}
// ---------------------------------------------------------------------------------//
boolean is (byte token)
// ---------------------------------------------------------------------------------//
{
return parent.buffer[startPtr] == token;
}
// ---------------------------------------------------------------------------------//
boolean has (byte token)
// ---------------------------------------------------------------------------------//
{
int ptr = startPtr + 1;
int max = startPtr + length;
while (ptr < max)
{
if (parent.buffer[ptr++] == token)
return true;
}
return false;
}
// ---------------------------------------------------------------------------------//
boolean isEmpty ()
// ---------------------------------------------------------------------------------//
{
return length == 1 && buffer[startPtr] == 0;
}
// ---------------------------------------------------------------------------------//
boolean containsToken ()
// ---------------------------------------------------------------------------------//
{
// ignore first byte, check the rest for tokens
for (int p = startPtr + 1, max = startPtr + length; p < max; p++)
if (Utility.isHighBitSet (buffer[p]))
return true;
return false;
}
// ---------------------------------------------------------------------------------//
boolean containsControlChars ()
// ---------------------------------------------------------------------------------//
{
for (int p = startPtr + 1, max = startPtr + length; p < max; p++)
{
int c = buffer[p] & 0xFF;
if (c == 0)
break;
if (c < 32)
return true;
}
return false;
}
// ---------------------------------------------------------------------------------//
void addFormattedRem (StringBuilder text)
// ---------------------------------------------------------------------------------//
{
int ptr = startPtr + 1;
int max = startPtr + length - 2;
while (ptr <= max)
{
int c = buffer[ptr] & 0xFF;
// System.out.printf ("%02X %s%n", c, (char) c);
if (c == 0x08 && text.length () > 0)
text.deleteCharAt (text.length () - 1);
else if (c == 0x0D)
text.append ("\n");
else
text.append ((char) c);
ptr++;
}
}
// ---------------------------------------------------------------------------------//
public int getAddress ()
// ---------------------------------------------------------------------------------//
{
return program.getLoadAddress () + startPtr;
}
// ---------------------------------------------------------------------------------//
public String getAlignedText (int alignPosition)
// ---------------------------------------------------------------------------------//
{
StringBuilder line = toStringBuilder ();
while (alignPosition-- > assignEqualPos)
line.insert (assignEqualPos, ' ');
return line.toString ();
}
// A REM statement might conceal an assembler routine
// ---------------------------------------------------------------------------------//
public String[] getAssembler ()
// ---------------------------------------------------------------------------------//
{
byte[] buffer2 = new byte[length - 1];
System.arraycopy (buffer, startPtr + 1, buffer2, 0, buffer2.length);
AssemblerProgram program =
new AssemblerProgram ("REM assembler", buffer2, getAddress () + 1);
return program.getAssembler ().split ("\n");
}
// ---------------------------------------------------------------------------------//
@Override
public String toString ()
// ---------------------------------------------------------------------------------//
{
return toStringBuilder ().toString ();
}
// ---------------------------------------------------------------------------------//
public StringBuilder toStringBuilder ()
// ---------------------------------------------------------------------------------//
{
StringBuilder line = new StringBuilder ();
// All sublines end with 0 or : except IF lines that are split into two
int max = startPtr + length - 1;
if (buffer[max] == 0)
--max;
if (isImpliedGoto () && !ApplesoftBasicProgram.basicPreferences.showThen)
line.append ("GOTO ");
for (int p = startPtr; p <= max; p++)
{
byte b = buffer[p];
if (Utility.isHighBitSet (b))
{
if (line.length () > 0 && line.charAt (line.length () - 1) != ' ')
line.append (' ');
int val = b & 0x7F;
if (val < ApplesoftConstants.tokens.length)
{
if (b != ApplesoftConstants.TOKEN_THEN
|| ApplesoftBasicProgram.basicPreferences.showThen)
line.append (ApplesoftConstants.tokens[val]);
}
}
else if (Utility.isControlCharacter (b))
line.append (ApplesoftBasicProgram.basicPreferences.showCaret
? "^" + (char) (b + 64) : "");
else
line.append ((char) b);
}
return line;
}
}

View File

@ -17,6 +17,14 @@ import java.util.zip.Checksum;
public class Utility public class Utility
// -----------------------------------------------------------------------------------// // -----------------------------------------------------------------------------------//
{ {
public static final byte ASCII_QUOTE = 0x22;
public static final byte ASCII_DOLLAR = 0x24;
public static final byte ASCII_PERCENT = 0x25;
public static final byte ASCII_LEFT_BRACKET = 0x28;
public static final byte ASCII_COLON = 0x3A;
public static final byte ASCII_SEMI_COLON = 0x3B;
public static final byte ASCII_CARET = 0x5E;
public static final List<String> suffixes = Arrays.asList ("po", "dsk", "do", "hdv", public static final List<String> suffixes = Arrays.asList ("po", "dsk", "do", "hdv",
"2mg", "v2d", "d13", "sdk", "woz", "img", "dimg"); "2mg", "v2d", "d13", "sdk", "woz", "img", "dimg");
@ -258,6 +266,43 @@ public class Utility
} }
} }
// ---------------------------------------------------------------------------------//
public static boolean isHighBitSet (byte value)
// ---------------------------------------------------------------------------------//
{
return (value & 0x80) != 0;
}
// ---------------------------------------------------------------------------------//
public static boolean isControlCharacter (byte value)
// ---------------------------------------------------------------------------------//
{
int val = value & 0xFF;
return val > 0 && val < 32;
}
// ---------------------------------------------------------------------------------//
public static boolean isDigit (byte value)
// ---------------------------------------------------------------------------------//
{
return value >= 0x30 && value <= 0x39;
}
// ---------------------------------------------------------------------------------//
public static boolean isLetter (byte value)
// ---------------------------------------------------------------------------------//
{
return value >= 0x41 && value <= 0x5A;
}
// ---------------------------------------------------------------------------------//
public static boolean isPossibleVariable (byte value)
// ---------------------------------------------------------------------------------//
{
return isDigit (value) || isLetter (value) || value == ASCII_DOLLAR
|| value == ASCII_PERCENT;
}
// ---------------------------------------------------------------------------------// // ---------------------------------------------------------------------------------//
public static long getChecksumValue (File file) public static long getChecksumValue (File file)
// ---------------------------------------------------------------------------------// // ---------------------------------------------------------------------------------//