diff --git a/build.gradle b/build.gradle index 049846f..ac19a89 100644 --- a/build.gradle +++ b/build.gradle @@ -63,6 +63,7 @@ tasks.withType(Jar) { dependencies { compile "net.sf.applecommander:ShrinkItArchive:$shkVersion" compile "net.sf.applecommander:applesingle-api:$asVersion" + compile "net.sf.applecommander:bastokenizer-api:$btVersion" compileOnly "org.apache.ant:ant:$antVersion" testCompile "junit:junit:$junitVersion" diff --git a/gradle.properties b/gradle.properties index 13ec011..3f1fe05 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,7 +5,8 @@ version=1.5.0-BETA # Dependency versions shkVersion=1.2.2 -asVersion=1.1.0 +asVersion=1.2.1 +btVersion=0.2.0 swtVersion=4.6.1 junitVersion=4.12 antVersion=1.8.2 diff --git a/src/main/java/com/webcodepro/applecommander/ui/ac.java b/src/main/java/com/webcodepro/applecommander/ui/ac.java index e842dc0..4c02181 100644 --- a/src/main/java/com/webcodepro/applecommander/ui/ac.java +++ b/src/main/java/com/webcodepro/applecommander/ui/ac.java @@ -53,13 +53,15 @@ import com.webcodepro.applecommander.storage.physical.ProdosOrder; import com.webcodepro.applecommander.util.AppleUtil; import com.webcodepro.applecommander.util.StreamUtil; import com.webcodepro.applecommander.util.TextBundle; -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 io.github.applecommander.applesingle.AppleSingle; import io.github.applecommander.applesingle.ProdosFileInfo; +import io.github.applecommander.bastokenizer.api.Configuration; +import io.github.applecommander.bastokenizer.api.Parser; +import io.github.applecommander.bastokenizer.api.TokenReader; +import io.github.applecommander.bastokenizer.api.Visitors; +import io.github.applecommander.bastokenizer.api.model.Program; +import io.github.applecommander.bastokenizer.api.model.Token; /** * ac provides a command line interface to key AppleCommander functions. Text @@ -183,11 +185,11 @@ public class ac { * to 0x801. */ public static void putAppleSoft(String imageName, String fileName) throws IOException, DiskException { + Configuration config = Configuration.builder().build(); Queue tokens = TokenReader.tokenize(System.in); Parser parser = new Parser(tokens); Program program = parser.parse(); - int address = 0x801; - byte[] data = program.toBytes(address); + byte[] data = Visitors.byteVisitor(config).dump(program); Name name = new Name(fileName); File file = new File(imageName); @@ -209,7 +211,7 @@ public class ac { entry.setFilename(name.name); entry.setFileData(data); if (entry.needsAddress()) { - entry.setAddress(address); + entry.setAddress(config.startAddress); } formattedDisk.save(); } diff --git a/src/main/java/com/webcodepro/applecommander/ui/swt/DiskExplorerTab.java b/src/main/java/com/webcodepro/applecommander/ui/swt/DiskExplorerTab.java index 6d6a6e1..2db2c1e 100644 --- a/src/main/java/com/webcodepro/applecommander/ui/swt/DiskExplorerTab.java +++ b/src/main/java/com/webcodepro/applecommander/ui/swt/DiskExplorerTab.java @@ -26,7 +26,6 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; @@ -72,7 +71,6 @@ import org.eclipse.swt.widgets.TreeItem; import com.webcodepro.applecommander.compiler.ApplesoftCompiler; import com.webcodepro.applecommander.storage.DirectoryEntry; import com.webcodepro.applecommander.storage.Disk; -import com.webcodepro.applecommander.storage.DiskCorruptException; import com.webcodepro.applecommander.storage.DiskException; import com.webcodepro.applecommander.storage.FileEntry; import com.webcodepro.applecommander.storage.FileEntryComparator; @@ -87,10 +85,10 @@ import com.webcodepro.applecommander.storage.filters.AssemblySourceFileFilter; import com.webcodepro.applecommander.storage.filters.BinaryFileFilter; import com.webcodepro.applecommander.storage.filters.BusinessBASICFileFilter; import com.webcodepro.applecommander.storage.filters.GraphicsFileFilter; +import com.webcodepro.applecommander.storage.filters.GutenbergFileFilter; import com.webcodepro.applecommander.storage.filters.IntegerBasicFileFilter; import com.webcodepro.applecommander.storage.filters.PascalTextFileFilter; import com.webcodepro.applecommander.storage.filters.TextFileFilter; -import com.webcodepro.applecommander.storage.filters.GutenbergFileFilter; import com.webcodepro.applecommander.storage.os.prodos.ProdosDiskSizeDoesNotMatchException; import com.webcodepro.applecommander.storage.os.prodos.ProdosFormatDisk; import com.webcodepro.applecommander.storage.physical.ByteArrayImageLayout; @@ -112,6 +110,8 @@ import com.webcodepro.applecommander.util.Host; import com.webcodepro.applecommander.util.StreamUtil; import com.webcodepro.applecommander.util.TextBundle; +import io.github.applecommander.applesingle.AppleSingle; + /** * Build the Disk File tab for the Disk Window. *

@@ -261,7 +261,7 @@ public class DiskExplorerTab { if (disks[i].canHaveDirectories()) { try { - Iterator files = disks[i].getFiles().iterator(); + Iterator files = disks[i].getFiles().iterator(); while (files.hasNext()) { FileEntry entry = (FileEntry) files.next(); if (entry.isDirectory()) { @@ -848,7 +848,7 @@ public class DiskExplorerTab { * These can and are over-ridden by user sizing. */ protected void computeColumnWidths(int format) { - List headers = disks[0].getFileColumnHeaders(format); + List headers = disks[0].getFileColumnHeaders(format); int[] headerWidths = new int[headers.size()]; GC gc = new GC(shell); for (int i=0; i headers = disks[0].getFileColumnHeaders(currentFormat); int[] widths = (int[])columnWidths.get(new Integer(currentFormat)); for (int i=0; i files = fileList.iterator(); while (files.hasNext()) { FileEntry entry = (FileEntry) files.next(); if (showDeletedFiles || !entry.isDeleted()) { TableItem item = new TableItem(fileTable, 0); - List data = entry.getFileColumnData(currentFormat); + List data = entry.getFileColumnData(currentFormat); for (int i=0; i specs = wizard.getImportSpecifications(); // Progress meter for import wizard: dialog = new Shell(shell, SWT.DIALOG_TRIM | SWT.APPLICATION_MODAL); dialog.setText(textBundle.get("ImportingFilesTitle")); //$NON-NLS-1$ @@ -1196,17 +1196,22 @@ public class DiskExplorerTab { ByteArrayOutputStream buffer = new ByteArrayOutputStream(); InputStream input = new FileInputStream(spec.getSourceFilename()); StreamUtil.copy(input, buffer); + byte[] fileData = buffer.toByteArray(); FileEntry fileEntry = directory.createFile(); fileEntry.setFilename(spec.getTargetFilename()); fileEntry.setFiletype(spec.getFiletype()); if (spec.isRawFileImport()) { - disks[0].setFileData(fileEntry, buffer.toByteArray()); + disks[0].setFileData(fileEntry, fileData); } else { + if (AppleSingle.test(fileData)) { + AppleSingle as = AppleSingle.read(fileData); + fileData = as.getDataFork(); + } if (fileEntry.needsAddress()) { fileEntry.setAddress(spec.getAddress()); } try { - fileEntry.setFileData(buffer.toByteArray()); + fileEntry.setFileData(fileData); } catch (ProdosDiskSizeDoesNotMatchException ex) { int answer = SwtUtil.showYesNoDialog(shell, textBundle.get("ResizeDiskTitle"), //$NON-NLS-1$ @@ -1215,7 +1220,7 @@ public class DiskExplorerTab { ProdosFormatDisk prodosDisk = (ProdosFormatDisk) fileEntry.getFormattedDisk(); prodosDisk.resizeDiskImage(); - fileEntry.setFileData(buffer.toByteArray()); + fileEntry.setFileData(fileData); } } } @@ -1243,7 +1248,7 @@ public class DiskExplorerTab { * @throws DiskException */ protected void addDirectoriesToTree(TreeItem directoryItem, DirectoryEntry directoryEntry) throws DiskException { - Iterator files = directoryEntry.getFiles().iterator(); + Iterator files = directoryEntry.getFiles().iterator(); while (files.hasNext()) { final FileEntry entry = (FileEntry) files.next(); if (entry.isDirectory()) { @@ -1585,7 +1590,7 @@ public class DiskExplorerTab { * Open up the view file window for the currently selected file. * @throws DiskException */ - protected void viewFile(Class fileFilterClass) throws DiskException { + protected void viewFile(Class fileFilterClass) throws DiskException { FileEntry fileEntry = getSelectedFileEntry(); if (fileEntry.isDeleted()) { SwtUtil.showErrorDialog(shell, textBundle.get("DeleteFileErrorTitle"), //$NON-NLS-1$ @@ -1601,7 +1606,7 @@ public class DiskExplorerTab { FileViewerWindow window = null; FileFilter fileFilter = null; try { - fileFilter = (FileFilter) fileFilterClass.newInstance(); + fileFilter = fileFilterClass.newInstance(); } catch (NullPointerException ex) { // This is expected } catch (InstantiationException e) { @@ -1757,7 +1762,7 @@ public class DiskExplorerTab { private int x; private Rectangle clientArea; private GC gc; - private List fileHeaders; + private List fileHeaders; private int[] printColumnWidths; private int[] printColumnPosition; private Font normalFont; @@ -1889,11 +1894,11 @@ public class DiskExplorerTab { page++; } protected void printFiles(DirectoryEntry directory, int level) throws DiskException { - Iterator iterator = directory.getFiles().iterator(); + Iterator iterator = directory.getFiles().iterator(); while (iterator.hasNext()) { FileEntry fileEntry = (FileEntry) iterator.next(); if (!fileEntry.isDeleted() || isShowDeletedFiles()) { - List columns = fileEntry.getFileColumnData(getCurrentFormat()); + List columns = fileEntry.getFileColumnData(getCurrentFormat()); for (int i=0; i @@ -121,15 +133,31 @@ public class ImportSelectFilesWizardPane extends WizardPane { * Single click. */ public void widgetSelected(SelectionEvent event) { - FileDialog dialog = new FileDialog(getParent().getShell(), - SWT.OPEN | SWT.MULTI); - dialog.setFilterPath( - UserPreferences.getInstance().getImportDirectory()); - String filename = dialog.open(); - if (filename != null) { - setFilenames(dialog.getFilterPath(), dialog.getFileNames()); - UserPreferences.getInstance().setImportDirectory( - dialog.getFilterPath()); + try { + FileDialog dialog = new FileDialog(getParent().getShell(), + SWT.OPEN | SWT.MULTI); + dialog.setFilterPath( + UserPreferences.getInstance().getImportDirectory()); + String filename = dialog.open(); + if (filename != null) { + setFilenames(dialog.getFilterPath(), dialog.getFileNames()); + UserPreferences.getInstance().setImportDirectory( + dialog.getFilterPath()); + } + } catch (Throwable t) { + MessageBox mb = new MessageBox(getParent().getShell(), + SWT.OK | SWT.ICON_ERROR | SWT.APPLICATION_MODAL); + mb.setText("Error!"); + StringBuilder message = new StringBuilder(); + while (t != null) { + if (t.getMessage() != null && t.getMessage().length() > 0) { + message.append(t.getMessage()); + message.append("\n"); + } + t = t.getCause(); + } + mb.setMessage(message.toString()); + mb.open(); } } }); @@ -168,12 +196,29 @@ public class ImportSelectFilesWizardPane extends WizardPane { /** * Set all filenames to be imported. */ - protected void setFilenames(String path, String[] filenames) { + protected void setFilenames(String path, String[] filenames) throws FileNotFoundException, IOException { for (int i=0; i specs = wizard.getImportSpecifications().iterator(); boolean canFinish = specs.hasNext(); while (specs.hasNext()) { ImportSpecification spec = (ImportSpecification) specs.next(); diff --git a/src/main/java/com/webcodepro/applecommander/util/applesoft/ApplesoftKeyword.java b/src/main/java/com/webcodepro/applecommander/util/applesoft/ApplesoftKeyword.java deleted file mode 100644 index 0bc5504..0000000 --- a/src/main/java/com/webcodepro/applecommander/util/applesoft/ApplesoftKeyword.java +++ /dev/null @@ -1,201 +0,0 @@ -package com.webcodepro.applecommander.util.applesoft; - -import java.io.IOException; -import java.io.Reader; -import java.io.StreamTokenizer; -import java.io.StringReader; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Objects; -import java.util.Optional; - -/** All elements of AppleSoft that are tokenized in some manner. "Keyword" was picked as it is not the word token. ;-) */ -public enum ApplesoftKeyword { - END(0x80, "END"), - FOR(0x81, "FOR"), - NEXT(0x82, "NEXT"), - DATA(0x83, "DATA"), - INPUT(0x84, "INPUT"), - DEL(0x85, "DEL"), - DIM(0x86, "DIM"), - READ(0x87, "READ"), - GR(0x88, "GR"), - TEXT(0x89, "TEXT"), - PR(0x8A, "PR#"), - IN(0x8B, "IN#"), - CALL(0x8C, "CALL"), - PLOT(0x8D, "PLOT"), - HLIN(0x8E, "HLIN"), - VLIN(0x8F, "VLIN"), - HGR2(0x90, "HGR2"), - HGR(0x91, "HGR"), - HCOLOR(0x92, "HCOLOR="), - HPLOT(0x93, "HPLOT"), - DRAW(0x94, "DRAW"), - XDRAW(0x95, "XDRAW"), - HTAB(0x96, "HTAB"), - HOME(0x97, "HOME"), - ROT(0x98, "ROT="), - SCALE(0x99, "SCALE="), - SHLOAD(0x9A, "SHLOAD"), - TRACE(0x9B, "TRACE"), - NOTRACE(0x9C, "NOTRACE"), - NORMAL(0x9D, "NORMAL"), - INVERSE(0x9E, "INVERSE"), - FLASH(0x9F, "FLASH"), - COLOR(0xA0, "COLOR="), - POP(0xA1, "POP"), - VTAB(0xA2, "VTAB"), - HIMEM(0xA3, "HIMEM:"), - LOMEM(0xA4, "LOMEM:"), - ONERR(0xA5, "ONERR"), - RESUME(0xA6, "RESUME"), - RECALL(0xA7, "RECALL"), - STORE(0xA8, "STORE"), - SPEED(0xA9, "SPEED="), - LET(0xAA, "LET"), - GOTO(0xAB, "GOTO"), - RUN(0xAC, "RUN"), - IF(0xAD, "IF"), - RESTORE(0xAE, "RESTORE"), - amp(0xAF, "&"), - GOSUB(0xB0, "GOSUB"), - RETURN(0xB1, "RETURN"), - REM(0xB2, "REM"), - STOP(0xB3, "STOP"), - ON(0xB4, "ON"), - WAIT(0xB5, "WAIT"), - LOAD(0xB6, "LOAD"), - SAVE(0xB7, "SAVE"), - DEF(0xB8, "DEF"), - POKE(0xB9, "POKE"), - PRINT(0xBA, "PRINT"), - CONT(0xBB, "CONT"), - LIST(0xBC, "LIST"), - CLEAR(0xBD, "CLEAR"), - GET(0xBE, "GET"), - NEW(0xBF, "NEW"), - TAB(0xC0, "TAB("), - TO(0xC1, "TO"), - FN(0xC2, "FN"), - SPC(0xC3, "SPC("), - THEN(0xC4, "THEN"), - AT(0xC5, "AT"), - NOT(0xC6, "NOT"), - STEP(0xC7, "STEP"), - add(0xC8, "+"), - sub(0xC9, "-"), - mul(0xCA, "*"), - div(0xCB, "/"), - pow(0xCC, "^"), - AND(0xCD, "AND"), - OR(0xCE, "OR"), - gt(0xCF, ">"), - eq(0xD0, "="), - lt(0xD1, "<"), - SGN(0xD2, "SGN"), - INT(0xD3, "INT"), - ABS(0xD4, "ABS"), - USR(0xD5, "USR"), - FRE(0xD6, "FRE"), - SCRN(0xD7, "SCRN("), - PDL(0xD8, "PDL"), - POS(0xD9, "POS"), - SQR(0xDA, "SQR"), - RND(0xDB, "RND"), - LOG(0xDC, "LOG"), - EXP(0xDD, "EXP"), - COS(0xDE, "COS"), - SIN(0xDF, "SIN"), - TAN(0xE0, "TAN"), - ATN(0xE1, "ATN"), - PEEK(0xE2, "PEEK"), - LEN(0xE3, "LEN"), - STR(0xE4, "STR$"), - VAL(0xE5, "VAL"), - ASC(0xE6, "ASC"), - CHR(0xE7, "CHR$"), - LEFT(0xE8, "LEFT$"), - RIGHT(0xE9, "RIGHT$"), - MID(0xEA, "MID$"); - - /** - * The AppleSoft token value. Token is overloaded, so "code" is good enough. - */ - public final int code; - /** - * Full text of the token. - */ - public final String text; - /** - * Token parts as seen by the StreamTokenizer. - */ - public final List parts; - /** - * Indicates that this needs _just_ a closing right parenthesis since the - * opening left parenthesis is included in the token - */ - public boolean needsRParen; - - private ApplesoftKeyword(int code, String text) { - this.code = code; - this.text = text; - - try { - // A bit brute-force, but should always match the tokenizer configuration! - List list = new ArrayList<>(); - StreamTokenizer t = tokenizer(new StringReader(text)); - while (t.nextToken() != StreamTokenizer.TT_EOF) { - switch (t.ttype) { - case StreamTokenizer.TT_WORD: - list.add(t.sval); - break; - default: - list.add(String.format("%c", t.ttype)); - break; - } - } - this.parts = Collections.unmodifiableList(list); - this.needsRParen = parts.contains("("); - } catch (IOException ex) { - throw new RuntimeException(ex); - } - } - - public boolean equalsIgnoreCase(String value) { - return this.text.equalsIgnoreCase(value); - } - - @Override - public String toString() { - return String.format("%s (%02x)", text, code); - } - - /** Utility method to create a shared definition for AppleSoft file parsing. */ - public static StreamTokenizer tokenizer(Reader r) { - StreamTokenizer tokenizer = new StreamTokenizer(r); - tokenizer.resetSyntax(); - tokenizer.wordChars('a', 'z'); - tokenizer.wordChars('A', 'Z'); - tokenizer.wordChars(128 + 32, 255); - tokenizer.whitespaceChars(0, ' '); - tokenizer.quoteChar('"'); - tokenizer.parseNumbers(); - // This resets part of parseNumbers to match AppleSoft tokenization! - tokenizer.ordinaryChar('-'); - tokenizer.eolIsSignificant(true); - return tokenizer; - } - - /** Utility method to locate a keyword ignoring case. */ - public static Optional find(String value) { - Objects.requireNonNull(value); - for (ApplesoftKeyword kw : values()) { - if (value.equalsIgnoreCase(kw.parts.get(0))) { - return Optional.of(kw); - } - } - return Optional.empty(); - } -} diff --git a/src/main/java/com/webcodepro/applecommander/util/applesoft/Line.java b/src/main/java/com/webcodepro/applecommander/util/applesoft/Line.java deleted file mode 100644 index 3467341..0000000 --- a/src/main/java/com/webcodepro/applecommander/util/applesoft/Line.java +++ /dev/null @@ -1,48 +0,0 @@ -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; - -/** An AppleSoft BASIC Line representation. */ -public class Line { - public final int lineNumber; - public final List statements = new ArrayList<>(); - - public Line(int lineNumber) { - 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; - } -} diff --git a/src/main/java/com/webcodepro/applecommander/util/applesoft/Parser.java b/src/main/java/com/webcodepro/applecommander/util/applesoft/Parser.java deleted file mode 100644 index 0796759..0000000 --- a/src/main/java/com/webcodepro/applecommander/util/applesoft/Parser.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.webcodepro.applecommander.util.applesoft; - -import java.util.Objects; -import java.util.Queue; - -import com.webcodepro.applecommander.util.applesoft.Token.Type; - -/** - * The Parser will read a series of Tokens and build a Program. - * Note that this is not a compiler and does not "understand" the program. - */ -public class Parser { - private final Queue tokens; - - public Parser(Queue tokens) { - Objects.requireNonNull(tokens); - this.tokens = tokens; - } - - public Program parse() { - Program program = new Program(); - while (!tokens.isEmpty()) { - Line line = readLine(); - program.lines.add(line); - } - return program; - } - - public Line readLine() { - Line line = new Line(expectNumber()); - while (!tokens.isEmpty() && tokens.peek().type != Type.EOL) { - Statement statement = readStatement(); - if (statement != null) { - line.statements.add(statement); - } else { - break; - } - } - if (!tokens.isEmpty() && tokens.peek().type == Type.EOL) { - tokens.remove(); // Skip that EOL - } - return line; - } - - public Statement readStatement() { - Statement statement = new Statement(); - while (!tokens.isEmpty()) { - if (tokens.peek().type == Type.EOL) break; - Token t = tokens.remove(); - if (":".equals(t.text)) break; - statement.tokens.add(t); - } - return statement; - } - - public int expectNumber() { - Token c = tokens.remove(); - if (c.type != Type.NUMBER) { - throw new RuntimeException("Expected a number in line #" + c.line); - } - return c.number.intValue(); - } -} diff --git a/src/main/java/com/webcodepro/applecommander/util/applesoft/Program.java b/src/main/java/com/webcodepro/applecommander/util/applesoft/Program.java deleted file mode 100644 index 0152cb6..0000000 --- a/src/main/java/com/webcodepro/applecommander/util/applesoft/Program.java +++ /dev/null @@ -1,26 +0,0 @@ -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; - -/** A Program is a series of lines. */ -public class Program { - public final List 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(); - } -} diff --git a/src/main/java/com/webcodepro/applecommander/util/applesoft/Statement.java b/src/main/java/com/webcodepro/applecommander/util/applesoft/Statement.java deleted file mode 100644 index 75c47a6..0000000 --- a/src/main/java/com/webcodepro/applecommander/util/applesoft/Statement.java +++ /dev/null @@ -1,22 +0,0 @@ -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; - -/** A Statement is simply a series of Tokens. */ -public class Statement { - public final List 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); - } -} diff --git a/src/main/java/com/webcodepro/applecommander/util/applesoft/Token.java b/src/main/java/com/webcodepro/applecommander/util/applesoft/Token.java deleted file mode 100644 index a7438d5..0000000 --- a/src/main/java/com/webcodepro/applecommander/util/applesoft/Token.java +++ /dev/null @@ -1,133 +0,0 @@ -package com.webcodepro.applecommander.util.applesoft; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.util.Optional; - -/** - * A Token in the classic compiler sense, in that this represents a component of the application. - * - * @author rob - */ -public class Token { - public final int line; - public final Type type; - public final ApplesoftKeyword keyword; - public final Double number; - public final String text; - - private Token(int line, Type type, ApplesoftKeyword keyword, Double number, String text) { - this.line = line; - this.type = type; - this.keyword = keyword; - this.number = number; - this.text = text; - } - @Override - public String toString() { - switch (type) { - case EOL: - return type.toString(); - case KEYWORD: - return keyword.toString(); - case NUMBER: - return String.format("%s(%f)", type, number); - default: - return String.format("%s(%s)", type, text); - } - } - - public void prettyPrint(PrintStream ps) { - switch (type) { - case EOL: - ps.print(""); - 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 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); - } - public static Token number(int line, Double number) { - return new Token(line, Type.NUMBER, null, number, null); - } - public static Token ident(int line, String text) { - return new Token(line, Type.IDENT, null, null, text); - } - public static Token comment(int line, String text) { - return new Token(line, Type.COMMENT, null, null, text); - } - public static Token string(int line, String text) { - return new Token(line, Type.STRING, null, null, text); - } - public static Token keyword(int line, ApplesoftKeyword keyword) { - // Note that the text component is useful to have for parsing, so we replicate it... - return new Token(line, Type.KEYWORD, keyword, null, keyword.text); - } - public static Token syntax(int line, int ch) { - return new Token(line, Type.SYNTAX, null, null, String.format("%c", ch)); - } - - public static enum Type { - EOL, NUMBER, IDENT, COMMENT, STRING, KEYWORD, SYNTAX - } -} \ No newline at end of file diff --git a/src/main/java/com/webcodepro/applecommander/util/applesoft/TokenReader.java b/src/main/java/com/webcodepro/applecommander/util/applesoft/TokenReader.java deleted file mode 100644 index 62e4650..0000000 --- a/src/main/java/com/webcodepro/applecommander/util/applesoft/TokenReader.java +++ /dev/null @@ -1,132 +0,0 @@ -package com.webcodepro.applecommander.util.applesoft; - -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; -import java.io.StreamTokenizer; -import java.util.LinkedList; -import java.util.Optional; -import java.util.Queue; - -/** - * The TokenReader, given a text file, generates a series of Tokens (in the compiler sense, - * not AppleSoft) for the AppleSoft program. - * - * @author rob - */ -public class TokenReader { - private boolean hasMore = true; - // Internal flag just in case we consume the EOL (see REM for instance)s - private boolean needSyntheticEol = false; - private Reader reader; - private StreamTokenizer tokenizer; - - /** A handy method to generate a list of Tokens from a file. */ - public static Queue tokenize(String filename) throws FileNotFoundException, IOException { - try (FileReader fileReader = new FileReader(filename)) { - return tokenize(fileReader); - } - } - /** A handy method to generate a list of Tokens from an InputStream. */ - public static Queue tokenize(InputStream inputStream) throws IOException { - try (InputStreamReader streamReader = new InputStreamReader(inputStream)) { - return tokenize(streamReader); - } - } - private static Queue tokenize(Reader reader) throws IOException { - TokenReader tokenReader = new TokenReader(reader); - LinkedList tokens = new LinkedList<>(); - while (tokenReader.hasMore()) { - // Magic number: maximum number of pieces from the StreamTokenizer that may be combined. - tokenReader.next(2) - .ifPresent(tokens::add); - } - return tokens; - } - - public TokenReader(Reader reader) { - this.reader = reader; - this.tokenizer = ApplesoftKeyword.tokenizer(reader); - } - - public boolean hasMore() { - return hasMore; - } - - public Optional next(int depth) throws IOException { - // A cheesy attempt to prevent too much looping... - if (depth > 0) { - if (this.needSyntheticEol) { - this.needSyntheticEol = false; - int line = tokenizer.lineno(); - return Optional.of(Token.eol(line)); - } - hasMore = tokenizer.nextToken() != StreamTokenizer.TT_EOF; - if (hasMore) { - int line = tokenizer.lineno(); - switch (tokenizer.ttype) { - case StreamTokenizer.TT_EOL: - return Optional.of(Token.eol(line)); - case StreamTokenizer.TT_NUMBER: - return Optional.of(Token.number(line, tokenizer.nval)); - case StreamTokenizer.TT_WORD: - Optional opt = ApplesoftKeyword.find(tokenizer.sval); - if (opt.filter(kw -> kw == ApplesoftKeyword.REM).isPresent()) { - StringBuilder sb = new StringBuilder(); - while (true) { - // Bypass the Tokenizer and just read to EOL for the comment - int ch = reader.read(); - if (ch == '\n') { - // Recover to the newline so that the next token is a EOL - // This is needed for parsing! - this.needSyntheticEol = true; - break; - } - sb.append((char)ch); - } - return Optional.of(Token.comment(line, sb.toString())); - } - // Optional and exceptions don't play well. :-/ - if (opt.isPresent() && opt.get().parts.size() > 1) { - // Pull next token and see if it is the 2nd part ("MID$" == "MID", "$"; checking for the "$") - next(depth-1) - .filter(t -> opt.get().parts.get(1).equals(t.text)) - .orElseThrow(() -> new IOException("Expecting: " + opt.get().parts)); - } - return Optional.of(opt - .map(kw -> Token.keyword(line, kw)) - .orElse(Token.ident(line, tokenizer.sval))); - case '"': - return Optional.of(Token.string(line, tokenizer.sval)); - case '(': - case ')': - case ',': - case ':': - case '$': - case '#': - case ';': - case '&': - case '=': - case '<': - case '>': - case '*': - case '+': - case '-': - case '/': - case '^': - return Optional.of( - ApplesoftKeyword.find(String.format("%c", tokenizer.ttype)) - .map(kw -> Token.keyword(line, kw)) - .orElse(Token.syntax(line, tokenizer.ttype))); - default: - throw new IOException(String.format( - "Unknown! ttype=%d, nval=%f, sval=%s\n", tokenizer.ttype, tokenizer.nval, tokenizer.sval)); - } - } - } - return Optional.empty(); - } -} diff --git a/src/main/java/com/webcodepro/applecommander/util/applesoft/package-info.java b/src/main/java/com/webcodepro/applecommander/util/applesoft/package-info.java deleted file mode 100644 index 2674183..0000000 --- a/src/main/java/com/webcodepro/applecommander/util/applesoft/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -/** - * This package is used by the AppleSoft text file import capabilities of AppleCommander. - * It is separate from the other components, primarily due to it being used on the input side, - * but also due to the fact that this is the third time AppleSoft tokens have been defined! - * - * @author rob - */ -package com.webcodepro.applecommander.util.applesoft; \ No newline at end of file