This commit is contained in:
Denis Molony 2019-08-10 09:45:49 +10:00
parent 9e94bdfe50
commit c7a16c8033
13 changed files with 1588 additions and 786 deletions

View File

@ -0,0 +1,758 @@
package com.bytezone.diskbrowser.applefile;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Stack;
import com.bytezone.diskbrowser.utilities.HexFormatter;
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_INPUT = (byte) 0x84;
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_REM = (byte) 0xB2;
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 int endPtr;
private final Set<Integer> gotoLines = new HashSet<> ();
private final Set<Integer> gosubLines = new HashSet<> ();
public ApplesoftBasicProgram (String name, byte[] buffer)
{
super (name, buffer);
int ptr = 0;
int prevOffset = 0;
int max = buffer.length - 4; // need at least 4 bytes to make a SourceLine
while (ptr < max)
{
int offset = HexFormatter.unsignedShort (buffer, ptr);
if (offset <= prevOffset)
break;
SourceLine line = new SourceLine (ptr);
sourceLines.add (line);
ptr += line.length;
prevOffset = offset;
}
endPtr = ptr;
}
@Override
public String getText ()
{
StringBuilder fullText = new StringBuilder ();
Stack<String> loopVariables = new Stack<String> ();
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
if (subline.is (TOKEN_REM) && subline.containsToken ())
{
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;
}
// Reduce the indent by each NEXT, but only as far as the IF indent allows
if (subline.is (TOKEN_NEXT))
{
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 ())
if (subline.is (TOKEN_GOSUB))
text.append ("<<--");
else if (subline.is (TOKEN_GOTO) || subline.isImpliedGoto ())
text.append (" <--");
// Align assign statements if required
if (basicPreferences.alignAssign)
alignPos = alignEqualsPosition (subline, alignPos);
int column = indent * 2 + baseOffset;
while (text.length () < column)
text.append (" ");
}
// Add the current text, then reset it
int pos = subline.is (TOKEN_REM) ? 0 : alignPos;
String lineText = subline.getAlignedText (pos);
// Check for a wrappable REM statement
// (see SEA BATTLE on DISK283.DSK)
if (subline.is (TOKEN_REM) && lineText.length () > basicPreferences.wrapRemAt + 4)
{
// System.out.println (lineText.length ());
String copy = lineText.substring (4);
text.append ("REM ");
int inset = text.length () + 1;
List<String> remarks = splitRemark (copy, basicPreferences.wrapRemAt);
boolean first = true;
for (String remark : remarks)
{
if (first)
{
first = false;
text.append (remark);
}
else
text.append ("\n ".substring (0, inset) + remark);
}
}
else
text.append (lineText);
// Check for a wrappable PRINT statement
// (see FROM MACHINE LANGUAGE TO BASIC on DOSToolkit2eB.dsk)
if (basicPreferences.wrapPrintAt > 0 //
&& (subline.is (TOKEN_PRINT) || subline.is (TOKEN_INPUT))
&& countChars (text, ASCII_QUOTE) == 2 // just start and end quotes
&& countChars (text, ASCII_CARET) == 0) // no control characters
// && 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
if (subline.is (TOKEN_IF))
ifIndent = ++indent;
else if (subline.is (TOKEN_FOR))
{
loopVariables.push (subline.forVariable);
++indent;
}
}
// Reset alignment value if we just left an IF - the indentation will be different now.
if (ifIndent > 0)
alignPos = 0;
}
fullText.deleteCharAt (fullText.length () - 1); // remove last newline
return fullText.toString ();
}
private List<String> splitPrint (String line)
{
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;
}
private List<String> splitRemark (String remark, int wrapLength)
{
List<String> remarks = new ArrayList<> ();
while (remark.length () > wrapLength)
{
int max = Math.min (wrapLength, remark.length () - 1);
while (max > 0 && remark.charAt (max) != ' ')
--max;
if (max == 0)
break;
remarks.add (remark.substring (0, max));
remark = remark.substring (max);
}
remarks.add (remark);
return remarks;
}
private int countChars (StringBuilder text, byte ch)
{
int total = 0;
for (int i = 0; i < text.length (); i++)
if (text.charAt (i) == ch)
total++;
return total;
}
private String getBase (SourceLine line)
{
if (!basicPreferences.showTargets)
return String.format (" %5d", line.lineNumber);
String lineNumberText = String.format ("%5d", line.lineNumber);
SubLine subline = line.sublines.get (0);
String c1 = " ", c2 = " ";
if (subline.is (TOKEN_GOSUB))
c1 = "<<";
if (subline.is (TOKEN_GOTO))
c1 = " <";
if (gotoLines.contains (line.lineNumber))
c2 = "> ";
if (gosubLines.contains (line.lineNumber))
c2 = ">>";
if (c1.equals (" ") && !c2.equals (" "))
c1 = "--";
if (!c1.equals (" ") && c2.equals (" "))
c2 = "--";
if (basicPreferences.onlyShowTargetLineNumbers && !c2.startsWith (">"))
lineNumberText = "";
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.
private int alignEqualsPosition (SubLine subline, int currentAlignPosition)
{
if (subline.assignEqualPos > 0) // does the line have an equals sign?
{
if (currentAlignPosition == 0)
currentAlignPosition = findHighest (subline); // examine following sublines for alignment
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).
private int findHighest (SubLine startSubline)
{
boolean started = false;
int highestAssign = startSubline.assignEqualPos;
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;
else if (subline.is (TOKEN_IF))
inIf = true;
}
if (started && inIf)
break;
}
return highestAssign;
}
@Override
public String getHexDump ()
{
if (buffer.length < 2)
return super.getHexDump ();
StringBuilder pgm = new StringBuilder ();
if (basicPreferences.showHeader)
addHeader (pgm);
int ptr = 0;
int offset = HexFormatter.unsignedShort (buffer, 0);
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 ();
}
private void addHeader (StringBuilder pgm)
{
pgm.append ("Name : " + name + "\n");
pgm.append (String.format ("Length : $%04X (%<,d)%n", buffer.length));
pgm.append (String.format ("Load at : $%04X (%<,d)%n%n", getLoadAddress ()));
}
private int getLoadAddress ()
{
int programLoadAddress = 0;
if (buffer.length > 1)
{
int offset = HexFormatter.intValue (buffer[0], buffer[1]);
programLoadAddress = offset - getLineLength (0);
}
return programLoadAddress;
}
private int getLineLength (int ptr)
{
int offset = HexFormatter.unsignedShort (buffer, ptr);
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;
}
private void popLoopVariables (Stack<String> loopVariables, SubLine subline)
{
if (subline.nextVariables.length == 0) // naked NEXT
{
if (loopVariables.size () > 0)
loopVariables.pop ();
}
else
for (String variable : subline.nextVariables)
// e.g. NEXT X,Y,Z
while (loopVariables.size () > 0)
if (sameVariable (variable, loopVariables.pop ()))
break;
}
private boolean sameVariable (String v1, String v2)
{
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;
}
private class SourceLine
{
List<SubLine> sublines = new ArrayList<SubLine> ();
int lineNumber;
int linePtr;
int length;
public SourceLine (int ptr)
{
linePtr = ptr;
lineNumber = HexFormatter.intValue (buffer[ptr + 2], buffer[ptr + 3]);
int startPtr = ptr += 4;
boolean inString = false; // can toggle
boolean inRemark = false; // can only go false -> true
byte b;
while ((b = buffer[ptr++]) != 0)
{
if (inRemark) // cannot terminate a REM
continue;
if (inString)
{
if (b == ASCII_QUOTE) // terminate string
inString = false;
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;
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 = "";
int targetLine = -1;
// used for aligning the equals sign
int assignEqualPos;
public SubLine (SourceLine parent, int startPtr, int length)
{
this.parent = parent;
this.startPtr = startPtr;
this.length = length;
byte b = buffer[startPtr];
if (isToken (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:
String target = new String (buffer, startPtr + 1, length - 2);
try
{
gotoLines.add (Integer.parseInt (target));
}
catch (NumberFormatException e)
{
System.out.println (
"Error parsing : GOTO " + target + " in " + parent.lineNumber);
}
break;
case TOKEN_GOSUB:
String target2 = new String (buffer, startPtr + 1, length - 2);
try
{
gosubLines.add (Integer.parseInt (target2));
}
catch (NumberFormatException e)
{
System.out.println (HexFormatter.format (buffer, startPtr + 1, length - 2));
System.out.println (
"Error parsing : GOSUB " + target2 + " in " + parent.lineNumber);
}
break;
}
}
else
{
if (isDigit (b)) // numeric, so must be a line number
{
String target = new String (buffer, startPtr, length - 1);
try
{
targetLine = Integer.parseInt (target);
gotoLines.add (targetLine);
}
catch (NumberFormatException e)
{
System.out.printf ("b: %d, start: %d, length: %d%n", b, startPtr,
(length - 1));
System.out.println (target);
System.out.println (HexFormatter.format (buffer, startPtr, length - 1));
System.out.println (e);
// assert false;
}
}
// else if (basicPreferences.alignAssign)
else
recordEqualsPosition ();
}
}
private boolean isImpliedGoto ()
{
byte b = buffer[startPtr];
if (isToken (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 ();
}
public boolean isFirst ()
{
return (parent.linePtr + 4) == startPtr;
}
public boolean is (byte token)
{
return buffer[startPtr] == token;
}
public boolean isEmpty ()
{
return length == 1 && buffer[startPtr] == 0;
}
public boolean containsToken ()
{
// ignore first byte, check the rest for tokens
for (int p = startPtr + 1, max = startPtr + length; p < max; p++)
if (isToken (buffer[p]))
return true;
return false;
}
// private boolean isToken (byte value)
// {
// return (value & 0x80) > 0;
// }
// private boolean isControlCharacter (byte value)
// {
// return value < 32;
// }
// private boolean isDigit (byte value)
// {
// return value >= 48 && value <= 57;
// }
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;
for (int p = startPtr; p <= max; p++)
{
byte b = buffer[p];
if (isToken (b))
{
if (line.length () > 0 && line.charAt (line.length () - 1) != ' ')
line.append (' ');
int val = b & 0x7F;
if (val < ApplesoftConstants.tokens.length)
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

@ -42,7 +42,7 @@ public interface ApplesoftConstants
0xF769, 0xF76F, 0xF7E7, 0xFC58, 0xF721, 0xF727, 0xF775, 0xF26D, 0xF26F, 0xF273,
0xF277, 0xF280, 0xF24F, 0xD96B, 0xF256, 0xF286, 0xF2A6, 0xF2CB, 0xF318, 0xF3BC,
0xF39F, 0xF262, 0xDA46, 0xD93E, 0xD912, 0xD9C9, 0xD849, 0x03F5, 0xD921, 0xD96B,
0xD9DC, 0xD86E, 0xD9EC, 0xE784, 0xD8C9, 0xD8B0, 0xE313, 0xE77B, 0xFDAD5, 0xD896,
0xD9DC, 0xD86E, 0xD9EC, 0xE784, 0xD8C9, 0xD8B0, 0xE313, 0xE77B, 0xDAD5, 0xD896,
0xD6A5, 0xD66A, 0xDBA0, 0xD649, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xEB90, 0xEC23, 0xEBAF, 0x000A, 0xE2DE,
0xD412, 0xDFCD, 0xE2FF, 0xEE8D, 0xEFAE, 0xE941, 0xEF09, 0xEFEA, 0xEFF1, 0xF03A,

View File

@ -8,6 +8,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import com.bytezone.diskbrowser.gui.DiskBrowser;
import com.bytezone.diskbrowser.utilities.HexFormatter;
@ -95,7 +96,9 @@ public class AssemblerProgram extends AbstractFile
pgm.append (String.format ("Entry : $%04X%n", (loadAddress + executeOffset)));
pgm.append (String.format ("%n"));
return pgm.append (getStringBuilder2 ()).toString ();
String stringText = getStringsText ();
return pgm.append (getStringBuilder2 ()).toString () + stringText;
}
// private StringBuilder getStringBuilder ()
@ -267,6 +270,44 @@ public class AssemblerProgram extends AbstractFile
return lines;
}
private Map<Integer, String> getStrings ()
{
TreeMap<Integer, String> strings = new TreeMap<> ();
int start = 0;
for (int ptr = 0; ptr < buffer.length; ptr++)
{
if ((buffer[ptr] & 0x80) != 0) // high bit set
continue;
if (buffer[ptr] == 0 && ptr - start > 5) // possible end of string
strings.put (start, HexFormatter.getString (buffer, start, ptr - start));
start = ptr + 1;
}
return strings;
}
private String getStringsText ()
{
Map<Integer, String> strings = getStrings ();
if (strings.size () == 0)
return "";
StringBuilder text = new StringBuilder ("\n\nPossible strings:\n\n");
for (Integer key : strings.keySet ())
{
String s = strings.get (key);
int start = key + loadAddress;
text.append (String.format ("%04X - %04X %s %n", start, start + s.length (), s));
}
if (text.length () > 0)
text.deleteCharAt (text.length () - 1);
return text.toString ();
}
private String getArrow (AssemblerStatement cmd)
{
String arrow = "";

View File

@ -1,771 +1,38 @@
package com.bytezone.diskbrowser.applefile;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Stack;
import com.bytezone.diskbrowser.gui.BasicPreferences;
import com.bytezone.diskbrowser.utilities.HexFormatter;
public class BasicProgram extends AbstractFile
{
private static final byte ASCII_QUOTE = 0x22;
private static final byte ASCII_COLON = 0x3A;
// private static final byte ASCII_SEMI_COLON = 0x3B;
private static final byte ASCII_CARET = 0x5E;
private static final byte TOKEN_FOR = (byte) 0x81;
private static final byte TOKEN_NEXT = (byte) 0x82;
private static final byte TOKEN_INPUT = (byte) 0x84;
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_REM = (byte) 0xB2;
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<SourceLine> ();
private final int endPtr;
private final Set<Integer> gotoLines = new HashSet<Integer> ();
private final Set<Integer> gosubLines = new HashSet<Integer> ();
private static BasicPreferences basicPreferences;
public BasicProgram (String name, byte[] buffer)
{
super (name, buffer);
int ptr = 0;
int prevOffset = 0;
int max = buffer.length - 4; // need at least 4 bytes to make a SourceLine
while (ptr < max)
{
int offset = HexFormatter.unsignedShort (buffer, ptr);
if (offset <= prevOffset)
break;
SourceLine line = new SourceLine (ptr);
sourceLines.add (line);
ptr += line.length;
prevOffset = offset;
}
endPtr = ptr;
}
public static void setBasicPreferences (BasicPreferences basicPreferences)
{
BasicProgram.basicPreferences = basicPreferences;
}
@Override
public String getText ()
{
StringBuilder fullText = new StringBuilder ();
Stack<String> loopVariables = new Stack<String> ();
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
if (subline.is (TOKEN_REM) && subline.containsToken ())
{
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;
}
// Reduce the indent by each NEXT, but only as far as the IF indent allows
if (subline.is (TOKEN_NEXT))
{
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 ())
if (subline.is (TOKEN_GOSUB))
text.append ("<<--");
else if (subline.is (TOKEN_GOTO) || subline.isImpliedGoto ())
text.append (" <--");
// Align assign statements if required
if (basicPreferences.alignAssign)
alignPos = alignEqualsPosition (subline, alignPos);
int column = indent * 2 + baseOffset;
while (text.length () < column)
text.append (" ");
}
// Add the current text, then reset it
int pos = subline.is (TOKEN_REM) ? 0 : alignPos;
String lineText = subline.getAlignedText (pos);
// Check for a wrappable REM statement
// (see SEA BATTLE on DISK283.DSK)
if (subline.is (TOKEN_REM) && lineText.length () > basicPreferences.wrapRemAt + 4)
{
// System.out.println (lineText.length ());
String copy = lineText.substring (4);
text.append ("REM ");
int inset = text.length () + 1;
List<String> remarks = splitRemark (copy, basicPreferences.wrapRemAt);
boolean first = true;
for (String remark : remarks)
{
if (first)
{
first = false;
text.append (remark);
}
else
text.append ("\n ".substring (0, inset) + remark);
}
}
else
text.append (lineText);
// Check for a wrappable PRINT statement
// (see FROM MACHINE LANGUAGE TO BASIC on DOSToolkit2eB.dsk)
if (basicPreferences.wrapPrintAt > 0 //
&& (subline.is (TOKEN_PRINT) || subline.is (TOKEN_INPUT))
&& countChars (text, ASCII_QUOTE) == 2 // just start and end quotes
&& countChars (text, ASCII_CARET) == 0) // no control characters
// && 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
if (subline.is (TOKEN_IF))
ifIndent = ++indent;
else if (subline.is (TOKEN_FOR))
{
loopVariables.push (subline.forVariable);
++indent;
}
}
// Reset alignment value if we just left an IF - the indentation will be different now.
if (ifIndent > 0)
alignPos = 0;
}
fullText.deleteCharAt (fullText.length () - 1); // remove last newline
return fullText.toString ();
}
private List<String> splitPrint (String line)
{
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> ();
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;
}
private List<String> splitRemark (String remark, int wrapLength)
{
List<String> remarks = new ArrayList<String> ();
while (remark.length () > wrapLength)
{
int max = Math.min (wrapLength, remark.length () - 1);
while (max > 0 && remark.charAt (max) != ' ')
--max;
if (max == 0)
break;
remarks.add (remark.substring (0, max));
remark = remark.substring (max);
}
remarks.add (remark);
return remarks;
}
private int countChars (StringBuilder text, byte ch)
{
int total = 0;
for (int i = 0; i < text.length (); i++)
if (text.charAt (i) == ch)
total++;
return total;
}
private String getBase (SourceLine line)
{
if (!basicPreferences.showTargets)
return String.format (" %5d", line.lineNumber);
String lineNumberText = String.format ("%5d", line.lineNumber);
SubLine subline = line.sublines.get (0);
String c1 = " ", c2 = " ";
if (subline.is (TOKEN_GOSUB))
c1 = "<<";
if (subline.is (TOKEN_GOTO))
c1 = " <";
if (gotoLines.contains (line.lineNumber))
c2 = "> ";
if (gosubLines.contains (line.lineNumber))
c2 = ">>";
if (c1.equals (" ") && !c2.equals (" "))
c1 = "--";
if (!c1.equals (" ") && c2.equals (" "))
c2 = "--";
if (basicPreferences.onlyShowTargetLineNumbers && !c2.startsWith (">"))
lineNumberText = "";
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.
private int alignEqualsPosition (SubLine subline, int currentAlignPosition)
{
if (subline.assignEqualPos > 0) // does the line have an equals sign?
{
if (currentAlignPosition == 0)
currentAlignPosition = findHighest (subline); // examine following sublines for alignment
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).
private int findHighest (SubLine startSubline)
{
boolean started = false;
int highestAssign = startSubline.assignEqualPos;
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;
else if (subline.is (TOKEN_IF))
inIf = true;
}
if (started && inIf)
break;
}
return highestAssign;
}
@Override
public String getHexDump ()
{
if (buffer.length < 2)
return super.getHexDump ();
StringBuilder pgm = new StringBuilder ();
if (basicPreferences.showHeader)
addHeader (pgm);
int ptr = 0;
int offset = HexFormatter.unsignedShort (buffer, 0);
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 ();
}
private void addHeader (StringBuilder pgm)
{
pgm.append ("Name : " + name + "\n");
pgm.append (String.format ("Length : $%04X (%<,d)%n", buffer.length));
pgm.append (String.format ("Load at : $%04X (%<,d)%n%n", getLoadAddress ()));
}
private int getLoadAddress ()
{
int programLoadAddress = 0;
if (buffer.length > 1)
{
int offset = HexFormatter.intValue (buffer[0], buffer[1]);
programLoadAddress = offset - getLineLength (0);
}
return programLoadAddress;
}
private int getLineLength (int ptr)
{
int offset = HexFormatter.unsignedShort (buffer, ptr);
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;
}
private void popLoopVariables (Stack<String> loopVariables, SubLine subline)
{
if (subline.nextVariables.length == 0) // naked NEXT
{
if (loopVariables.size () > 0)
loopVariables.pop ();
}
else
for (String variable : subline.nextVariables)
// e.g. NEXT X,Y,Z
while (loopVariables.size () > 0)
if (sameVariable (variable, loopVariables.pop ()))
break;
}
private boolean sameVariable (String v1, String v2)
{
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;
}
private class SourceLine
{
List<SubLine> sublines = new ArrayList<SubLine> ();
int lineNumber;
int linePtr;
int length;
public SourceLine (int ptr)
{
linePtr = ptr;
lineNumber = HexFormatter.intValue (buffer[ptr + 2], buffer[ptr + 3]);
int startPtr = ptr += 4;
boolean inString = false; // can toggle
boolean inRemark = false; // can only go false -> true
byte b;
while ((b = buffer[ptr++]) != 0)
{
if (inRemark) // cannot terminate a REM
continue;
if (inString)
{
if (b == ASCII_QUOTE) // terminate string
inString = false;
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;
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 = "";
int targetLine = -1;
// used for aligning the equals sign
int assignEqualPos;
public SubLine (SourceLine parent, int startPtr, int length)
{
this.parent = parent;
this.startPtr = startPtr;
this.length = length;
byte b = buffer[startPtr];
if (isToken (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:
String target = new String (buffer, startPtr + 1, length - 2);
try
{
gotoLines.add (Integer.parseInt (target));
}
catch (NumberFormatException e)
{
System.out.println (
"Error parsing : GOTO " + target + " in " + parent.lineNumber);
}
break;
case TOKEN_GOSUB:
String target2 = new String (buffer, startPtr + 1, length - 2);
try
{
gosubLines.add (Integer.parseInt (target2));
}
catch (NumberFormatException e)
{
System.out.println (HexFormatter.format (buffer, startPtr + 1, length - 2));
System.out.println (
"Error parsing : GOSUB " + target2 + " in " + parent.lineNumber);
}
break;
}
}
else
{
if (isDigit (b)) // numeric, so must be a line number
{
String target = new String (buffer, startPtr, length - 1);
try
{
targetLine = Integer.parseInt (target);
gotoLines.add (targetLine);
}
catch (NumberFormatException e)
{
System.out.printf ("b: %d, start: %d, length: %d%n", b, startPtr,
(length - 1));
System.out.println (target);
System.out.println (HexFormatter.format (buffer, startPtr, length - 1));
System.out.println (e);
// assert false;
}
}
// else if (basicPreferences.alignAssign)
else
recordEqualsPosition ();
}
}
private boolean isImpliedGoto ()
{
byte b = buffer[startPtr];
if (isToken (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 ();
}
public boolean isFirst ()
{
return (parent.linePtr + 4) == startPtr;
}
public boolean is (byte token)
{
return buffer[startPtr] == token;
}
public boolean isEmpty ()
{
return length == 1 && buffer[startPtr] == 0;
}
public boolean containsToken ()
{
// ignore first byte, check the rest for tokens
for (int p = startPtr + 1, max = startPtr + length; p < max; p++)
if (isToken (buffer[p]))
return true;
return false;
}
private boolean isToken (byte value)
{
return (value & 0x80) > 0;
}
private boolean isControlCharacter (byte value)
{
return value < 32;
}
private boolean isDigit (byte value)
{
return value >= 48 && value <= 57;
}
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;
for (int p = startPtr; p <= max; p++)
{
byte b = buffer[p];
if (isToken (b))
{
if (line.length () > 0 && line.charAt (line.length () - 1) != ' ')
line.append (' ');
int val = b & 0x7F;
if (val < ApplesoftConstants.tokens.length)
line.append (ApplesoftConstants.tokens[b & 0x7F]);
}
else if (isControlCharacter (b))
line.append (basicPreferences.showCaret ? "^" + (char) (b + 64) : "");
else
line.append ((char) b);
}
return line;
}
}
}
package com.bytezone.diskbrowser.applefile;
import com.bytezone.diskbrowser.gui.BasicPreferences;
public abstract class BasicProgram extends AbstractFile
{
static final byte ASCII_QUOTE = 0x22;
static final byte ASCII_COLON = 0x3A;
static final byte ASCII_SEMI_COLON = 0x3B;
static final byte ASCII_CARET = 0x5E;
static BasicPreferences basicPreferences;
public static void setBasicPreferences (BasicPreferences basicPreferences)
{
ApplesoftBasicProgram.basicPreferences = basicPreferences;
}
public BasicProgram (String name, byte[] buffer)
{
super (name, buffer);
}
boolean isToken (byte value)
{
return (value & 0x80) != 0;
}
boolean isControlCharacter (byte value)
{
return (value & 0xFF) < 32;
}
boolean isDigit (byte value)
{
return value >= 48 && value <= 57;
}
}

View File

@ -0,0 +1,714 @@
package com.bytezone.diskbrowser.applefile;
import java.util.ArrayList;
import java.util.List;
import com.bytezone.diskbrowser.utilities.HexFormatter;
public class BasicProgramGS extends BasicProgram
{
private final List<SourceLine> sourceLines = new ArrayList<> ();
public BasicProgramGS (String name, byte[] buffer)
{
super (name, buffer);
int ptr = 5;
while (ptr < buffer.length)
{
SourceLine sourceLine = new SourceLine (ptr);
if (sourceLine.lineNumber == 0)
break;
sourceLines.add (sourceLine);
ptr += sourceLine.length;
}
}
@Override
public String getText ()
{
StringBuilder text = new StringBuilder ();
for (SourceLine sourceLine : sourceLines)
text.append (sourceLine + "\n");
return text.toString ();
}
private class SourceLine
{
int lineNumber;
int length;
String label;
String line;
public SourceLine (int ptr)
{
int labelLength = buffer[ptr] & 0xFF;
if (labelLength > 1)
label = new String (buffer, ptr + 1, labelLength - 1);
else
label = "";
ptr += labelLength;
int lineLength = buffer[ptr] & 0xFF;
lineNumber = HexFormatter.intValue (buffer[ptr + 1], buffer[ptr + 2]);
length = labelLength + lineLength;
if (lineNumber == 0)
return;
ptr += 3;
int max = ptr + lineLength - 4;
StringBuilder text = new StringBuilder (String.format ("%-12s :", label));
while (ptr < max)
{
byte b = buffer[ptr++];
if (isToken (b))
{
switch (b & 0xFF)
{
case 0x80:
text.append (" AUTO ");
break;
case 0x81:
text.append (" DEL ");
break;
case 0x82:
text.append (" EDIT ");
break;
case 0x83:
text.append (" HLIST ");
break;
case 0x84:
text.append (" LIST ");
break;
case 0x85:
text.append (" RENUM ");
break;
case 0x86:
text.append (" BREAK ");
break;
case 0x87:
text.append (" FN ");
break;
case 0x88:
text.append (" PROC ");
break;
case 0x89:
text.append (" GOSUB ");
break;
case 0x8A:
text.append (" GOTO ");
break;
case 0x8B:
text.append (" FOR ");
break;
case 0x8C:
text.append (" THEN ");
break;
case 0x8D:
text.append (" ELSE ");
break;
case 0x8E:
text.append (" NEXT ");
break;
case 0x8F:
text.append (" OFF ");
break;
case 0x90:
text.append (" ON ");
break;
case 0x91:
text.append (" INPUT ");
break;
case 0x92:
text.append (" OUTPUT ");
break;
case 0x93:
text.append (" TEXT ");
break;
case 0x94:
text.append (" TIMER ");
break;
case 0x95:
text.append (" EXCEPTION ");
break;
case 0x96:
text.append (" CAT ");
break;
case 0x98:
text.append (" INIT ");
break;
case 0x99:
text.append (" INVOKE ");
break;
case 0x9A:
text.append (" LIBRARY ");
break;
case 0x9B:
text.append (" PREFIX ");
break;
case 0x9C:
text.append (" TYPE ");
break;
case 0x9D:
text.append (" LOAD ");
break;
case 0x9E:
text.append (" SAVE ");
break;
case 0x9F:
text.append (" DELETE ");
break;
case 0xA0:
text.append (" RUN ");
break;
case 0xA1:
text.append (" RENAME ");
break;
case 0xA2:
text.append (" CREATE ");
break;
case 0xA3:
text.append (" LOCK ");
break;
case 0xA4:
text.append (" UNLOCK ");
break;
case 0xA5:
text.append (" EXEC ");
break;
case 0xA6:
text.append (" CHAIN ");
break;
case 0xA7:
text.append (" CATALOG ");
break;
case 0xA8:
text.append (" OPEN ");
break;
case 0xA9:
text.append (" QUIT ");
break;
case 0xAA:
text.append (" DIR ");
break;
case 0xAB:
text.append (" DIM ");
break;
case 0xAC:
text.append (" READ ");
break;
case 0xAD:
text.append (" WRITE ");
break;
case 0xAE:
text.append (" CLOSE ");
break;
case 0xAF:
text.append (" TASKPOLL ");
break;
case 0xB0:
text.append (" LOCATE ");
break;
case 0xB1:
text.append (" EVENTDEF ");
break;
case 0xB2:
text.append (" MENUDEF ");
break;
case 0xB3:
text.append (" VOLUMES ");
break;
case 0xB4:
text.append (" CALL% ");
break;
case 0xB5:
text.append (" CALL ");
break;
case 0xB6:
text.append (" _ ");
break;
case 0xB7:
text.append (" TEXTPORT ");
break;
case 0xB8:
text.append (" PERFORM ");
break;
case 0xB9:
text.append (" GRAF ");
break;
case 0xBA:
text.append (" DBUG ");
break;
case 0xBB:
text.append (" POP ");
break;
case 0xBC:
text.append (" HOME ");
break;
case 0xBD:
text.append (" SUB$( ");
break;
case 0xBE:
text.append (" TRACE ");
break;
case 0xBF:
text.append (" NOTRACE ");
break;
case 0xC0:
text.append (" NORMAL ");
break;
case 0xC1:
text.append (" INVERSE ");
break;
case 0xC2:
text.append (" RESUME ");
break;
case 0xC3:
text.append (" LET ");
break;
case 0xC4:
text.append (" IF ");
break;
case 0xC5:
text.append (" RESTORE ");
break;
case 0xC6:
text.append (" SWAP ");
break;
case 0xC7:
text.append (" RETURN ");
break;
case 0xC8:
text.append (" REM ");
break;
case 0xC9:
text.append (" STOP ");
break;
case 0xCA:
text.append (" DATA ");
break;
case 0xCB:
text.append (" IMAGE ");
break;
case 0xCC:
text.append (" LIBFIND ");
break;
case 0xCD: // gimme some skin
text.append (" DEF ");
break;
case 0xCE:
text.append (" PRINT ");
break;
case 0xCF:
text.append (" CLEAR ");
break;
case 0xD0:
text.append (" RANDOMIZE ");
break;
case 0xD1:
text.append (" NEW ");
break;
case 0xD2:
text.append (" POKE ");
break;
case 0xD3:
text.append (" ASSIGN ");
break;
case 0xD4:
text.append (" GET ");
break;
case 0xD5:
text.append (" PUT ");
break;
case 0xD6:
text.append (" SET ");
break;
case 0xD7:
text.append (" ERROR ");
break;
case 0xD8:
text.append (" ERASE ");
break;
case 0xD9:
text.append (" LOCAL ");
break;
case 0xDA:
text.append (" WHILE ");
break;
case 0xDB:
text.append (" CONT ");
break;
case 0xDC:
text.append (" DO ");
break;
case 0xDD:
text.append (" UNTIL ");
break;
case 0xDE:
text.append (" END ");
break;
case 0xDF:
switch (buffer[ptr] & 0xFF)
{
case 0x80:
text.append (" TAB( ");
break;
case 0x81:
text.append (" TO ");
break;
case 0x82:
text.append (" SPC( ");
break;
case 0x83:
text.append (" USING ");
break;
case 0x84:
text.append (" APPEND ");
break;
case 0x85:
text.append (" MOD ");
break;
case 0x86:
text.append (" REMDR ");
break;
case 0x87:
text.append (" STEP ");
break;
case 0x88:
text.append (" AND ");
break;
case 0x89:
text.append (" OR ");
break;
case 0x8A:
text.append (" XOR ");
break;
case 0x8B:
text.append (" DIV ");
break;
case 0x8C:
text.append (" SRC ");
break;
case 0x8D:
text.append (" NOT ");
break;
case 0x8F:
text.append (" UPDATE ");
break;
case 0x90:
text.append (" TXT ");
break;
case 0x91:
text.append (" BDF ");
break;
case 0x92:
text.append (" FILTYPE= ");
break;
case 0x93:
text.append (" AS ");
break;
case 0x96:
text.append (" SGN( ");
break;
case 0x97:
text.append (" INT ");
break;
case 0x98:
text.append (" ABS( ");
break;
case 0x99:
text.append (" TYP( ");
break;
case 0x9A:
text.append (" REC( ");
break;
case 0x9B:
text.append (" JOYX( ");
break;
case 0x9C:
text.append (" PDL( ");
break;
case 0x9D:
text.append (" BTN( ");
break;
case 0x9E:
text.append (" R.STACK%( ");
break;
case 0x9F:
text.append (" R.STACK@( ");
break;
case 0xA0:
text.append (" R.STACK&( ");
break;
case 0xA1:
text.append (" SQR( ");
break;
case 0xA2:
text.append (" RND( ");
break;
case 0xA3:
text.append (" LOG( ");
break;
case 0xA4:
text.append (" LOG1( ");
break;
case 0xA5:
text.append (" LOG2( ");
break;
case 0xA6:
text.append (" LOGB%( ");
break;
case 0xA7:
text.append (" EXP( ");
break;
case 0xA8:
text.append (" EXP1( ");
break;
case 0xA9:
text.append (" EXP2( ");
break;
case 0xAA:
text.append (" COS( ");
break;
case 0xAB:
text.append (" SIN( ");
break;
case 0xAC:
text.append (" TAN( ");
break;
case 0xAD:
text.append (" ATN( ");
break;
case 0xAE:
text.append (" BASIC@( ");
break;
case 0xAF:
text.append (" DATE( ");
break;
case 0xB0:
text.append (" EOFMARK( ");
break;
case 0xB1:
text.append (" FILTYP( ");
break;
case 0xB2:
text.append (" FIX( ");
break;
case 0xB3:
text.append (" FREMEM( ");
break;
case 0xB4:
text.append (" NEGATE( ");
break;
case 0xB5:
text.append (" PEEK( ");
break;
case 0xB6:
text.append (" ROUND( ");
break;
case 0xB7:
text.append (" TASKREC%( ");
break;
case 0xB8:
text.append (" TASKREC@( ");
break;
case 0xB9:
text.append (" TIME( ");
break;
case 0xBA:
text.append (" UIR( ");
break;
case 0xBB:
text.append (" STR$( ");
break;
case 0xBC:
text.append (" HEX$( ");
break;
case 0xBD:
text.append (" PFX$( ");
break;
case 0xBE:
text.append (" SPACE$( ");
break;
case 0xBF:
text.append (" ERRTXT$( ");
break;
case 0xC0:
text.append (" CHR$( ");
break;
case 0xC1:
text.append (" RELATION( ");
break;
case 0xC2:
text.append (" ANU( ");
break;
case 0xC3:
text.append (" COMPI( ");
break;
case 0xC4:
text.append (" SCALB( ");
break;
case 0xC5:
text.append (" SCALE( ");
break;
case 0xC6:
text.append (" LEN( ");
break;
case 0xC7:
text.append (" VAL( ");
break;
case 0xC8:
text.append (" ASC( ");
break;
case 0xC9:
text.append (" UCASE$( ");
break;
case 0xCA:
text.append (" TEN( ");
break;
case 0xCB:
text.append (" CONV#( ");
break;
case 0xCC:
text.append (" CONV@( ");
break;
case 0xCD:
text.append (" CONV( ");
break;
case 0xCE:
text.append (" CONV&( ");
break;
case 0xCF:
text.append (" CONV$ ");
break;
case 0xD0:
text.append (" CONV%( ");
break;
case 0xD1:
text.append (" LEFT$( ");
break;
case 0xD2:
text.append (" RIGHT$( ");
break;
case 0xD3:
text.append (" REP$( ");
break;
case 0xD4:
text.append (" MID$( ");
break;
case 0xD5:
text.append (" INSTR( ");
break;
case 0xD6:
text.append (" VARPTR( ");
break;
case 0xD7:
text.append (" VARPTR$( ");
break;
case 0xD8:
text.append (" VAR$( ");
break;
case 0xD9:
text.append (" VAR( ");
break;
case 0xDA:
text.append (" UBOUND( ");
break;
case 0xDB:
text.append (" FILE( ");
break;
case 0xDC:
text.append (" EXEVENT@( ");
break;
case 0xE0:
text.append (" HPOS ");
break;
case 0xE1:
text.append (" VPOS ");
break;
case 0xE2:
text.append (" TIME$ ");
break;
case 0xE3:
text.append (" DATE$ ");
break;
case 0xE4:
text.append (" PREFIX$ ");
break;
case 0xE6:
text.append (" OUTREC ");
break;
case 0xE7:
text.append (" INDENT ");
break;
case 0xE8:
text.append (" SHOWDIGITS ");
break;
case 0xE9:
text.append (" LISTTAB ");
break;
case 0xEA:
text.append (" AUXID@ ");
break;
case 0xEB:
text.append (" EXFN ");
break;
case 0xEC:
text.append (" SECONDS@ ");
break;
case 0xED:
text.append (" FRE ");
break;
case 0xEE:
text.append (" ERRLIN ");
break;
case 0xEF:
text.append (" ERR ");
break;
case 0xF0:
text.append (" KBD ");
break;
case 0xF1:
text.append (" EOF ");
break;
case 0xF2:
text.append (" JOYY ");
break;
case 0xF3:
text.append (" PDL9 ");
break;
case 0xF4:
text.append (" PI ");
break;
case 0xF5:
text.append (" ERRTOOL ");
break;
}
break;
default:
text.append (String.format (" %02X ", b));
}
}
else
{
if ((b & 0xFF) < 32)
text.append ('.');
else
text.append ((char) b);
}
}
line = text.toString ();
}
@Override
public String toString ()
{
return String.format ("%5d %s", lineNumber, line);
}
}
}

View File

@ -101,7 +101,7 @@ public abstract class HiResImage extends AbstractFile
protected void createImage ()
{
if (failureReason.isEmpty ())
if (isGif (buffer) || isPng (buffer) || isBmp (buffer))
if (isGif (buffer) || isPng (buffer) || isBmp (buffer) || isTiff (buffer))
makeImage ();
else if (monochrome)
createMonochromeImage ();
@ -464,6 +464,18 @@ public abstract class HiResImage extends AbstractFile
return true;
}
public static boolean isTiff (byte[] buffer)
{
if (buffer.length < 3)
return false;
String text = new String (buffer, 0, 2);
if (!"II".equals (text) && !"MM".equals (text))
return false;
if (buffer[2] != 0x2A)
return false;
return true;
}
// http://www.daubnet.com/en/file-format-bmp
public static boolean isBmp (byte[] buffer)
{

View File

@ -2,7 +2,7 @@ package com.bytezone.diskbrowser.applefile;
import com.bytezone.diskbrowser.utilities.HexFormatter;
public class IntegerBasicProgram extends AbstractFile
public class IntegerBasicProgram extends BasicProgram
{
private static String[] tokens =
{ "?", "?", "?", " : ", "?", "?", "?", "?", "?", "?", "?", "?", "CLR", "?", "?",
@ -170,8 +170,8 @@ public class IntegerBasicProgram extends AbstractFile
{
int b = buffer[p] & 0xFF;
if (b == 0x03 // token for colon (:)
&& !inString && !inRemark && buffer[p + 1] != 1) // not end of line
if (b == 0x03 // token for colon (:)
&& !inString && !inRemark && buffer[p + 1] != 1) // not end of line
{
text.append (":\n" + " ".substring (0, lineTab));
continue;

View File

@ -175,7 +175,7 @@ abstract class AbstractCatalogEntry implements AppleFileSource
if (reportedLength > buffer.length)
reportedLength = buffer.length - 2;
System.arraycopy (buffer, 2, exactBuffer, 0, reportedLength);
appleFile = new BasicProgram (name, exactBuffer);
appleFile = new ApplesoftBasicProgram (name, exactBuffer);
break;
case Binary: // binary file
@ -238,7 +238,7 @@ abstract class AbstractCatalogEntry implements AppleFileSource
{
byte[] buf = new byte[exactBuffer.length - 4];
System.arraycopy (exactBuffer, 4, buf, 0, buf.length);
appleFile = new BasicProgram (name, buf);
appleFile = new ApplesoftBasicProgram (name, buf);
System.out.printf ("Possible basic binary: %s%n", name);
}
else

View File

@ -13,7 +13,7 @@ import javax.swing.event.ChangeListener;
import com.bytezone.common.FontAction.FontChangeEvent;
import com.bytezone.common.FontAction.FontChangeListener;
import com.bytezone.diskbrowser.applefile.BasicProgram;
import com.bytezone.diskbrowser.applefile.ApplesoftBasicProgram;
import com.bytezone.diskbrowser.applefile.HiResImage;
import com.bytezone.diskbrowser.applefile.Palette;
import com.bytezone.diskbrowser.applefile.PaletteFactory.CycleDirection;
@ -424,7 +424,7 @@ class DataPanel extends JTabbedPane
@Override
public void setBasicPreferences (BasicPreferences basicPreferences)
{
if (currentDataSource instanceof BasicProgram)
if (currentDataSource instanceof ApplesoftBasicProgram)
setDataSource (currentDataSource);
}
}

View File

@ -34,7 +34,7 @@ public class DiskBrowser extends JFrame implements DiskSelectionListener, QuitLi
}
JToolBar toolBar = new JToolBar ("Toolbar", JToolBar.HORIZONTAL);
MenuHandler menuHandler = new MenuHandler (prefs);
MenuHandler menuHandler = new MenuHandler ();
setJMenuBar (menuHandler.menuBar);
setLayout (new BorderLayout ());

View File

@ -92,7 +92,7 @@ public class MenuHandler
ButtonGroup paletteGroup = new ButtonGroup ();
public MenuHandler (Preferences prefs)
public MenuHandler ()
{
menuBar.add (fileMenu);
menuBar.add (formatMenu);

View File

@ -289,7 +289,11 @@ class FileEntry extends CatalogEntry implements ProdosConstants
break;
case FILE_TYPE_APPLESOFT_BASIC:
file = new BasicProgram (name, exactBuffer);
file = new ApplesoftBasicProgram (name, exactBuffer);
break;
case FILE_TYPE_GS_BASIC:
file = new BasicProgramGS (name, exactBuffer);
break;
case FILE_TYPE_INTEGER_BASIC:
@ -391,10 +395,16 @@ class FileEntry extends CatalogEntry implements ProdosConstants
case FILE_TYPE_LDF:
case FILE_TYPE_ANI:
case FILE_TYPE_PAL:
case FILE_TYPE_NON:
file = new DefaultAppleFile (name, exactBuffer);
break;
case FILE_TYPE_NON:
if (name.endsWith (".TIFF") && HiResImage.isTiff (exactBuffer))
file = new OriginalHiResImage (name, exactBuffer, auxType);
else
file = new DefaultAppleFile (name, exactBuffer);
break;
default:
System.out.format ("%s - Unknown Prodos file type : %02X%n", name, fileType);
file = new DefaultAppleFile (name, exactBuffer);

View File

@ -18,10 +18,10 @@ public interface ProdosConstants
int FILE_TYPE_GEO = 0x82;
int FILE_TYPE_IIGS_SOURCE = 0xB0;
int FILE_TYPE_IIGS_OBJECT = 0xB1;
// int FILE_TYPE_FORKED_FILE = 0xB3; // S16
int FILE_TYPE_IIGS_APPLICATION = 0xB3;
int FILE_TYPE_IIGS_DEVICE_DRIVER = 0xBB;
int FILE_TYPE_LDF = 0xBC;
int FILE_TYPE_GS_BASIC = 0xAB;
int FILE_TYPE_GSOS_FILE_SYSTEM_TRANSLATOR = 0xBD;
int FILE_TYPE_PNT = 0xC0;
int FILE_TYPE_PIC = 0xC1;