mirror of
https://github.com/dmolony/DiskBrowser.git
synced 2024-11-25 16:34:00 +00:00
GS Basic
This commit is contained in:
parent
9e94bdfe50
commit
c7a16c8033
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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,
|
||||
|
@ -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 = "";
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
714
src/com/bytezone/diskbrowser/applefile/BasicProgramGS.java
Normal file
714
src/com/bytezone/diskbrowser/applefile/BasicProgramGS.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
{
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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 ());
|
||||
|
@ -92,7 +92,7 @@ public class MenuHandler
|
||||
|
||||
ButtonGroup paletteGroup = new ButtonGroup ();
|
||||
|
||||
public MenuHandler (Preferences prefs)
|
||||
public MenuHandler ()
|
||||
{
|
||||
menuBar.add (fileMenu);
|
||||
menuBar.add (formatMenu);
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user