Two changes: (1) Refactored "proofreaders" into an input buffer-based proofreader and a tokenized proofreader along with code changes; (2) Checkit now handles "?" as "PRINT".

This commit is contained in:
Rob Greene committed 2026-01-10 13:03:20 -06:00
1 parent 20d8d53b5a
commit 2c03a3c62d
18 files changed
+431 -321

No files matched your search

@@ -0,0 +1,35 @@
package org.applecommander.bastools.api.proofreaders;
import org.applecommander.bastools.api.Configuration;
import org.applecommander.bastools.api.Visitor;
import org.applecommander.bastools.api.Visitors;
import org.applecommander.bastools.api.model.Program;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
/**
* Standard interface for proofreaders that are evaluated as code is typed in.
* That is, these evaluate the input buffer at $200 when a return is issued.
*/
public interface ApplesoftInputBufferProofReader {
/**
* Returns the configuration object to use. The configuration object includes the following details:
* start address, number preservation settings, and file name.
*/
Configuration getConfiguration();
/** Adds program by converting to text and using addProgramText. */
default void addProgram(Program program) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
PrintStream printStream = new PrintStream(outputStream);
Visitor printVisitor = Visitors.printBuilder(getConfiguration()).printStream(printStream).print().build();
printVisitor.visit(program);
addProgramText(outputStream.toString());
}
/** Adds source code line-by-line using addLine. */
default void addProgramText(String code) {
code.lines().forEach(this::addLine);
}
/** Parses the line and adds to the current checksums. */
void addLine(String line);
}
@@ -0,0 +1,24 @@
package org.applecommander.bastools.api.proofreaders;
import org.applecommander.bastools.api.Configuration;
import org.applecommander.bastools.api.model.Program;
import org.applecommander.bastools.api.visitors.ByteVisitor;
/**
* Standard interface for proofreaders that evaluate a tokenized program.
*/
public interface ApplesoftTokenizedProofReader {
/**
* Returns the configuration object to use. The configuration object includes the following details:
* start address, number preservation settings, and file name.
*/
Configuration getConfiguration();
/** Convert the Program to a tokenized byte[] and then process with proofreader. */
default void addProgram(Program program) {
ByteVisitor byteVisitor = new ByteVisitor(getConfiguration());
byteVisitor.visit(program);
addBytes(byteVisitor.getBytes());
}
/** Parses the line and adds to the current checksums. */
void addBytes(byte... tokenizedProgram);
}
@@ -1,34 +1,43 @@
package org.applecommander.bastools.api.proofreaders;
import org.applecommander.bastools.api.Configuration;
import org.applecommander.bastools.api.model.Line;
import org.applecommander.bastools.api.model.Program;
public class ComputeAutomaticProofreader extends LineOrientedProofReader {
public class ComputeAutomaticProofreader implements ApplesoftInputBufferProofReader {
private final Configuration config;
private final Checksum checksum = new Checksum();
public ComputeAutomaticProofreader(Configuration config) {
super(config);
this.config = config;
}
@Override
public Program visit(Program program) {
public Configuration getConfiguration() {
return config;
}
@Override
public void addProgram(Program program) {
System.out.println("Compute! Apple Automatic Proofreader, Copyright 1985");
return super.visit(program);
ApplesoftInputBufferProofReader.super.addProgram(program);
}
@Override
public Line visit(Line line) {
String text = toString(line);
int checksum = 0;
for (int i=text.length()-1; i>=0; i--) {
char ch = text.charAt(i);
public void addLine(String line) {
checksum.reset();
for (int i=line.length()-1; i>=0; i--) {
char ch = line.charAt(i);
if (ch != ' ') {
checksum = proofreader(checksum, ch|0x80);
checksum.add(ch|0x80);
}
}
System.out.printf("%02X | %s\n", checksum, text);
return line;
System.out.printf("%02X | %s\n", checksum.value(), line);
}
// For testing purposes; only valid for the line
protected int getChecksumValue() {
return checksum.value();
}
/**
@@ -43,20 +52,34 @@ public class ComputeAutomaticProofreader extends LineOrientedProofReader {
* </pre>
* Generalized algorithm: <code>cksum = (cksum << 1) + (char|$80) + carry</code>
*/
public static int proofreader(int checksum, int value) {
assert value >= 0 && value <= 0xff;
checksum <<= 1;
// Handle carry from ASL
if (checksum > 0xff) {
checksum++;
checksum &= 0xff;
public static class Checksum implements ProofReaderChecksum {
private int checksum = 0;
@Override
public void reset() {
this.checksum = 0;
}
// Handle carry from first ADC
checksum += value;
if (checksum > 0xff) {
checksum++;
checksum &= 0xff;
@Override
public void add(int value) {
assert value >= 0 && value <= 0xff;
checksum <<= 1;
// Handle carry from ASL
if (checksum > 0xff) {
checksum++;
checksum &= 0xff;
}
// Handle carry from first ADC
checksum += value;
if (checksum > 0xff) {
checksum++;
checksum &= 0xff;
}
}
@Override
public int value() {
return checksum;
}
return checksum;
}
}
@@ -1,58 +0,0 @@
package org.applecommander.bastools.api.proofreaders;
import org.applecommander.bastools.api.Configuration;
import org.applecommander.bastools.api.Visitor;
import org.applecommander.bastools.api.model.Line;
import org.applecommander.bastools.api.model.Statement;
import org.applecommander.bastools.api.model.Token;
import java.io.PrintWriter;
import java.io.StringWriter;
public abstract class LineOrientedProofReader implements Visitor {
protected final Configuration config;
protected LineOrientedProofReader(Configuration config) {
this.config = config;
}
public String toString(Line line) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
pw.printf("%d ", line.lineNumber);
boolean first = true;
for (Statement statement : line.statements) {
if (first) {
first = false;
} else {
pw.print(":");
}
for (Token token : statement.tokens) {
switch (token.type()) {
case EOL:
pw.print("<EOL>");
break;
case COMMENT:
pw.printf("REM %s", token.text());
break;
case DATA, IDENT, SYNTAX:
pw.print(token.text());
break;
case STRING:
pw.printf("\"%s\"", token.text());
break;
case KEYWORD:
pw.printf(" %s ", token.keyword().text);
break;
case DIRECTIVE:
pw.printf("%s ", token.text());
break;
case NUMBER:
pw.print(config.numberToString(token));
break;
}
}
}
return sw.toString();
}
}
@@ -1,9 +1,6 @@
package org.applecommander.bastools.api.proofreaders;
import org.applecommander.bastools.api.Configuration;
import org.applecommander.bastools.api.Visitor;
import org.applecommander.bastools.api.model.Program;
import org.applecommander.bastools.api.visitors.ByteVisitor;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
@@ -44,19 +41,21 @@ import java.util.List;
* 1710- 60 RTS
* </pre>
*/
public class MicrosparcKeyPerfect2 implements Visitor {
public class MicrosparcKeyPerfect2 implements ApplesoftTokenizedProofReader {
private final Configuration config;
private final ByteVisitor byteVisitor;
public MicrosparcKeyPerfect2(Configuration config) {
this.config = config;
this.byteVisitor = new ByteVisitor(config);
}
@Override
public Program visit(Program program) {
byteVisitor.visit(program);
ByteBuffer code = ByteBuffer.wrap(byteVisitor.getBytes());
public Configuration getConfiguration() {
return config;
}
@Override
public void addBytes(byte... tokenizedProgram) {
ByteBuffer code = ByteBuffer.wrap(tokenizedProgram);
code.order(ByteOrder.LITTLE_ENDIAN);
System.out.println("Line# - Line# CODE-2.0");
@@ -96,8 +95,6 @@ public class MicrosparcKeyPerfect2 implements Visitor {
printLines(lines, lineChecksum);
}
printLine("PROGRAM TOTAL", programChecksum);
return program;
}
public void printLines(List<Integer> lines, int lineChecksum) {
@@ -1,9 +1,6 @@
package org.applecommander.bastools.api.proofreaders;
import org.applecommander.bastools.api.Configuration;
import org.applecommander.bastools.api.Visitor;
import org.applecommander.bastools.api.model.Program;
import org.applecommander.bastools.api.visitors.ByteVisitor;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
@@ -85,19 +82,21 @@ import java.util.List;
* </pre>
* ('@' replaced with ']' since Javadoc uses '@' for other purposes.)
*/
public class MicrosparcKeyPerfect4 implements Visitor {
public class MicrosparcKeyPerfect4 implements ApplesoftTokenizedProofReader {
private final Configuration config;
private final ByteVisitor byteVisitor;
public MicrosparcKeyPerfect4(Configuration config) {
this.config = config;
this.byteVisitor = new ByteVisitor(config);
}
@Override
public Program visit(Program program) {
byteVisitor.visit(program);
ByteBuffer code = ByteBuffer.wrap(byteVisitor.getBytes());
public Configuration getConfiguration() {
return config;
}
@Override
public void addBytes(byte... tokenizedProgram) {
ByteBuffer code = ByteBuffer.wrap(tokenizedProgram);
code.order(ByteOrder.LITTLE_ENDIAN);
System.out.println("Line# - Line# CODE-4.0");
@@ -112,9 +111,9 @@ public class MicrosparcKeyPerfect4 implements Visitor {
if (ptr == 0) break;
// Line number always gets added to checksum and total
int b1 = Byte.toUnsignedInt(code.get());
checksum.addByte(b1);
checksum.add(b1);
int b2 = Byte.toUnsignedInt(code.get());
checksum.addByte(b2);
checksum.add(b2);
programChecksum+= 2;
lines.add(b2 << 8 | b1);
// Process tokenized line...
@@ -123,7 +122,7 @@ public class MicrosparcKeyPerfect4 implements Visitor {
ch = Byte.toUnsignedInt(code.get());
if (ch > 0x20 || ch == 0x04) {
programChecksum++;
checksum.addByte(ch);
checksum.add(ch);
}
} while (ch > 0);
@@ -137,15 +136,13 @@ public class MicrosparcKeyPerfect4 implements Visitor {
printLines(lines, checksum);
}
printLine("PROGRAM TOTAL", programChecksum);
return program;
}
public void printLines(List<Integer> lines, Checksum checksum) {
int firstLine = lines.getFirst();
int lastLine = lines.getLast();
String text = String.format("%5d - %5d", firstLine, lastLine);
printLine(text, checksum.getChecksum());
printLine(text, checksum.value());
}
public static void printLine(String text, int checksum) {
@@ -161,20 +158,23 @@ public class MicrosparcKeyPerfect4 implements Visitor {
System.out.println();
}
public static class Checksum {
public static class Checksum implements ProofReaderChecksum {
private int lineChecksum = 0; // low byte with has EOR, ROL, ADC
private int lineCounter = 0; // high 2 bytes that just ADC on carry
public int getChecksum() {
@Override
public int value() {
return lineCounter << 8 | lineChecksum;
}
@Override
public void reset() {
lineChecksum = 0;
lineCounter = 0;
}
public void addByte(int code) {
@Override
public void add(int code) {
int acc = lineChecksum ^ code;
acc <<= 1;
if (acc > 0xff) {
@@ -1,11 +1,7 @@
package org.applecommander.bastools.api.proofreaders;
import org.applecommander.bastools.api.Configuration;
import org.applecommander.bastools.api.Visitor;
import org.applecommander.bastools.api.model.Program;
import org.applecommander.bastools.api.visitors.ByteVisitor;
import javax.swing.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
@@ -110,19 +106,21 @@ import java.util.List;
* </pre>
* ('@' replaced with ']' since Javadoc uses '@' for other purposes.)
*/
public class MicrosparcKeyPerfect5 implements Visitor {
public class MicrosparcKeyPerfect5 implements ApplesoftTokenizedProofReader {
private final Configuration config;
private final ByteVisitor byteVisitor;
public MicrosparcKeyPerfect5(Configuration config) {
this.config = config;
this.byteVisitor = new ByteVisitor(config);
}
@Override
public Program visit(Program program) {
byteVisitor.visit(program);
ByteBuffer code = ByteBuffer.wrap(byteVisitor.getBytes());
public Configuration getConfiguration() {
return config;
}
@Override
public void addBytes(byte... tokenizedProgram) {
ByteBuffer code = ByteBuffer.wrap(tokenizedProgram);
code.order(ByteOrder.LITTLE_ENDIAN);
System.out.println("Line# - Line# CODE-5.0");
@@ -136,11 +134,11 @@ public class MicrosparcKeyPerfect5 implements Visitor {
if (ptr == 0) break;
// Line number always gets added to checksum and total
int b1 = Byte.toUnsignedInt(code.get());
lineChecksum.addByte(b1);
pgmChecksum.addByte(b1);
lineChecksum.add(b1);
pgmChecksum.add(b1);
int b2 = Byte.toUnsignedInt(code.get());
lineChecksum.addByte(b2);
pgmChecksum.addByte(b2);
lineChecksum.add(b2);
pgmChecksum.add(b2);
lines.add(b2 << 8 | b1);
// Process tokenized line...
boolean inComment = false;
@@ -149,8 +147,8 @@ public class MicrosparcKeyPerfect5 implements Visitor {
ch = Byte.toUnsignedInt(code.get());
if (ch == 0xb2) {
inComment = true;
lineChecksum.addByte(ch);
pgmChecksum.addByte(ch);
lineChecksum.add(ch);
pgmChecksum.add(ch);
}
else if (inComment) {
// do not add to checksum
@@ -159,8 +157,8 @@ public class MicrosparcKeyPerfect5 implements Visitor {
if (ch >= 0x60 && ch < 0x80) {
ch -= 0x20; // Force characters to uppercase
}
lineChecksum.addByte(ch);
pgmChecksum.addByte(ch);
lineChecksum.add(ch);
pgmChecksum.add(ch);
}
} while (ch > 0);
@@ -174,8 +172,6 @@ public class MicrosparcKeyPerfect5 implements Visitor {
printLines(lines, lineChecksum);
}
printLine("PROGRAM TOTAL", pgmChecksum);
return program;
}
public void printLines(List<Integer> lines, Checksum checksum) {
@@ -186,24 +182,27 @@ public class MicrosparcKeyPerfect5 implements Visitor {
}
public static void printLine(String text, Checksum checksum) {
System.out.printf("%-13.13s %08X\n", text, checksum.getChecksum());
System.out.printf("%-13.13s %08X\n", text, checksum.value());
}
public static class Checksum {
public static class Checksum implements ProofReaderChecksum {
/**
* Checksum is 4 bytes, long is 8 so we don't get surprised by negativity.
*/
private long checksum = 0xffffffffL;
public int getChecksum() {
@Override
public int value() {
return (int)checksum;
}
@Override
public void reset() {
checksum = 0xffffffffL;
}
public void addByte(int code) {
@Override
public void add(int code) {
assert code >= 0 && code <= 0xff;
// Combines the CHKSUM_V5_1 effort
int idx = (int)(checksum >> 24 & 0xff) ^ code;
@@ -1,70 +1,64 @@
package org.applecommander.bastools.api.proofreaders;
import org.applecommander.bastools.api.Configuration;
import org.applecommander.bastools.api.Visitor;
import org.applecommander.bastools.api.model.Program;
import org.applecommander.bastools.api.visitors.ByteVisitor;
public class NibbleAppleChecker implements Visitor {
public class NibbleAppleChecker implements ApplesoftTokenizedProofReader {
private final Configuration config;
private final ByteVisitor byteVisitor;
private int length;
private final Checksum checksum = new Checksum();
public NibbleAppleChecker(Configuration config) {
this.config = config;
this.byteVisitor = new ByteVisitor(config);
}
@Override
public Program visit(Program program) {
byteVisitor.visit(program);
byte[] code = byteVisitor.getBytes();
Checksum checksum = new Checksum();
checksum.compute(code, config.startAddress);
public Configuration getConfiguration() {
return config;
}
@Override
public void addProgram(Program program) {
ApplesoftTokenizedProofReader.super.addProgram(program);
System.out.printf("On: %s\n", config.sourceFile.getName());
System.out.println("Type: A");
System.out.println();
System.out.printf("Length: %04X\n", checksum.getLength());
System.out.printf("Checksum: %02X\n", checksum.getChecksum());
return program;
System.out.printf("Length: %04X\n", length);
System.out.printf("Checksum: %02X\n", checksum.value());
}
public static class Checksum {
private int length;
private int checksum;
public int getLength() {
return length;
@Override
public void addBytes(byte... tokenizedProgram) {
// Length is less 2 for Applesoft
length = tokenizedProgram.length+1 - 2;
// Zero out the next line links
int nextAddr = config.startAddress;
while (nextAddr >= 0 && nextAddr < tokenizedProgram.length) {
nextAddr = (tokenizedProgram[nextAddr+1]<<8 | tokenizedProgram[nextAddr]) - config.startAddress;
tokenizedProgram[nextAddr] = 0;
tokenizedProgram[nextAddr+1] = 0;
}
public int getChecksum() {
return checksum;
}
public void compute(byte[] code, int startAddress) {
// Length is less 2 for Applesoft
length = code.length+1 - 2;
// Zero out the next line links
int nextAddr = startAddress;
while (nextAddr >= 0 && nextAddr < code.length) {
nextAddr = (code[nextAddr+1]<<8 | code[nextAddr]) - startAddress;
code[nextAddr] = 0;
code[nextAddr+1] = 0;
}
// Compute checksum
this.checksum = 0;
for (int i=0; i<code.length; i++) {
int acc = Byte.toUnsignedInt(code[i]);
if (acc >= 0x21 || acc == 0x04) {
this.checksum = checksum(this.checksum, acc);
}
else {
// We skip _and_ reduce the length by 1
this.length--;
}
// Compute checksum
checksum.reset();
for (byte b : tokenizedProgram) {
int acc = Byte.toUnsignedInt(b);
if (acc >= 0x21 || acc == 0x04) {
checksum.add(acc);
} else {
// We skip _and_ reduce the length by 1
this.length--;
}
}
}
public int getLength() {
return length;
}
public int getChecksumValue() {
return checksum.value();
}
/**
* Compute a single iteration of the checksum. Broken out for testing purposes.
* <pre>
@@ -76,23 +70,38 @@ public class NibbleAppleChecker implements Visitor {
* STA CKCD
* </pre>
*/
public static int checksum(int checksum, int acc) {
// EOR CKCD
acc ^= checksum;
// ROL A
acc <<= 1;
// ADC CKCD
if (acc >= 0xff) {
checksum++;
acc &= 0xff;
public static class Checksum implements ProofReaderChecksum {
private int checksum;
@Override
public void reset() {
this.checksum = 0;
}
acc += checksum;
// ADC #0
if (acc > 0xff) {
acc++;
acc &= 0xff;
@Override
public void add(int acc) {
// EOR CKCD
acc ^= checksum;
// ROL A
acc <<= 1;
// ADC CKCD
if (acc >= 0xff) {
checksum++;
acc &= 0xff;
}
acc += checksum;
// ADC #0
if (acc > 0xff) {
acc++;
acc &= 0xff;
}
// STA CKCD
checksum = acc;
}
@Override
public int value() {
return this.checksum;
}
// STA CKCD
return acc;
}
}
@@ -1,76 +1,72 @@
package org.applecommander.bastools.api.proofreaders;
import org.applecommander.bastools.api.Configuration;
import org.applecommander.bastools.api.model.Line;
import org.applecommander.bastools.api.model.Program;
import org.applecommander.bastools.api.model.Statement;
import org.applecommander.bastools.api.model.Token;
public class NibbleCheckit extends LineOrientedProofReader {
private int totalChecksum;
private int lineChecksum;
public class NibbleCheckit implements ApplesoftInputBufferProofReader {
private final Configuration config;
private final Checksum totalChecksum = new Checksum();
private final Checksum lineChecksum = new Checksum();
public NibbleCheckit(Configuration config) {
super(config);
this.config = config;
}
@Override
public Program visit(Program program) {
public Configuration getConfiguration() {
return config;
}
/** {@inheritDoc} */
@Override
public void addProgramText(String code) {
System.out.println("Nibble Checkit, Copyright 1988, Microsparc Inc.");
try {
return super.visit(program);
} finally {
System.out.printf("TOTAL: %02X%02X\n", totalChecksum & 0xff, totalChecksum >> 8);
}
ApplesoftInputBufferProofReader.super.addProgramText(code);
System.out.printf("TOTAL: %02X%02X\n", totalChecksum.checksum & 0xff, totalChecksum.checksum >> 8);
}
/** {@inheritDoc} */
@Override
public Line visit(Line line) {
public void addLine(String line) {
// Calculate and output line value
lineChecksum = 0;
for (char ch : Integer.toString(line.lineNumber).toCharArray()) {
lineChecksum = checkit(lineChecksum, ch|0x80);
}
boolean first = true;
for (Statement statement : line.statements) {
if (!first) {
// We need to synthesize the colons
lineChecksum = checkit(lineChecksum, ':'|0x80);
}
super.visit(statement);
first = false;
}
int cs = ( (lineChecksum & 0xff) - (lineChecksum >> 8) ) & 0xff;
System.out.printf("%02X | %s\n", cs, toString(line));
lineChecksum.reset();
// Update program values
totalChecksum = checkit(totalChecksum, line.lineNumber & 0xff);
totalChecksum = checkit(totalChecksum, line.lineNumber >> 8);
return line;
}
// The ? => PRINT replacement always occurs, including in strings!
line = line.replace("?", "PRINT");
@Override
public Token visit(Token token) {
String value = switch (token.type()) {
case EOL, DIRECTIVE -> "";
case DATA, SYNTAX, IDENT -> token.text();
case COMMENT -> "REM";
case KEYWORD -> token.keyword().text;
case NUMBER -> {
if (token.text() != null) {
yield token.text();
}
yield config.numberToString(token);
}
case STRING -> String.format("\"%s\"", token.text());
};
boolean inQuote = false;
for (char ch : value.toCharArray()) {
StringBuilder remLettersSeen = new StringBuilder();
for (char ch : line.toCharArray()) {
if (ch == '"') inQuote = !inQuote;
if (!inQuote && ch == ' ') continue;
lineChecksum = checkit(lineChecksum, ch|0x80);
lineChecksum.add(ch|0x80);
// we only allow R E M from a comment; skip rest of the comment
remLettersSeen.append(ch);
if ("REM".contentEquals(remLettersSeen)) {
break;
}
else if ("R".contentEquals(remLettersSeen) || "RE".contentEquals(remLettersSeen)) {
// Keep them, this may be a comment
}
else {
remLettersSeen = new StringBuilder();
}
}
return token;
int cs = ( (lineChecksum.checksum & 0xff) - (lineChecksum.checksum >> 8) ) & 0xff;
System.out.printf("%02X | %s\n", cs, line);
// Update program values
int lineNumber = Integer.parseInt(line.split(" ")[0].trim());
totalChecksum.add(lineNumber & 0xff);
totalChecksum.add(lineNumber >> 8);
}
public int getLineChecksumValue() {
return lineChecksum.value();
}
public int getTotalChecksumValue() {
return totalChecksum.value();
}
/**
@@ -101,17 +97,29 @@ public class NibbleCheckit extends LineOrientedProofReader {
* XOR. The result is the middle two bytes where the checksum resides. (Note that the
* <code>0x1021</code> was shifted by a byte as well.)
*/
public static int checkit(int checksum, int value) {
assert value >= 0 && value <= 0xff;
int work = (checksum << 8) | value;
for (int i=0; i<8; i++) {
work <<= 1;
// Note: If we run into negative issues somehow, this could also be "((work & 0xff000000) != 0)".
if (work > 0xffffff) {
work &= 0xffffff;
work ^= 0x102100;
public static class Checksum implements ProofReaderChecksum {
private int checksum = 0;
@Override
public void reset() {
this.checksum = 0;
}
@Override
public void add(int value) {
assert value >= 0 && value <= 0xff;
int work = (checksum << 8) | value;
for (int i=0; i<8; i++) {
work <<= 1;
// Note: If we run into negative issues somehow, this could also be "((work & 0xff000000) != 0)".
if (work > 0xffffff) {
work &= 0xffffff;
work ^= 0x102100;
}
}
checksum = work >> 8;
}
@Override
public int value() {
return checksum;
}
return work >> 8;
}
}
@@ -0,0 +1,10 @@
package org.applecommander.bastools.api.proofreaders;
public interface ProofReaderChecksum {
/** Reset the checksum back to initial state. */
void reset();
/** Add a value (typically a byte) into the checksum. */
void add(int value);
/** Get the current value of the checksum. */
int value();
}
@@ -12,7 +12,7 @@ import java.util.List;
import java.util.Objects;
import java.util.Queue;
import static org.junit.Assert.*;
import static org.junit.Assert.fail;
public class ClassicTokenReaderTest {
@Test
@@ -357,6 +357,23 @@ public class ClassicTokenReaderTest {
.end());
}
@Test
public void testQuestionMarkAsPrint49() throws IOException {
testCode("""
100 PRINT "HELLO WORLD"
110 PRINT "HELLO WORLD?"
""",
Tokens.builder()
.number("100")
.keyword(ApplesoftKeyword.PRINT)
.data("\"HELLO WORLD\"")
.nextLine()
.number("110")
.keyword(ApplesoftKeyword.PRINT)
.data("\"HELLO WORLD?\"")
.end());
}
public void testCode(String code, Token... expectedTokens) throws IOException {
final Queue<Token> actualTokens = ClassicTokenReader.tokenize(new StringReader(code));
final Token[] actualArray = actualTokens.toArray(new Token[0]);
@@ -1,7 +1,10 @@
package org.applecommander.bastools.api.proofreaders;
import org.applecommander.bastools.api.Configuration;
import org.junit.Test;
import java.io.File;
import static org.junit.Assert.assertEquals;
/**
@@ -17,13 +20,12 @@ public class ComputeAutomaticProofreaderTest {
}
protected int performLineCalc(String text) {
int checksum = 0;
for (int i=text.length()-1; i>=0; i--) {
char ch = text.charAt(i);
if (ch != ' ') {
checksum = ComputeAutomaticProofreader.proofreader(checksum, ch|0x80);
}
}
return checksum;
Configuration config = Configuration.builder()
.preserveNumbers(true)
.sourceFile(new File("test.bas"))
.build();
ComputeAutomaticProofreader proofreader = new ComputeAutomaticProofreader(config);
proofreader.addLine(text);
return proofreader.getChecksumValue();
}
}
@@ -16,8 +16,8 @@ public class MicrosparcKeyPerfect4Test {
};
Checksum checksum = new Checksum();
for (int b : code) {
checksum.addByte(b);
checksum.add(b);
}
assertEquals(0x783, checksum.getChecksum());
assertEquals(0x783, checksum.value());
}
}
@@ -16,9 +16,9 @@ public class MicrosparcKeyPerfect5Test {
};
Checksum checksum = new Checksum();
for (int b : code) {
checksum.addByte(b);
checksum.add(b);
}
assertEquals(0xC397EF76, checksum.getChecksum());
assertEquals(0xC397EF76, checksum.value());
}
@Test
@@ -28,8 +28,8 @@ public class MicrosparcKeyPerfect5Test {
};
Checksum checksum = new Checksum();
for (int b : code) {
checksum.addByte(b);
checksum.add(b);
}
assertEquals(0x9b714628, checksum.getChecksum());
assertEquals(0x9b714628, checksum.value());
}
}
@@ -1,25 +1,36 @@
package org.applecommander.bastools.api.proofreaders;
import org.applecommander.bastools.api.Configuration;
import org.junit.Test;
import java.io.File;
import static org.junit.Assert.assertEquals;
public class NibbleAppleCheckerTest {
@Test
public void testChecksum() {
// Based on 6502 assembly run
assertEquals(0x13, NibbleAppleChecker.checksum(0x0, 0x89)); // TEXT
assertEquals(0x65, NibbleAppleChecker.checksum(0x13, 0x3a)); // :
assertEquals(0x4b, NibbleAppleChecker.checksum(0x65, 0x97)); // HOME
// Based on 6502 assembly run - checks step-by-step values
NibbleAppleChecker.Checksum checksum = new NibbleAppleChecker.Checksum();
checksum.add(0x89); // TEXT
assertEquals(0x13, checksum.value());
checksum.add(0x3a); // :
assertEquals(0x65, checksum.value());
checksum.add(0x97); // HOME
assertEquals(0x4b, checksum.value());
}
@Test
public void testCode() {
NibbleAppleChecker.Checksum cksum = new NibbleAppleChecker.Checksum();
Configuration config = Configuration.builder()
.startAddress(0xb01)
.sourceFile(new File("test.bas"))
.build();
NibbleAppleChecker checker = new NibbleAppleChecker(config);
// 10 TEXT:HOME as a sample...
byte[] code = { 0x09, 0x0b, 0x0a, (byte)0x89, 0x3a, (byte)0x97, 0x00, 0x00, 0x00, 0x00, 0x00 };
cksum.compute(code, 0xb01);
//assertEquals(0x2, cksum.getLength());
assertEquals(0x4b, cksum.getChecksum());
checker.addBytes(code);
assertEquals(0x2, checker.getLength());
assertEquals(0x4b, checker.getChecksumValue());
}
}
@@ -1,7 +1,10 @@
package org.applecommander.bastools.api.proofreaders;
import org.applecommander.bastools.api.Configuration;
import org.junit.Test;
import java.io.File;
import static org.junit.Assert.assertEquals;
/**
@@ -16,6 +19,12 @@ public class NibbleCheckitTest {
assertEquals(0x91, performLineCalc("30 END"));
}
@Test
public void testQuestionMark49() {
assertEquals(0xf6, performLineCalc("110 PRINT \"HELLO WORLD?\""));
assertEquals(0xe7, performLineCalc("10 PRINT \"?\""));
}
@Test
public void testProgramChecksum() {
// Note: The value displayed is 1CB9, but the code prints low byte first...
@@ -23,21 +32,25 @@ public class NibbleCheckitTest {
}
protected int performLineCalc(String text) {
int checksum = 0;
for (char ch : text.toCharArray()) {
if (ch != ' ') {
checksum = NibbleCheckit.checkit(checksum, ch | 0x80);
}
}
return ( (checksum & 0xff) - (checksum >> 8) ) & 0xff;
Configuration config = Configuration.builder()
.preserveNumbers(true)
.sourceFile(new File("test.bas"))
.build();
NibbleCheckit proofreader = new NibbleCheckit(config);
proofreader.addLine(text);
return ( (proofreader.getLineChecksumValue() & 0xff) - (proofreader.getLineChecksumValue() >> 8) ) & 0xff;
}
protected int performProgramCalc(int... lineNumbers) {
int checksum = 0;
Configuration config = Configuration.builder()
.preserveNumbers(true)
.sourceFile(new File("test.bas"))
.build();
NibbleCheckit proofreader = new NibbleCheckit(config);
for (int lineNumber : lineNumbers) {
checksum = NibbleCheckit.checkit(checksum, lineNumber & 0xff);
checksum = NibbleCheckit.checkit(checksum, lineNumber >> 8);
// We ignore the line, but line also computes the program checksum
proofreader.addLine(Integer.toString(lineNumber));
}
return checksum;
return proofreader.getTotalChecksumValue();
}
}
+9 -8
View File
@@ -4,18 +4,19 @@
Some summary notes and observations on the Apple II code checking tools.
| | Includes REM contents | Includes spaces | Control Chars? | Case-sensitive? | Operation | Grouping(s) | Size | Program Total |
|--------------------------------|:----------------------|:----------------|:---------------|:----------------|:-------------|:--------------------------------|:------------------------------------|:-------------------------------------|
| Compute! Automatic Proofreader | Yes | No | CTRL+D | Yes | Input Buffer | Line | 1 byte | No |
| Nibble Checkit | No | In strings | In Strings | Yes | Input Buffer | Line, Program | 1 byte (line)<br/>2 bytes (program) | Yes, on line numbers |
| Nibble Apple Checker | Yes | No | CTRL+D | Yes | Program | Program | 1 byte (2 bytes length) | Yes, checksum and length calculation |
| MicroSPARC Key Perfect 2 | Yes | Yes | All | Yes | Program | Groups of 10 lines,<br/>Program | Max 3 bytes | Yes, byte count |
| MicroSPARC Key Perfect 4 | Yes | No | CTRL+D | Yes | Program | Groups of 10 lines,<br/>Program | Max 3 bytes | Yes, byte count |
| MicroSPARC Key Perfect 5 | No | No | CTRL+D | No | Program | Groups of 10 lines,<br/>Program | 4 bytes | Yes |
| | Includes REM contents | Includes spaces | Control Chars? | Other? | Case-sensitive? | Operation | Grouping(s) | Checksum Size | Program Total |
|--------------------------------|:----------------------|:----------------|:---------------|--------|:----------------|:-------------|:--------------------------------|:------------------------------------|:-------------------------------------|
| Compute! Automatic Proofreader | Yes | No | CTRL+D | N/A | Yes | Input Buffer | Line | 1 byte | No |
| Nibble Checkit | No | In strings | In Strings | "?" | Yes | Input Buffer | Line, Program | 1 byte (line)<br/>2 bytes (program) | Yes, on line numbers |
| Nibble Apple Checker | Yes | No | CTRL+D | N/A | Yes | Program | Program | 1 byte (2 bytes length) | Yes, checksum and length calculation |
| MicroSPARC Key Perfect 2 | Yes | Yes | All | N/A | Yes | Program | Groups of 10 lines,<br/>Program | Max 3 bytes | Yes, byte count |
| MicroSPARC Key Perfect 4 | Yes | No | CTRL+D | N/A | Yes | Program | Groups of 10 lines,<br/>Program | Max 3 bytes | Yes, byte count |
| MicroSPARC Key Perfect 5 | No | No | CTRL+D | N/A | No | Program | Groups of 10 lines,<br/>Program | 4 bytes | Yes |
* *Includes REM contents* - most checksum algorithms includes the REM as well as the contents of the REM statement; some only include the REM itself.
* *Includes spaces* - indicates if whitespace is important.
* *Control Chars?* - indicates if any control characters are included in the checksum.
* *Other?* - Checkit replaces "?" with "PRINT" to standardize the shortcut for the `PRINT` statement. The catch is that it ignores quotes. So _any_ "?" is replaced with "PRINT" for the checksum algorithm.
* *Case-sensitive?* - is "A" and "a" handled differently?
* *Operation* - if "Input Buffer" is specified, the checksums are displayed after a line of code has been entered; otherwise, the program must be completed and saved before running checksum.
* *Groupings* - what, if any, groupings apply.
@@ -17,15 +17,6 @@
*/
package org.applecommander.bastools.tools.bt;
import java.io.*;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Queue;
import java.util.concurrent.Callable;
import java.util.function.Function;
import io.github.applecommander.applesingle.AppleSingle;
import org.applecommander.bastools.api.*;
import org.applecommander.bastools.api.model.Program;
@@ -34,11 +25,21 @@ import org.applecommander.bastools.api.model.Token.Type;
import org.applecommander.bastools.api.proofreaders.*;
import org.applecommander.bastools.api.visitors.ByteVisitor;
import picocli.CommandLine;
import picocli.CommandLine.ArgGroup;
import picocli.CommandLine.Command;
import picocli.CommandLine.Help.Visibility;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;
import picocli.CommandLine.ArgGroup;
import java.io.*;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Queue;
import java.util.concurrent.Callable;
import java.util.function.BiConsumer;
import java.util.function.Function;
import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_FOOTER;
@@ -218,7 +219,7 @@ public class Main implements Callable<Integer> {
program.accept(Visitors.variableReportVisitor());
}
if (proofReader.proofReaderFn != null) {
program.accept(proofReader.proofReaderFn.apply(config));
proofReader.proofReaderFn.accept(config, program);
}
ByteVisitor byteVisitor = Visitors.byteVisitor(config);
@@ -346,36 +347,54 @@ public class Main implements Callable<Integer> {
}
public static class ProofReaderSelection {
Function<Configuration,Visitor> proofReaderFn;
BiConsumer<Configuration,Program> proofReaderFn;
@Option(names = "--checkit", description = "Apply Nibble Checkit (ca 1988) to code")
public void selectNibbleCheckit(boolean flag) {
this.proofReaderFn = NibbleCheckit::new;
this.proofReaderFn = (c,p) -> {
NibbleCheckit proofreader = new NibbleCheckit(c);
proofreader.addProgram(p);
};
}
@Option(names = "--proofreader", description = "Apply Compute! Apple Automatic Proofreader (ca 1985) to code")
public void selectComputeProofreader(boolean flag) {
this.proofReaderFn = ComputeAutomaticProofreader::new;
this.proofReaderFn = (c,p) -> {
ComputeAutomaticProofreader proofreader = new ComputeAutomaticProofreader(c);
proofreader.addProgram(p);
};
}
@Option(names = "--apple-checker", description = "Apply Nibble Apple Checker 3.0 (ca 1982) to code")
public void selectNibbleAppleChecker(boolean flag) {
this.proofReaderFn = NibbleAppleChecker::new;
this.proofReaderFn = (c,p) -> {
NibbleAppleChecker proofreader = new NibbleAppleChecker(c);
proofreader.addProgram(p);
};
}
@Option(names = { "--key-perfect-2", "--kp2" }, description = "Apply MicroSPARC Key Perfect V2 (ca 1981) to code")
public void selectKeyPerfectV2(boolean flag) {
this.proofReaderFn = MicrosparcKeyPerfect2::new;
this.proofReaderFn = (c,p) -> {
MicrosparcKeyPerfect2 proofreader = new MicrosparcKeyPerfect2(c);
proofreader.addProgram(p);
};
}
@Option(names = { "--key-perfect-4", "--kp4" }, description = "Apply MicroSPARC Key Perfect V4 (ca 1981) to code")
public void selectKeyPerfectV4(boolean flag) {
this.proofReaderFn = MicrosparcKeyPerfect4::new;
this.proofReaderFn = (c,p) -> {
MicrosparcKeyPerfect4 proofreader = new MicrosparcKeyPerfect4(c);
proofreader.addProgram(p);
};
}
@Option(names = { "--key-perfect-5", "--kp5" }, description = "Apply MicroSPARC Key Perfect V5 (ca 1985) to code")
public void selectKeyPerfectV5(boolean flag) {
this.proofReaderFn = MicrosparcKeyPerfect5::new;
this.proofReaderFn = (c,p) -> {
MicrosparcKeyPerfect5 proofreader = new MicrosparcKeyPerfect5(c);
proofreader.addProgram(p);
};
}
}
}