Making Apple Pascal CODEFILE listing an independent entity. It allows for the disassembly routine to be customized (primary to support the disassembler).

This commit is contained in:
Rob Greene
2025-10-01 10:15:11 -05:00
parent 3042229aa2
commit 13c28c7bd0
3 changed files with 182 additions and 138 deletions
@@ -22,7 +22,7 @@ package com.webcodepro.applecommander.storage.filters;
import com.webcodepro.applecommander.storage.FileEntry;
import com.webcodepro.applecommander.storage.FileFilter;
import org.applecommander.os.pascal.CodeFile;
import org.applecommander.os.pascal.PascalSupport;
import org.applecommander.os.pascal.CodeFileLister;
import java.io.PrintWriter;
import java.io.StringWriter;
@@ -37,11 +37,11 @@ public class PascalCodeFileFilter implements FileFilter {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw, true);
PascalSupport.disassemble(pw, codeFile);
CodeFileLister lister = new CodeFileLister();
lister.list(pw, codeFile);
return sw.toString().getBytes();
}
/**
* Give suggested file name.
*/
@@ -0,0 +1,176 @@
/*
* AppleCommander - An Apple ][ image utility.
* Copyright (C) 2025 by Robert Greene and others
* robgreene at users.sourceforge.net
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.applecommander.os.pascal;
import org.applecommander.disassembler.api.Disassembler;
import org.applecommander.disassembler.api.Instruction;
import org.applecommander.disassembler.api.InstructionSet;
import org.applecommander.disassembler.api.mos6502.InstructionSet6502;
import org.applecommander.disassembler.api.pcode.InstructionSetPCode;
import org.applecommander.util.DataBuffer;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
/**
* Provide standardized list mechanism for Apple Pascal CODEFILE.
* This allows minimal configuration in the disassemble function for pcode and 6502.
*/
public class CodeFileLister {
private final DisassemblyFunc disassemblyFunc;
public CodeFileLister() {
this.disassemblyFunc = this::disassemble;
}
public CodeFileLister(DisassemblyFunc disassemblyFunc) {
this.disassemblyFunc = disassemblyFunc;
}
public void list(PrintWriter pw, CodeFile codeFile) {
// Reset components that are implied in the CodeFile itself:
if (codeFile.comment() != null && !codeFile.comment().isEmpty()) {
pw.printf("Comment: %s\n", codeFile.comment());
}
for (Segment segment : codeFile.segments()) {
if (segment != null) list(pw, segment);
}
}
public void list(PrintWriter pw, Segment segment) {
pw.printf(">> Seg #%02d: FROM=$%04x, TO=$%04x, N='%s', %-10s, T=$%04x, M=%-10s, Ver=%d\n",
segment.segNum(), segment.data().position(), segment.data().limit(), segment.name(),
segment.kind(), segment.textAddr(), segment.machineType(), segment.version());
if (segment.textInterface() != null && !segment.textInterface().isEmpty()) {
pw.println("> Interface text:");
pw.println(segment.textInterface().indent(5));
}
for (var proc : segment.dictionary()) {
if (proc == null) {
pw.println("> Invalid procedure header.");
continue;
}
switch (proc) {
case PCodeProcedure pcode -> list(pw, pcode);
case AssemblyProcedure asm -> list(pw, asm);
default -> throw new RuntimeException("Unexpected procedure type: " + proc.getClass().getName());
}
}
}
public void list(PrintWriter pw, PCodeProcedure pcode) {
pw.printf("> Proc#%d, Lex Lvl %d, Enter $%04x, Exit $%04x, Param %d, Data %d, JTAB=$%04x\n",
pcode.procNum(), pcode.lexLevel(), pcode.enterIC(), pcode.exitIC(),
pcode.paramsSize(), pcode.dataSize(), pcode.jumpTable());
// We want to indent the resulting assembly, so a temporary new PrintWriter so indentation can be applied
StringWriter stringWriter = new StringWriter();
PrintWriter printWriter = new PrintWriter(stringWriter, true);
disassemblyFunc.disassemble(printWriter, InstructionSetPCode.forApplePascal(), pcode.enterIC(), pcode.codeBytes());
pw.println(stringWriter.toString().indent(5));
}
public void list(PrintWriter pw, AssemblyProcedure asm) {
pw.printf("> ASM Proc#%d, Relocation Segment #%d, Enter $%04x\n",
asm.procNum(), asm.relocSegNum(), asm.enterIC());
BiConsumer<int[], String> formatter = (table, name) -> {
if (table.length > 0) {
pw.printf("\t%s-relative relocation table: ", name);
for (int addr : table) pw.printf("$%04X ", addr);
pw.println();
}
};
formatter.accept(asm.baseRelativeReloc(), "base");
formatter.accept(asm.segRelativeReloc(), "segment");
formatter.accept(asm.procRelativeReloc(), "procedure");
formatter.accept(asm.interpRelativeReloc(), "interpreter");
var db = DataBuffer.wrap(asm.codeBytes());
for (int addr : asm.procRelativeReloc()) {
int offset = addr - asm.enterIC();
db.putUnsignedShort(offset, db.getUnsignedShort(offset) + asm.enterIC());
}
// We want to indent the resulting assembly, so a temporary new PrintWriter so indentation can be applied
StringWriter stringWriter = new StringWriter();
PrintWriter printWriter = new PrintWriter(stringWriter, true);
disassemblyFunc.disassemble(printWriter, InstructionSet6502.for6502(), asm.enterIC(), db.asBytes());
pw.println(stringWriter.toString().indent(5));
}
public interface DisassemblyFunc {
void disassemble(PrintWriter pw, InstructionSet instructionSet, int startAddress, byte[] procedureCode);
}
public void disassemble(PrintWriter pw, InstructionSet instructionSet, int startAddress, byte[] procedureCode) {
Map<Integer, String> labels = new HashMap<>();
List<Instruction> assembly = Disassembler.with(procedureCode)
.startingAddress(startAddress)
.use(instructionSet)
.decode(labels);
final int bytesPerLine = instructionSet.defaults().bytesPerInstruction();
assembly.forEach(instruction -> {
pw.printf("%04X- ", instruction.address());
byte[] instructionCode = instruction.code();
for (int i = 0; i < bytesPerLine; i++) {
if (i >= instructionCode.length) {
pw.print(" ");
} else {
pw.printf("%02X ", instructionCode[i]);
}
}
pw.printf(" %-10.10s ", labels.getOrDefault(instruction.address(), ""));
pw.printf("%-5s ", instruction.mnemonic());
pw.printf("%-30s ", instruction.operands().stream().map(operand -> {
if (operand.address().isPresent() && labels.containsKey(operand.address().get())) {
return operand.format(labels.get(operand.address().get()));
} else {
return operand.format();
}
})
.collect(Collectors.joining(",")));
if (instructionSet.defaults().includeDescription()) {
instruction.description().ifPresent(description -> {
pw.printf("; %s", description);
});
}
pw.println();
if (instructionCode.length > bytesPerLine) {
for (int i = bytesPerLine; i < instructionCode.length; i++) {
if (i % bytesPerLine == 0) {
if (i > bytesPerLine) pw.println();
pw.printf("%04X- ", instruction.address() + i);
}
pw.printf("%02X ", instructionCode[i]);
}
pw.println();
}
});
}
}
@@ -19,21 +19,11 @@
*/
package org.applecommander.os.pascal;
import org.applecommander.disassembler.api.Disassembler;
import org.applecommander.disassembler.api.Instruction;
import org.applecommander.disassembler.api.InstructionSet;
import org.applecommander.disassembler.api.mos6502.InstructionSet6502;
import org.applecommander.disassembler.api.pcode.InstructionSetPCode;
import org.applecommander.util.DataBuffer;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
/**
* Provides common Pascal support code.
*/
public class PascalSupport {
private PascalSupport() {
// prevent construction
@@ -62,126 +52,4 @@ public class PascalSupport {
}
return sb.toString();
}
public static void disassemble(PrintWriter pw, CodeFile codeFile) {
// Reset components that are implied in the CodeFile itself:
if (codeFile.comment() != null && !codeFile.comment().isEmpty()) {
pw.printf("Comment: %s\n", codeFile.comment());
}
for (Segment segment : codeFile.segments()) {
if (segment != null) disassemble(pw, segment);
}
}
public static void disassemble(PrintWriter pw, Segment segment) {
pw.printf(">> Seg #%02d: FROM=$%04x, TO=$%04x, N='%s', %-10s, T=$%04x, M=%-10s, Ver=%d\n",
segment.segNum(), segment.data().position(), segment.data().limit(), segment.name(),
segment.kind(), segment.textAddr(), segment.machineType(), segment.version());
if (segment.textInterface() != null && !segment.textInterface().isEmpty()) {
pw.println("> Interface text:");
pw.println(segment.textInterface().indent(5));
}
for (var proc : segment.dictionary()) {
if (proc == null) {
pw.println("> Invalid procedure header.");
continue;
}
switch (proc) {
case PCodeProcedure pcode -> disassemble(pw, pcode);
case AssemblyProcedure asm -> disassemble(pw, asm);
default -> throw new RuntimeException("Unexpected procedure type: " + proc.getClass().getName());
}
}
}
public static void disassemble(PrintWriter pw, PCodeProcedure pcode) {
pw.printf("> Proc#%d, Lex Lvl %d, Enter $%04x, Exit $%04x, Param %d, Data %d, JTAB=$%04x\n",
pcode.procNum(), pcode.lexLevel(), pcode.enterIC(), pcode.exitIC(),
pcode.paramsSize(), pcode.dataSize(), pcode.jumpTable());
// We want to indent the resulting assembly, so a temporary new PrintWriter so indentation can be applied
StringWriter stringWriter = new StringWriter();
PrintWriter printWriter = new PrintWriter(stringWriter, true);
disassemble(printWriter, InstructionSetPCode.forApplePascal(), pcode.enterIC(), pcode.codeBytes());
pw.println(stringWriter.toString().indent(5));
}
public static void disassemble(PrintWriter pw, AssemblyProcedure asm) {
pw.printf("> ASM Proc#%d, Relocation Segment #%d, Enter $%04x\n",
asm.procNum(), asm.relocSegNum(), asm.enterIC());
BiConsumer<int[], String> formatter = (table, name) -> {
if (table.length > 0) {
pw.printf("\t%s-relative relocation table: ", name);
for (int addr : table) pw.printf("$%04X ", addr);
pw.println();
}
};
formatter.accept(asm.baseRelativeReloc(), "base");
formatter.accept(asm.segRelativeReloc(), "segment");
formatter.accept(asm.procRelativeReloc(), "procedure");
formatter.accept(asm.interpRelativeReloc(), "interpreter");
var db = DataBuffer.wrap(asm.codeBytes());
for (int addr : asm.procRelativeReloc()) {
int offset = addr - asm.enterIC();
db.putUnsignedShort(offset, db.getUnsignedShort(offset) + asm.enterIC());
}
// We want to indent the resulting assembly, so a temporary new PrintWriter so indentation can be applied
StringWriter stringWriter = new StringWriter();
PrintWriter printWriter = new PrintWriter(stringWriter, true);
disassemble(printWriter, InstructionSet6502.for6502(), asm.enterIC(), db.asBytes());
pw.println(stringWriter.toString().indent(5));
}
public static void disassemble(PrintWriter pw, InstructionSet instructionSet, int startAddress, byte[] procedureCode) {
Map<Integer,String> labels = new HashMap<>();
List<Instruction> assembly = Disassembler.with(procedureCode)
.startingAddress(startAddress)
.use(instructionSet)
.decode(labels);
final int bytesPerLine = instructionSet.defaults().bytesPerInstruction();
assembly.forEach(instruction -> {
pw.printf("%04X- ", instruction.address());
byte[] instructionCode = instruction.code();
for (int i=0; i<bytesPerLine; i++) {
if (i >= instructionCode.length) {
pw.print(" ");
} else {
pw.printf("%02X ", instructionCode[i]);
}
}
pw.printf(" %-10.10s ", labels.getOrDefault(instruction.address(), ""));
pw.printf("%-5s ", instruction.mnemonic());
pw.printf("%-30s ", instruction.operands().stream().map(operand -> {
if (operand.address().isPresent() && labels.containsKey(operand.address().get())) {
return operand.format(labels.get(operand.address().get()));
}
else {
return operand.format();
}
})
.collect(Collectors.joining(",")));
if (instructionSet.defaults().includeDescription()) {
instruction.description().ifPresent(description -> {
pw.printf("; %s", description);
});
}
pw.println();
if (instructionCode.length > bytesPerLine) {
for (int i=bytesPerLine; i<instructionCode.length; i++) {
if (i % bytesPerLine == 0) {
if (i > bytesPerLine) pw.println();
pw.printf("%04X- ", instruction.address()+i);
}
pw.printf("%02X ", instructionCode[i]);
}
pw.println();
}
});
}
}