Refactored to implement visitor pattern in prettyPrint and toBytes. #7.

This commit is contained in:
Rob Greene 2018-05-13 21:15:14 -05:00
parent 19f3fe56bf
commit 8c6df25d80
7 changed files with 315 additions and 138 deletions

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}
}
}
}

View File

@ -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);
}