added header underlines

This commit is contained in:
Denis Molony 2020-09-13 10:38:45 +10:00
parent 147f7e03f2
commit 600c3a5ce2

View File

@ -1,454 +1,488 @@
package com.bytezone.diskbrowser.applefile; package com.bytezone.diskbrowser.applefile;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import com.bytezone.diskbrowser.gui.AssemblerPreferences; import com.bytezone.diskbrowser.gui.AssemblerPreferences;
import com.bytezone.diskbrowser.gui.DiskBrowser; import com.bytezone.diskbrowser.gui.DiskBrowser;
import com.bytezone.diskbrowser.utilities.HexFormatter; import com.bytezone.diskbrowser.utilities.HexFormatter;
public class AssemblerProgram extends AbstractFile // -----------------------------------------------------------------------------------//
{ public class AssemblerProgram extends AbstractFile
static AssemblerPreferences assemblerPreferences; // set by MenuHandler // -----------------------------------------------------------------------------------//
{
private static Map<Integer, String> equates; static AssemblerPreferences assemblerPreferences; // set by MenuHandler
private final int loadAddress; private static Map<Integer, String> equates;
private int executeOffset;
private final int loadAddress;
private byte[] extraBuffer = new byte[0]; private int executeOffset;
private List<Integer> entryPoints; private byte[] extraBuffer = new byte[0];
private List<StringLocation> stringLocations;
private List<Integer> entryPoints;
public static void setAssemblerPreferences (AssemblerPreferences assemblerPreferences) private List<StringLocation> stringLocations;
{
AssemblerProgram.assemblerPreferences = assemblerPreferences; // ---------------------------------------------------------------------------------//
} public static void setAssemblerPreferences (AssemblerPreferences assemblerPreferences)
// ---------------------------------------------------------------------------------//
public AssemblerProgram (String name, byte[] buffer, int address) {
{ AssemblerProgram.assemblerPreferences = assemblerPreferences;
super (name, buffer); }
this.loadAddress = address;
// ---------------------------------------------------------------------------------//
if (equates == null) public AssemblerProgram (String name, byte[] buffer, int address)
getEquates (); // ---------------------------------------------------------------------------------//
{
// AssemblerBlocks assemblerBlocks = new AssemblerBlocks (buffer, address); super (name, buffer);
} this.loadAddress = address;
public AssemblerProgram (String name, byte[] buffer, int address, int executeOffset) if (equates == null)
{ getEquates ();
this (name, buffer, address);
this.executeOffset = executeOffset; // AssemblerBlocks assemblerBlocks = new AssemblerBlocks (buffer, address);
} }
public void setExtraBuffer (byte[] fullBuffer, int offset, int length) // ---------------------------------------------------------------------------------//
{ public AssemblerProgram (String name, byte[] buffer, int address, int executeOffset)
if (length >= 0) // ---------------------------------------------------------------------------------//
{ {
this.extraBuffer = new byte[length]; this (name, buffer, address);
System.arraycopy (fullBuffer, offset, extraBuffer, 0, length); this.executeOffset = executeOffset;
} }
else
System.out.println ("Invalid length in setExtraBuffer() : " + length); // ---------------------------------------------------------------------------------//
} public void setExtraBuffer (byte[] fullBuffer, int offset, int length)
// ---------------------------------------------------------------------------------//
@Override {
public String getHexDump () if (length >= 0)
{ {
// It might be useful to add opt-O to change the offset. Sometimes it's useful this.extraBuffer = new byte[length];
// to see the hex dump offset from zero, other times it's better to use the System.arraycopy (fullBuffer, offset, extraBuffer, 0, length);
// load address. }
String text = HexFormatter.format (buffer, 0, buffer.length, loadAddress); else
System.out.println ("Invalid length in setExtraBuffer() : " + length);
if (extraBuffer.length == 0) }
return text;
// ---------------------------------------------------------------------------------//
return text + "\n\nData outside actual buffer:\n\n" + HexFormatter @Override
.format (extraBuffer, 0, extraBuffer.length, loadAddress + buffer.length); public String getHexDump ()
} // ---------------------------------------------------------------------------------//
{
@Override // It might be useful to add opt-O to change the offset. Sometimes it's useful
public String getAssembler () // to see the hex dump offset from zero, other times it's better to use the
{ // load address.
if (buffer == null) String text = HexFormatter.format (buffer, 0, buffer.length, loadAddress);
return "No buffer";
if (extraBuffer.length == 0)
if (assembler == null) return text;
this.assembler = new AssemblerProgram (name, buffer, loadAddress);
return text + "\n\nData outside actual buffer:\n\n" + HexFormatter
if (extraBuffer.length == 0) .format (extraBuffer, 0, extraBuffer.length, loadAddress + buffer.length);
return assembler.getText (); }
String extraName = String.format ("%s (extra)", name); // ---------------------------------------------------------------------------------//
AssemblerProgram assemblerProgram = @Override
new AssemblerProgram (extraName, extraBuffer, loadAddress + buffer.length); public String getAssembler ()
// ---------------------------------------------------------------------------------//
return assembler.getText () + "\n\n" + assemblerProgram.getText (); {
} if (buffer == null)
return "No buffer";
private void addHeader (StringBuilder pgm)
{ if (assembler == null)
pgm.append (String.format ("Name : %s%n", name)); this.assembler = new AssemblerProgram (name, buffer, loadAddress);
pgm.append (String.format ("Length : $%04X (%,d)%n", buffer.length, buffer.length));
pgm.append (String.format ("Load at : $%04X (%,d)%n", loadAddress, loadAddress)); if (extraBuffer.length == 0)
return assembler.getText ();
if (executeOffset > 0)
pgm.append (String.format ("Entry : $%04X%n", (loadAddress + executeOffset))); String extraName = String.format ("%s (extra)", name);
pgm.append ("\n"); AssemblerProgram assemblerProgram =
} new AssemblerProgram (extraName, extraBuffer, loadAddress + buffer.length);
@Override return assembler.getText () + "\n\n" + assemblerProgram.getText ();
public String getText () }
{
StringBuilder pgm = new StringBuilder (); // ---------------------------------------------------------------------------------//
private void addHeader (StringBuilder pgm)
if (assemblerPreferences.showHeader) // ---------------------------------------------------------------------------------//
addHeader (pgm); {
pgm.append (String.format ("Name : %s%n", name));
pgm.append (getListing ()); pgm.append (String.format ("Length : $%04X (%,d)%n", buffer.length, buffer.length));
pgm.append (String.format ("Load at : $%04X (%,d)%n", loadAddress, loadAddress));
if (assemblerPreferences.showStrings)
pgm.append (getStringsText ()); if (executeOffset > 0)
pgm.append (String.format ("Entry : $%04X%n", (loadAddress + executeOffset)));
return pgm.toString (); pgm.append ("\n");
} }
private String getListing () // ---------------------------------------------------------------------------------//
{ @Override
StringBuilder pgm = new StringBuilder (); public String getText ()
// ---------------------------------------------------------------------------------//
List<AssemblerStatement> lines = getLines (); {
StringBuilder pgm = new StringBuilder ();
if (stringLocations == null)
getStrings (); if (assemblerPreferences.showHeader)
addHeader (pgm);
// if the assembly doesn't start at the beginning, just dump the bytes that
// are skipped pgm.append (getListing ());
for (int i = 0; i < executeOffset; i++)
pgm.append (String.format (" %04X: %02X%n", (loadAddress + i), buffer[i])); if (assemblerPreferences.showStrings)
pgm.append (getStringsText ());
for (AssemblerStatement cmd : lines)
{ return pgm.toString ();
StringBuilder line = new StringBuilder (); }
String arrowText = assemblerPreferences.showTargets ? getArrow (cmd) : ""; // ---------------------------------------------------------------------------------//
line.append ( private String getListing ()
String.format ("%3.3s %04X: %02X ", arrowText, cmd.address, cmd.value)); // ---------------------------------------------------------------------------------//
{
if (cmd.size > 1) StringBuilder pgm = new StringBuilder ();
line.append (String.format ("%02X ", cmd.operand1));
if (cmd.size > 2) List<AssemblerStatement> lines = getLines ();
line.append (String.format ("%02X ", cmd.operand2));
if (stringLocations == null)
while (line.length () < 23) getStrings ();
line.append (" ");
// if the assembly doesn't start at the beginning, just dump the bytes that
line.append (cmd.mnemonic + " " + cmd.operand); // are skipped
for (int i = 0; i < executeOffset; i++)
if (cmd.offset != 0) pgm.append (String.format (" %04X: %02X%n", (loadAddress + i), buffer[i]));
{
int branch = cmd.address + cmd.offset + 2; for (AssemblerStatement cmd : lines)
line.append (String.format ("$%04X", branch < 0 ? branch += 0xFFFF : branch)); {
} StringBuilder line = new StringBuilder ();
else if (cmd.target > 0
&& (cmd.target < loadAddress - 1 || cmd.target > (loadAddress + buffer.length))) String arrowText = assemblerPreferences.showTargets ? getArrow (cmd) : "";
{ line.append (
while (line.length () < 40) String.format ("%3.3s %04X: %02X ", arrowText, cmd.address, cmd.value));
line.append (" ");
if (cmd.size > 1)
String text = equates.get (cmd.target); line.append (String.format ("%02X ", cmd.operand1));
if (text != null) if (cmd.size > 2)
line.append ("; " + text); line.append (String.format ("%02X ", cmd.operand2));
else
for (int i = 0, max = ApplesoftConstants.tokenAddresses.length; i < max; i++) while (line.length () < 23)
if (cmd.target == ApplesoftConstants.tokenAddresses[i]) line.append (" ");
{
line.append ("; Applesoft - " + ApplesoftConstants.tokens[i]); line.append (cmd.mnemonic + " " + cmd.operand);
break;
} if (cmd.offset != 0)
} {
int branch = cmd.address + cmd.offset + 2;
pgm.append (line.toString () + "\n"); line.append (String.format ("$%04X", branch < 0 ? branch += 0xFFFF : branch));
} }
else if (cmd.target > 0
if (pgm.length () > 0) && (cmd.target < loadAddress - 1 || cmd.target > (loadAddress + buffer.length)))
pgm.deleteCharAt (pgm.length () - 1); {
while (line.length () < 40)
return pgm.toString (); line.append (" ");
}
String text = equates.get (cmd.target);
// private int showString (AssemblerStatement cmd, StringBuilder line) if (text != null)
// { line.append ("; " + text);
// int key = cmd.address - loadAddress; else
// if (strings.containsKey (key)) for (int i = 0, max = ApplesoftConstants.tokenAddresses.length; i < max; i++)
// { if (cmd.target == ApplesoftConstants.tokenAddresses[i])
// while (line.length () < 40) {
// line.append (" "); line.append ("; Applesoft - " + ApplesoftConstants.tokens[i]);
// String s = strings.get (key); break;
// line.append ("# " + s); }
// return s.length () - cmd.size; }
// }
// return 0; pgm.append (line.toString () + "\n");
// } }
private List<AssemblerStatement> getLines () if (pgm.length () > 0)
{ pgm.deleteCharAt (pgm.length () - 1);
List<AssemblerStatement> lines = new ArrayList<> ();
Map<Integer, AssemblerStatement> linesMap = new HashMap<> (); return pgm.toString ();
List<Integer> targets = new ArrayList<> (); }
int ptr = executeOffset; // private int showString (AssemblerStatement cmd, StringBuilder line)
int address = loadAddress + executeOffset; // {
// int key = cmd.address - loadAddress;
while (ptr < buffer.length) // if (strings.containsKey (key))
{ // {
AssemblerStatement cmd = new AssemblerStatement (buffer[ptr]); // while (line.length () < 40)
lines.add (cmd); // line.append (" ");
linesMap.put (address, cmd); // String s = strings.get (key);
cmd.address = address; // line.append ("# " + s);
// return s.length () - cmd.size;
if (cmd.size == 2 && ptr < buffer.length - 1) // }
cmd.addData (buffer[ptr + 1]); // return 0;
else if (cmd.size == 3 && ptr < buffer.length - 2) // }
cmd.addData (buffer[ptr + 1], buffer[ptr + 2]);
else // ---------------------------------------------------------------------------------//
cmd.size = 1; private List<AssemblerStatement> getLines ()
// ---------------------------------------------------------------------------------//
// JMP, JMP, JSR {
if (cmd.target >= loadAddress && cmd.target < (loadAddress + buffer.length) List<AssemblerStatement> lines = new ArrayList<> ();
&& (cmd.value == 0x4C || cmd.value == 0x6C || cmd.value == 0x20)) Map<Integer, AssemblerStatement> linesMap = new HashMap<> ();
targets.add (cmd.target); List<Integer> targets = new ArrayList<> ();
// branch relative int ptr = executeOffset;
if (cmd.offset != 0) int address = loadAddress + executeOffset;
targets.add (cmd.address + cmd.offset + 2);
while (ptr < buffer.length)
address += cmd.size; {
ptr += cmd.size; AssemblerStatement cmd = new AssemblerStatement (buffer[ptr]);
} lines.add (cmd);
linesMap.put (address, cmd);
for (Integer target : targets) cmd.address = address;
{
AssemblerStatement cmd = linesMap.get (target); if (cmd.size == 2 && ptr < buffer.length - 1)
if (cmd != null) cmd.addData (buffer[ptr + 1]);
cmd.isTarget = true; else if (cmd.size == 3 && ptr < buffer.length - 2)
} cmd.addData (buffer[ptr + 1], buffer[ptr + 2]);
else
return lines; cmd.size = 1;
}
// JMP, JMP, JSR
private String getStringsText () if (cmd.target >= loadAddress && cmd.target < (loadAddress + buffer.length)
{ && (cmd.value == 0x4C || cmd.value == 0x6C || cmd.value == 0x20))
if (stringLocations.size () == 0) targets.add (cmd.target);
return "";
// branch relative
StringBuilder text = new StringBuilder ("\n\nPossible strings:\n\n"); if (cmd.offset != 0)
for (StringLocation stringLocation : stringLocations) targets.add (cmd.address + cmd.offset + 2);
{
int address = stringLocation.offset + loadAddress; address += cmd.size;
text.append (String.format ("%s %04X - %04X %s %n", ptr += cmd.size;
entryPoints.contains (stringLocation.offset) ? "*" : " ", address, }
address + stringLocation.length, stringLocation));
} for (Integer target : targets)
{
if (text.length () > 0) AssemblerStatement cmd = linesMap.get (target);
text.deleteCharAt (text.length () - 1); if (cmd != null)
cmd.isTarget = true;
return text.toString (); }
}
return lines;
private void getStrings () }
{
entryPoints = new ArrayList<> (); // ---------------------------------------------------------------------------------//
stringLocations = new ArrayList<> (); private String getStringsText ()
// ---------------------------------------------------------------------------------//
int start = 0; {
for (int ptr = 0; ptr < buffer.length; ptr++) if (stringLocations.size () == 0)
{ return "";
if ((buffer[ptr] & 0x80) != 0) // hi bit set
continue; StringBuilder text = new StringBuilder ("\n\nPossible strings:\n\n");
for (StringLocation stringLocation : stringLocations)
if (buffer[ptr] == 0x0D) // CR {
continue; int address = stringLocation.offset + loadAddress;
text.append (String.format ("%s %04X - %04X %s %n",
if (ptr - start > 3) entryPoints.contains (stringLocation.offset) ? "*" : " ", address,
stringLocations.add (new StringLocation (start, ptr - 1)); address + stringLocation.length, stringLocation));
}
start = ptr + 1;
} if (text.length () > 0)
text.deleteCharAt (text.length () - 1);
if (buffer.length - start > 3)
stringLocations.add (new StringLocation (start, buffer.length - 1)); return text.toString ();
}
int max = buffer.length - 2;
for (StringLocation stringLocation : stringLocations) // ---------------------------------------------------------------------------------//
for (int ptr = 0; ptr < max; ptr++) private void getStrings ()
if (stringLocation.matches (buffer, ptr)) // ---------------------------------------------------------------------------------//
{ {
entryPoints.add (stringLocation.offset); entryPoints = new ArrayList<> ();
break; stringLocations = new ArrayList<> ();
}
} int start = 0;
for (int ptr = 0; ptr < buffer.length; ptr++)
private String getArrow (AssemblerStatement cmd) {
{ if ((buffer[ptr] & 0x80) != 0) // hi bit set
String arrow = ""; continue;
if (cmd.value == 0x4C || cmd.value == 0x6C || cmd.value == 0x60 || cmd.offset != 0) if (buffer[ptr] == 0x0D) // CR
arrow = "<--"; continue;
if (cmd.value == 0x20 && isLocal (cmd.target)) // JSR if (ptr - start > 3)
arrow = "<--"; stringLocations.add (new StringLocation (start, ptr - 1));
if (cmd.isTarget) start = ptr + 1;
if (arrow.isEmpty ()) }
arrow = "-->";
else if (buffer.length - start > 3)
arrow = "<->"; stringLocations.add (new StringLocation (start, buffer.length - 1));
return arrow; int max = buffer.length - 2;
} for (StringLocation stringLocation : stringLocations)
for (int ptr = 0; ptr < max; ptr++)
private boolean isLocal (int target) if (stringLocation.matches (buffer, ptr))
{ {
return target >= loadAddress entryPoints.add (stringLocation.offset);
&& target < loadAddress + buffer.length + extraBuffer.length; break;
} }
}
private void getEquates ()
{ // ---------------------------------------------------------------------------------//
equates = new HashMap<Integer, String> (); private String getArrow (AssemblerStatement cmd)
DataInputStream inputEquates = // ---------------------------------------------------------------------------------//
new DataInputStream (DiskBrowser.class.getClassLoader () {
.getResourceAsStream ("com/bytezone/diskbrowser/applefile/equates.txt")); String arrow = "";
BufferedReader in = new BufferedReader (new InputStreamReader (inputEquates));
if (cmd.value == 0x4C || cmd.value == 0x6C || cmd.value == 0x60 || cmd.offset != 0)
String line; arrow = "<--";
try
{ if (cmd.value == 0x20 && isLocal (cmd.target)) // JSR
while ((line = in.readLine ()) != null) arrow = "<--";
{
if (!line.isEmpty () && !line.startsWith ("*")) if (cmd.isTarget)
{ if (arrow.isEmpty ())
int address = Integer.parseInt (line.substring (0, 4), 16); arrow = "-->";
if (equates.containsKey (address)) else
System.out.printf ("Duplicate equate entry : %04X%n" + address); arrow = "<->";
else
equates.put (address, line.substring (6)); return arrow;
} }
}
in.close (); // ---------------------------------------------------------------------------------//
} private boolean isLocal (int target)
catch (IOException e) // ---------------------------------------------------------------------------------//
{ {
e.printStackTrace (); return target >= loadAddress
} && target < loadAddress + buffer.length + extraBuffer.length;
} }
class StringLocation // ---------------------------------------------------------------------------------//
{ private void getEquates ()
int offset; // ---------------------------------------------------------------------------------//
byte hi, lo; {
int length; equates = new HashMap<Integer, String> ();
boolean zeroTerminated; DataInputStream inputEquates =
boolean lowTerminated; new DataInputStream (DiskBrowser.class.getClassLoader ()
boolean hasLengthByte; .getResourceAsStream ("com/bytezone/diskbrowser/applefile/equates.txt"));
int digits; BufferedReader in = new BufferedReader (new InputStreamReader (inputEquates));
int letters;
int punctuation; String line;
int controlChars; try
int spaces; {
while ((line = in.readLine ()) != null)
public StringLocation (int first, int last) {
{ if (!line.isEmpty () && !line.startsWith ("*"))
offset = first; {
length = last - offset + 1; int address = Integer.parseInt (line.substring (0, 4), 16);
int end = last + 1; if (equates.containsKey (address))
System.out.printf ("Duplicate equate entry : %04X%n" + address);
zeroTerminated = end < buffer.length && buffer[end] == 0; else
lowTerminated = end < buffer.length && buffer[end] >= 32 && buffer[end] < 127; equates.put (address, line.substring (6));
}
if (first > 0 && (buffer[first] & 0xFF) == length + 1) }
{ in.close ();
hasLengthByte = true; }
--offset; catch (IOException e)
++length; {
} e.printStackTrace ();
}
hi = (byte) ((offset + loadAddress) >>> 8); }
lo = (byte) ((offset + loadAddress) & 0x00FF);
// ---------------------------------------------------------------------------------//
for (int i = offset; i < offset + length; i++) class StringLocation
{ // ---------------------------------------------------------------------------------//
int val = buffer[i] & 0x7F; {
if (val < 32 || val == 127) int offset;
++controlChars; byte hi, lo;
else if (val == 32) int length;
++spaces; boolean zeroTerminated;
else if (val >= 48 && val <= 57) boolean lowTerminated;
++digits; boolean hasLengthByte;
else if (val >= 65 && val <= 90) int digits;
++letters; int letters;
else if (val >= 97 && val <= 122) int punctuation;
++letters; int controlChars;
else int spaces;
++punctuation;
} public StringLocation (int first, int last)
} {
offset = first;
boolean matches (byte[] buffer, int ptr) length = last - offset + 1;
{ int end = last + 1;
return lo == buffer[ptr] && hi == buffer[ptr + 1];
} zeroTerminated = end < buffer.length && buffer[end] == 0;
lowTerminated = end < buffer.length && buffer[end] >= 32 && buffer[end] < 127;
boolean likelyString ()
{ if (first > 0 && (buffer[first] & 0xFF) == length + 1)
return spaces > 0 || letters > punctuation; {
} hasLengthByte = true;
--offset;
public String address () ++length;
{ }
return String.format ("%04X %02X %02X", offset, hi, lo);
} hi = (byte) ((offset + loadAddress) >>> 8);
lo = (byte) ((offset + loadAddress) & 0x00FF);
public String toStatisticsString ()
{ for (int i = offset; i < offset + length; i++)
return String.format ("%2d, %2d, %2d, %2d, %2d", digits, letters, punctuation, {
controlChars, spaces); int val = buffer[i] & 0x7F;
} if (val < 32 || val == 127)
++controlChars;
@Override else if (val == 32)
public String toString () ++spaces;
{ else if (val >= 48 && val <= 57)
StringBuilder text = new StringBuilder (); ++digits;
else if (val >= 65 && val <= 90)
if (hasLengthByte) ++letters;
text.append ("<length>"); else if (val >= 97 && val <= 122)
++letters;
for (int i = offset; i < offset + length; i++) else
{ ++punctuation;
int val = buffer[i] & 0x7F; }
if (val == 4) }
text.append ("<ctrl-D>");
else if (val == 10) boolean matches (byte[] buffer, int ptr)
text.append ("<LF>"); {
else if (val == 13) return lo == buffer[ptr] && hi == buffer[ptr + 1];
text.append ("<CR>"); }
else
text.append ((char) val); boolean likelyString ()
} {
if (lowTerminated) return spaces > 0 || letters > punctuation;
text.append ((char) buffer[offset + length]); }
return text.toString (); public String address ()
} {
} return String.format ("%04X %02X %02X", offset, hi, lo);
}
public String toStatisticsString ()
{
return String.format ("%2d, %2d, %2d, %2d, %2d", digits, letters, punctuation,
controlChars, spaces);
}
@Override
public String toString ()
{
StringBuilder text = new StringBuilder ();
if (hasLengthByte)
text.append ("<length>");
for (int i = offset; i < offset + length; i++)
{
int val = buffer[i] & 0x7F;
if (val == 4)
text.append ("<ctrl-D>");
else if (val == 10)
text.append ("<LF>");
else if (val == 13)
text.append ("<CR>");
else
text.append ((char) val);
}
if (lowTerminated)
text.append ((char) buffer[offset + length]);
return text.toString ();
}
}
} }