mirror of
https://github.com/AppleCommander/bastools.git
synced 2024-12-21 23:29:31 +00:00
Refactored to implement visitor pattern in prettyPrint and toBytes. #7.
This commit is contained in:
parent
19f3fe56bf
commit
8c6df25d80
@ -1,13 +1,11 @@
|
||||
package com.webcodepro.applecommander.util.applesoft;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/** An AppleSoft BASIC Line representation. */
|
||||
public class Line {
|
||||
public class Line implements Consumer<Visitor> {
|
||||
public final int lineNumber;
|
||||
public final List<Statement> statements = new ArrayList<>();
|
||||
|
||||
@ -15,34 +13,8 @@ public class Line {
|
||||
this.lineNumber = lineNumber;
|
||||
}
|
||||
|
||||
public void prettyPrint(PrintStream ps) {
|
||||
boolean first = true;
|
||||
for (Statement statement : statements) {
|
||||
if (first) {
|
||||
first = false;
|
||||
ps.printf("%5d ", lineNumber);
|
||||
} else {
|
||||
ps.printf("%5s ", ":");
|
||||
}
|
||||
statement.prettyPrint(ps);
|
||||
ps.println();
|
||||
}
|
||||
}
|
||||
|
||||
public int toBytes(int address, ByteArrayOutputStream os) throws IOException {
|
||||
ByteArrayOutputStream tmp = new ByteArrayOutputStream();
|
||||
for (Statement stmt : statements) {
|
||||
if (tmp.size() > 0) tmp.write(':');
|
||||
stmt.toBytes(tmp);
|
||||
}
|
||||
|
||||
int nextAddress = address + tmp.size() + 5;
|
||||
os.write(nextAddress);
|
||||
os.write(nextAddress >> 8);
|
||||
os.write(lineNumber);
|
||||
os.write(lineNumber >> 8);
|
||||
tmp.writeTo(os);
|
||||
os.write(0x00);
|
||||
return nextAddress;
|
||||
@Override
|
||||
public void accept(Visitor t) {
|
||||
t.visit(this);
|
||||
}
|
||||
}
|
||||
|
@ -1,26 +1,15 @@
|
||||
package com.webcodepro.applecommander.util.applesoft;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/** A Program is a series of lines. */
|
||||
public class Program {
|
||||
public class Program implements Consumer<Visitor> {
|
||||
public final List<Line> lines = new ArrayList<>();
|
||||
|
||||
public void prettyPrint(PrintStream ps) {
|
||||
for (Line line : lines) {
|
||||
line.prettyPrint(ps);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] toBytes(int address) throws IOException {
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
for (Line line : lines) address = line.toBytes(address, os);
|
||||
os.write(0x00);
|
||||
os.write(0x00);
|
||||
return os.toByteArray();
|
||||
@Override
|
||||
public void accept(Visitor t) {
|
||||
t.visit(this);
|
||||
}
|
||||
}
|
||||
|
@ -1,22 +1,15 @@
|
||||
package com.webcodepro.applecommander.util.applesoft;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/** A Statement is simply a series of Tokens. */
|
||||
public class Statement {
|
||||
public class Statement implements Consumer<Visitor> {
|
||||
public final List<Token> tokens = new ArrayList<>();
|
||||
|
||||
public void prettyPrint(PrintStream ps) {
|
||||
for (Token token : tokens) {
|
||||
token.prettyPrint(ps);
|
||||
}
|
||||
}
|
||||
|
||||
public void toBytes(ByteArrayOutputStream os) throws IOException {
|
||||
for (Token t : tokens) t.toBytes(os);
|
||||
@Override
|
||||
public void accept(Visitor t) {
|
||||
t.visit(this);
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,13 @@
|
||||
package com.webcodepro.applecommander.util.applesoft;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* A Token in the classic compiler sense, in that this represents a component of the application.
|
||||
*
|
||||
* @author rob
|
||||
*/
|
||||
public class Token {
|
||||
public class Token implements Consumer<Visitor> {
|
||||
public final int line;
|
||||
public final Type type;
|
||||
public final ApplesoftKeyword keyword;
|
||||
@ -25,6 +22,10 @@ public class Token {
|
||||
this.text = text;
|
||||
}
|
||||
@Override
|
||||
public void accept(Visitor t) {
|
||||
t.visit(this);
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
switch (type) {
|
||||
case EOL:
|
||||
@ -38,72 +39,6 @@ public class Token {
|
||||
}
|
||||
}
|
||||
|
||||
public void prettyPrint(PrintStream ps) {
|
||||
switch (type) {
|
||||
case EOL:
|
||||
ps.print("<EOL>");
|
||||
break;
|
||||
case COMMENT:
|
||||
ps.printf(" REM %s", text);
|
||||
break;
|
||||
case STRING:
|
||||
ps.printf("\"%s\"", text);
|
||||
break;
|
||||
case KEYWORD:
|
||||
ps.printf(" %s ", keyword.text);
|
||||
break;
|
||||
case IDENT:
|
||||
case SYNTAX:
|
||||
ps.print(text);
|
||||
break;
|
||||
case NUMBER:
|
||||
if (Math.rint(number) == number) {
|
||||
ps.print(number.intValue());
|
||||
} else {
|
||||
ps.print(number);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void toBytes(ByteArrayOutputStream os) throws IOException {
|
||||
switch (type) {
|
||||
case COMMENT:
|
||||
os.write(ApplesoftKeyword.REM.code);
|
||||
os.write(text.getBytes());
|
||||
break;
|
||||
case EOL:
|
||||
os.write(0x00);
|
||||
break;
|
||||
case IDENT:
|
||||
os.write(text.getBytes());
|
||||
break;
|
||||
case KEYWORD:
|
||||
os.write(keyword.code);
|
||||
break;
|
||||
case NUMBER:
|
||||
if (Math.rint(number) == number) {
|
||||
os.write(Integer.toString(number.intValue()).getBytes());
|
||||
} else {
|
||||
os.write(Double.toString(number).getBytes());
|
||||
}
|
||||
break;
|
||||
case STRING:
|
||||
os.write('"');
|
||||
os.write(text.getBytes());
|
||||
os.write('"');
|
||||
break;
|
||||
case SYNTAX:
|
||||
Optional<ApplesoftKeyword> opt = ApplesoftKeyword.find(text);
|
||||
if (opt.isPresent()) {
|
||||
os.write(opt.get().code);
|
||||
} else {
|
||||
os.write(text.getBytes());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static Token eol(int line) {
|
||||
return new Token(line, Type.EOL, null, null, null);
|
||||
}
|
||||
|
@ -0,0 +1,21 @@
|
||||
package com.webcodepro.applecommander.util.applesoft;
|
||||
|
||||
/**
|
||||
* The Visitor interface allows some flexibility in what can be done with the
|
||||
* AppleSoft BASIC program code.
|
||||
*
|
||||
* @author rob
|
||||
* @see Visitors
|
||||
*/
|
||||
public interface Visitor {
|
||||
default public void visit(Program program) {
|
||||
program.lines.forEach(l -> l.accept(this));
|
||||
}
|
||||
default public void visit(Line line) {
|
||||
line.statements.forEach(s -> s.accept(this));
|
||||
}
|
||||
default public void visit(Statement statement) {
|
||||
statement.tokens.forEach(t -> t.accept(this));
|
||||
}
|
||||
public void visit(Token token);
|
||||
}
|
@ -0,0 +1,263 @@
|
||||
package com.webcodepro.applecommander.util.applesoft;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Stack;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* This class presents all of the common Visitor implementations via builder patterns.
|
||||
* The number is currently small enough that all the builders and visitors are defined
|
||||
* in this one class.
|
||||
*
|
||||
* @author rob
|
||||
*/
|
||||
public class Visitors {
|
||||
public static PrintBuilder printBuilder() {
|
||||
return new PrintBuilder();
|
||||
}
|
||||
public static class PrintBuilder {
|
||||
private PrintStream printStream = System.out;
|
||||
private Function<PrintBuilder,Visitor> creator = PrintVisitor::new;
|
||||
|
||||
public PrintBuilder printStream(PrintStream printStream) {
|
||||
Objects.requireNonNull(printStream);
|
||||
this.printStream = printStream;
|
||||
return this;
|
||||
}
|
||||
public PrintBuilder prettyPrint(boolean flag) {
|
||||
creator = flag ? PrettyPrintVisitor::new : PrintVisitor::new;
|
||||
return this;
|
||||
}
|
||||
public PrintBuilder prettyPrint() {
|
||||
creator = PrettyPrintVisitor::new;
|
||||
return this;
|
||||
}
|
||||
public PrintBuilder print() {
|
||||
creator = PrintVisitor::new;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Visitor build() {
|
||||
return creator.apply(this);
|
||||
}
|
||||
}
|
||||
|
||||
public static ByteVisitor byteVisitor(int address) {
|
||||
return new ByteVisitor(address);
|
||||
}
|
||||
|
||||
private static class PrettyPrintVisitor implements Visitor {
|
||||
private PrintStream printStream;
|
||||
|
||||
private PrettyPrintVisitor(PrintBuilder builder) {
|
||||
this.printStream = builder.printStream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(Line line) {
|
||||
boolean first = true;
|
||||
for (Statement statement : line.statements) {
|
||||
if (first) {
|
||||
first = false;
|
||||
printStream.printf("%5d ", line.lineNumber);
|
||||
} else {
|
||||
printStream.printf("%5s ", ":");
|
||||
}
|
||||
statement.accept(this);
|
||||
printStream.println();
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void visit(Token token) {
|
||||
switch (token.type) {
|
||||
case EOL:
|
||||
printStream.print("<EOL>");
|
||||
break;
|
||||
case COMMENT:
|
||||
printStream.printf(" REM %s", token.text);
|
||||
break;
|
||||
case STRING:
|
||||
printStream.printf("\"%s\"", token.text);
|
||||
break;
|
||||
case KEYWORD:
|
||||
printStream.printf(" %s ", token.keyword.text);
|
||||
break;
|
||||
case IDENT:
|
||||
case SYNTAX:
|
||||
printStream.print(token.text);
|
||||
break;
|
||||
case NUMBER:
|
||||
if (Math.rint(token.number) == token.number) {
|
||||
printStream.print(token.number.intValue());
|
||||
} else {
|
||||
printStream.print(token.number);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class PrintVisitor implements Visitor {
|
||||
private PrintStream printStream;
|
||||
|
||||
private PrintVisitor(PrintBuilder builder) {
|
||||
this.printStream = builder.printStream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(Line line) {
|
||||
printStream.printf("%d ", line.lineNumber);
|
||||
boolean first = true;
|
||||
for (Statement statement : line.statements) {
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
printStream.printf(":");
|
||||
}
|
||||
statement.accept(this);
|
||||
}
|
||||
printStream.println();
|
||||
}
|
||||
@Override
|
||||
public void visit(Token token) {
|
||||
switch (token.type) {
|
||||
case EOL:
|
||||
printStream.print("<EOL>");
|
||||
break;
|
||||
case COMMENT:
|
||||
printStream.printf("REM %s", token.text);
|
||||
break;
|
||||
case STRING:
|
||||
printStream.printf("\"%s\"", token.text);
|
||||
break;
|
||||
case KEYWORD:
|
||||
printStream.printf(" %s ", token.keyword.text);
|
||||
break;
|
||||
case IDENT:
|
||||
case SYNTAX:
|
||||
printStream.print(token.text);
|
||||
break;
|
||||
case NUMBER:
|
||||
if (Math.rint(token.number) == token.number) {
|
||||
printStream.print(token.number.intValue());
|
||||
} else {
|
||||
printStream.print(token.number);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class ByteVisitor implements Visitor {
|
||||
private Stack<ByteArrayOutputStream> stack;
|
||||
private int address;
|
||||
|
||||
private ByteVisitor(int address) {
|
||||
this.address = address;
|
||||
this.stack = new Stack<>();
|
||||
}
|
||||
|
||||
/** A convenience method to invoke {@link Program#accept(Visitor)} and {@link #getBytes()}. */
|
||||
public byte[] dump(Program program) {
|
||||
program.accept(this);
|
||||
return getBytes();
|
||||
}
|
||||
|
||||
public byte[] getBytes() {
|
||||
if (stack.size() != 1) {
|
||||
throw new RuntimeException("Error in processing internal BASIC model!");
|
||||
}
|
||||
return stack.peek().toByteArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(Program program) {
|
||||
if (stack.size() != 0) {
|
||||
throw new RuntimeException("Please do not reuse this ByteVisitor as that is an unsafe operation.");
|
||||
}
|
||||
stack.push(new ByteArrayOutputStream());
|
||||
program.lines.forEach(line -> line.accept(this));
|
||||
ByteArrayOutputStream os = stack.peek();
|
||||
os.write(0x00);
|
||||
os.write(0x00);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(Line line) {
|
||||
try {
|
||||
stack.push(new ByteArrayOutputStream());
|
||||
boolean first = true;
|
||||
for (Statement statement : line.statements) {
|
||||
if (!first) {
|
||||
first = false;
|
||||
stack.peek().write(':');
|
||||
}
|
||||
statement.accept(this);
|
||||
}
|
||||
|
||||
byte[] content = stack.pop().toByteArray();
|
||||
int nextAddress = address + content.length + 5;
|
||||
ByteArrayOutputStream os = stack.peek();
|
||||
os.write(nextAddress);
|
||||
os.write(nextAddress >> 8);
|
||||
os.write(line.lineNumber);
|
||||
os.write(line.lineNumber >> 8);
|
||||
os.write(content);
|
||||
os.write(0x00);
|
||||
this.address = nextAddress;
|
||||
} catch (IOException ex) {
|
||||
// Hiding the IOException as ByteArrayOutputStream does not throw it
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(Token token) {
|
||||
try {
|
||||
ByteArrayOutputStream os = stack.peek();
|
||||
switch (token.type) {
|
||||
case COMMENT:
|
||||
os.write(ApplesoftKeyword.REM.code);
|
||||
os.write(token.text.getBytes());
|
||||
break;
|
||||
case EOL:
|
||||
os.write(0x00);
|
||||
break;
|
||||
case IDENT:
|
||||
os.write(token.text.getBytes());
|
||||
break;
|
||||
case KEYWORD:
|
||||
os.write(token.keyword.code);
|
||||
break;
|
||||
case NUMBER:
|
||||
if (Math.rint(token.number) == token.number) {
|
||||
os.write(Integer.toString(token.number.intValue()).getBytes());
|
||||
} else {
|
||||
os.write(Double.toString(token.number).getBytes());
|
||||
}
|
||||
break;
|
||||
case STRING:
|
||||
os.write('"');
|
||||
os.write(token.text.getBytes());
|
||||
os.write('"');
|
||||
break;
|
||||
case SYNTAX:
|
||||
Optional<ApplesoftKeyword> opt = ApplesoftKeyword.find(token.text);
|
||||
if (opt.isPresent()) {
|
||||
os.write(opt.get().code);
|
||||
} else {
|
||||
os.write(token.text.getBytes());
|
||||
}
|
||||
break;
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
// Hiding the IOException as ByteArrayOutputStream does not throw it
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -11,6 +11,7 @@ import com.webcodepro.applecommander.util.applesoft.Parser;
|
||||
import com.webcodepro.applecommander.util.applesoft.Program;
|
||||
import com.webcodepro.applecommander.util.applesoft.Token;
|
||||
import com.webcodepro.applecommander.util.applesoft.TokenReader;
|
||||
import com.webcodepro.applecommander.util.applesoft.Visitors;
|
||||
|
||||
import picocli.CommandLine;
|
||||
import picocli.CommandLine.Command;
|
||||
@ -43,6 +44,9 @@ public class Main implements Callable<Void> {
|
||||
@Option(names = "--pretty", description = "Pretty print structure as bastokenizer understands it.")
|
||||
private boolean prettyPrint;
|
||||
|
||||
@Option(names = "--list", description = "List structure as bastokenizer understands it.")
|
||||
private boolean listPrint;
|
||||
|
||||
@Option(names = "--tokens", description = "Dump token list to stdout for debugging.")
|
||||
private boolean showTokens;
|
||||
|
||||
@ -64,10 +68,10 @@ public class Main implements Callable<Void> {
|
||||
|
||||
/** A basic test to ensure parameters are somewhat sane. */
|
||||
public boolean checkParameters() {
|
||||
if (pipeOutput && (hexFormat || copyFormat || prettyPrint || showTokens)) {
|
||||
if (pipeOutput && (hexFormat || copyFormat || prettyPrint || listPrint || showTokens)) {
|
||||
System.err.println("The pipe option blocks any other stdout options.");
|
||||
return false;
|
||||
} else if (!(pipeOutput || hexFormat || copyFormat || prettyPrint || showTokens || outputFile != null)) {
|
||||
} else if (!(pipeOutput || hexFormat || copyFormat || prettyPrint || listPrint || showTokens || outputFile != null)) {
|
||||
System.err.println("What do you want to do?");
|
||||
return false;
|
||||
}
|
||||
@ -82,11 +86,11 @@ public class Main implements Callable<Void> {
|
||||
}
|
||||
Parser parser = new Parser(tokens);
|
||||
Program program = parser.parse();
|
||||
if (prettyPrint) {
|
||||
program.prettyPrint(System.out);
|
||||
if (prettyPrint || listPrint) {
|
||||
program.accept(Visitors.printBuilder().prettyPrint(prettyPrint).build());
|
||||
}
|
||||
|
||||
byte[] data = program.toBytes(address);
|
||||
|
||||
byte[] data = Visitors.byteVisitor(address).dump(program);
|
||||
if (hexFormat) {
|
||||
hexDump(address, data, false);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user