diff --git a/src/com/bytezone/diskbrowser/applefile/ApplesoftBasicProgram.java b/src/com/bytezone/diskbrowser/applefile/ApplesoftBasicProgram.java new file mode 100644 index 0000000..fb4f8dc --- /dev/null +++ b/src/com/bytezone/diskbrowser/applefile/ApplesoftBasicProgram.java @@ -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 sourceLines = new ArrayList<> (); + private final int endPtr; + private final Set gotoLines = new HashSet<> (); + private final Set 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 loopVariables = new Stack (); + 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 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 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 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 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 splitRemark (String remark, int wrapLength) + { + List 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 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 sublines = new ArrayList (); + 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; + } + } +} \ No newline at end of file diff --git a/src/com/bytezone/diskbrowser/applefile/ApplesoftConstants.java b/src/com/bytezone/diskbrowser/applefile/ApplesoftConstants.java index 85e411a..2d7c19b 100755 --- a/src/com/bytezone/diskbrowser/applefile/ApplesoftConstants.java +++ b/src/com/bytezone/diskbrowser/applefile/ApplesoftConstants.java @@ -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, diff --git a/src/com/bytezone/diskbrowser/applefile/AssemblerProgram.java b/src/com/bytezone/diskbrowser/applefile/AssemblerProgram.java index fbedb8f..c348925 100755 --- a/src/com/bytezone/diskbrowser/applefile/AssemblerProgram.java +++ b/src/com/bytezone/diskbrowser/applefile/AssemblerProgram.java @@ -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 getStrings () + { + TreeMap 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 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 = ""; diff --git a/src/com/bytezone/diskbrowser/applefile/BasicProgram.java b/src/com/bytezone/diskbrowser/applefile/BasicProgram.java index d4434fc..6ea2aac 100644 --- a/src/com/bytezone/diskbrowser/applefile/BasicProgram.java +++ b/src/com/bytezone/diskbrowser/applefile/BasicProgram.java @@ -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 sourceLines = new ArrayList (); - private final int endPtr; - private final Set gotoLines = new HashSet (); - private final Set gosubLines = new HashSet (); - - 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 loopVariables = new Stack (); - 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 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 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 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 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 splitRemark (String remark, int wrapLength) - { - List 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 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 sublines = new ArrayList (); - 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; - } - } -} \ No newline at end of file +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; + } +} diff --git a/src/com/bytezone/diskbrowser/applefile/BasicProgramGS.java b/src/com/bytezone/diskbrowser/applefile/BasicProgramGS.java new file mode 100644 index 0000000..72dc59f --- /dev/null +++ b/src/com/bytezone/diskbrowser/applefile/BasicProgramGS.java @@ -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 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); + } + } +} diff --git a/src/com/bytezone/diskbrowser/applefile/HiResImage.java b/src/com/bytezone/diskbrowser/applefile/HiResImage.java index 9da4c0b..4727050 100644 --- a/src/com/bytezone/diskbrowser/applefile/HiResImage.java +++ b/src/com/bytezone/diskbrowser/applefile/HiResImage.java @@ -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) { diff --git a/src/com/bytezone/diskbrowser/applefile/IntegerBasicProgram.java b/src/com/bytezone/diskbrowser/applefile/IntegerBasicProgram.java index 01b82be..e652e5a 100755 --- a/src/com/bytezone/diskbrowser/applefile/IntegerBasicProgram.java +++ b/src/com/bytezone/diskbrowser/applefile/IntegerBasicProgram.java @@ -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; diff --git a/src/com/bytezone/diskbrowser/dos/AbstractCatalogEntry.java b/src/com/bytezone/diskbrowser/dos/AbstractCatalogEntry.java index bbfbfa3..bbbe500 100644 --- a/src/com/bytezone/diskbrowser/dos/AbstractCatalogEntry.java +++ b/src/com/bytezone/diskbrowser/dos/AbstractCatalogEntry.java @@ -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 diff --git a/src/com/bytezone/diskbrowser/gui/DataPanel.java b/src/com/bytezone/diskbrowser/gui/DataPanel.java index 3e29ef0..1f874ad 100755 --- a/src/com/bytezone/diskbrowser/gui/DataPanel.java +++ b/src/com/bytezone/diskbrowser/gui/DataPanel.java @@ -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); } } \ No newline at end of file diff --git a/src/com/bytezone/diskbrowser/gui/DiskBrowser.java b/src/com/bytezone/diskbrowser/gui/DiskBrowser.java index db758c8..2a645e3 100755 --- a/src/com/bytezone/diskbrowser/gui/DiskBrowser.java +++ b/src/com/bytezone/diskbrowser/gui/DiskBrowser.java @@ -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 ()); diff --git a/src/com/bytezone/diskbrowser/gui/MenuHandler.java b/src/com/bytezone/diskbrowser/gui/MenuHandler.java index 1f29857..f829e17 100755 --- a/src/com/bytezone/diskbrowser/gui/MenuHandler.java +++ b/src/com/bytezone/diskbrowser/gui/MenuHandler.java @@ -92,7 +92,7 @@ public class MenuHandler ButtonGroup paletteGroup = new ButtonGroup (); - public MenuHandler (Preferences prefs) + public MenuHandler () { menuBar.add (fileMenu); menuBar.add (formatMenu); diff --git a/src/com/bytezone/diskbrowser/prodos/FileEntry.java b/src/com/bytezone/diskbrowser/prodos/FileEntry.java index f701683..6107168 100755 --- a/src/com/bytezone/diskbrowser/prodos/FileEntry.java +++ b/src/com/bytezone/diskbrowser/prodos/FileEntry.java @@ -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); diff --git a/src/com/bytezone/diskbrowser/prodos/ProdosConstants.java b/src/com/bytezone/diskbrowser/prodos/ProdosConstants.java index de7c591..2b139b8 100755 --- a/src/com/bytezone/diskbrowser/prodos/ProdosConstants.java +++ b/src/com/bytezone/diskbrowser/prodos/ProdosConstants.java @@ -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;