mirror of
https://github.com/AppleCommander/AppleCommander.git
synced 2024-12-21 17:29:55 +00:00
Moved tokenizer out to the bastokenizer project; GUI now recognizes and
utilizes an AppleSingle on import.
This commit is contained in:
parent
6b1bdab366
commit
6915ada616
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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<Token> 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();
|
||||
}
|
||||
|
@ -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.
|
||||
* <p>
|
||||
@ -261,7 +261,7 @@ public class DiskExplorerTab {
|
||||
|
||||
if (disks[i].canHaveDirectories()) {
|
||||
try {
|
||||
Iterator files = disks[i].getFiles().iterator();
|
||||
Iterator<FileEntry> 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<FileColumnHeader> headers = disks[0].getFileColumnHeaders(format);
|
||||
int[] headerWidths = new int[headers.size()];
|
||||
GC gc = new GC(shell);
|
||||
for (int i=0; i<headers.size(); i++) {
|
||||
@ -921,7 +921,7 @@ public class DiskExplorerTab {
|
||||
}
|
||||
});
|
||||
TableColumn column = null;
|
||||
List headers = disks[0].getFileColumnHeaders(currentFormat);
|
||||
List<FileColumnHeader> headers = disks[0].getFileColumnHeaders(currentFormat);
|
||||
int[] widths = (int[])columnWidths.get(new Integer(currentFormat));
|
||||
for (int i=0; i<headers.size(); i++) {
|
||||
FileColumnHeader header = (FileColumnHeader) headers.get(i);
|
||||
@ -941,12 +941,12 @@ public class DiskExplorerTab {
|
||||
fileTable.removeAll();
|
||||
}
|
||||
|
||||
Iterator files = fileList.iterator();
|
||||
Iterator<FileEntry> 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<String> data = entry.getFileColumnData(currentFormat);
|
||||
for (int i=0; i<data.size(); i++) {
|
||||
item.setText(i, (String)data.get(i));
|
||||
}
|
||||
@ -1151,7 +1151,7 @@ public class DiskExplorerTab {
|
||||
if (wizard.isWizardCompleted()) {
|
||||
Shell dialog = null;
|
||||
try {
|
||||
List specs = wizard.getImportSpecifications();
|
||||
List<ImportSpecification> 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<FileEntry> 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<? extends FileFilter> 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<FileColumnHeader> 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<FileEntry> iterator = directory.getFiles().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
FileEntry fileEntry = (FileEntry) iterator.next();
|
||||
if (!fileEntry.isDeleted() || isShowDeletedFiles()) {
|
||||
List columns = fileEntry.getFileColumnData(getCurrentFormat());
|
||||
List<String> columns = fileEntry.getFileColumnData(getCurrentFormat());
|
||||
for (int i=0; i<columns.size(); i++) {
|
||||
FileColumnHeader header = (FileColumnHeader) fileHeaders.get(i);
|
||||
String text = (String)columns.get(i);
|
||||
|
@ -1,7 +1,12 @@
|
||||
package com.webcodepro.applecommander.ui.swt.wizard.importfile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Iterator;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.events.SelectionAdapter;
|
||||
@ -16,12 +21,14 @@ import org.eclipse.swt.widgets.Combo;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.FileDialog;
|
||||
import org.eclipse.swt.widgets.Label;
|
||||
import org.eclipse.swt.widgets.MessageBox;
|
||||
import org.eclipse.swt.widgets.Shell;
|
||||
import org.eclipse.swt.widgets.Table;
|
||||
import org.eclipse.swt.widgets.TableColumn;
|
||||
import org.eclipse.swt.widgets.TableItem;
|
||||
import org.eclipse.swt.widgets.Text;
|
||||
|
||||
import com.webcodepro.applecommander.storage.os.prodos.ProdosFormatDisk;
|
||||
import com.webcodepro.applecommander.ui.ImportSpecification;
|
||||
import com.webcodepro.applecommander.ui.UiBundle;
|
||||
import com.webcodepro.applecommander.ui.UserPreferences;
|
||||
@ -30,6 +37,11 @@ import com.webcodepro.applecommander.ui.swt.wizard.WizardPane;
|
||||
import com.webcodepro.applecommander.util.AppleUtil;
|
||||
import com.webcodepro.applecommander.util.TextBundle;
|
||||
|
||||
import io.github.applecommander.applesingle.AppleSingle;
|
||||
import io.github.applecommander.applesingle.AppleSingleReader;
|
||||
import io.github.applecommander.applesingle.ProdosFileInfo;
|
||||
import io.github.applecommander.applesingle.Utilities;
|
||||
|
||||
/**
|
||||
* Allow the used to choose the files to import into the disk image.
|
||||
* <br>
|
||||
@ -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<filenames.length; i++) {
|
||||
ImportSpecification spec = new ImportSpecification(
|
||||
path + File.separatorChar+ filenames[i],
|
||||
wizard.getDisk().getSuggestedFilename(filenames[i]),
|
||||
wizard.getDisk().getSuggestedFiletype(filenames[i]));
|
||||
String filename = path + File.separatorChar + filenames[i];
|
||||
|
||||
String suggestedFiletype = wizard.getDisk().getSuggestedFiletype(filenames[i]);
|
||||
String suggestedFilename = wizard.getDisk().getSuggestedFilename(filenames[i]);
|
||||
int suggestedAddress = 0;
|
||||
|
||||
try (InputStream inputStream = new FileInputStream(filename)) {
|
||||
byte[] data = Utilities.toByteArray(inputStream);
|
||||
AppleSingleReader reader = AppleSingleReader.builder(data).build();
|
||||
if (AppleSingle.test(reader)) {
|
||||
AppleSingle as = AppleSingle.read(data);
|
||||
suggestedFilename = Optional.ofNullable(as.getRealName())
|
||||
.orElse(suggestedFilename);
|
||||
suggestedFiletype = ProdosFormatDisk.getFiletype(as.getProdosFileInfo().getFileType());
|
||||
suggestedAddress = Optional.ofNullable(as.getProdosFileInfo())
|
||||
.map(ProdosFileInfo::getAuxType)
|
||||
.orElse(suggestedAddress);
|
||||
}
|
||||
}
|
||||
ImportSpecification spec = new ImportSpecification(filename, suggestedFilename, suggestedFiletype);
|
||||
spec.setAddress(suggestedAddress);
|
||||
wizard.addImportSpecification(spec);
|
||||
}
|
||||
refreshTable();
|
||||
@ -183,7 +228,7 @@ public class ImportSelectFilesWizardPane extends WizardPane {
|
||||
*/
|
||||
protected void refreshTable() {
|
||||
fileTable.removeAll();
|
||||
Iterator specs = wizard.getImportSpecifications().iterator();
|
||||
Iterator<ImportSpecification> specs = wizard.getImportSpecifications().iterator();
|
||||
boolean canFinish = specs.hasNext();
|
||||
while (specs.hasNext()) {
|
||||
ImportSpecification spec = (ImportSpecification) specs.next();
|
||||
|
@ -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<String> 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<String> 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<ApplesoftKeyword> find(String value) {
|
||||
Objects.requireNonNull(value);
|
||||
for (ApplesoftKeyword kw : values()) {
|
||||
if (value.equalsIgnoreCase(kw.parts.get(0))) {
|
||||
return Optional.of(kw);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
@ -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<Statement> 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;
|
||||
}
|
||||
}
|
@ -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<Token> tokens;
|
||||
|
||||
public Parser(Queue<Token> 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();
|
||||
}
|
||||
}
|
@ -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<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();
|
||||
}
|
||||
}
|
@ -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<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);
|
||||
}
|
||||
}
|
@ -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("<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);
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
@ -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<Token> 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<Token> tokenize(InputStream inputStream) throws IOException {
|
||||
try (InputStreamReader streamReader = new InputStreamReader(inputStream)) {
|
||||
return tokenize(streamReader);
|
||||
}
|
||||
}
|
||||
private static Queue<Token> tokenize(Reader reader) throws IOException {
|
||||
TokenReader tokenReader = new TokenReader(reader);
|
||||
LinkedList<Token> 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<Token> 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<ApplesoftKeyword> 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();
|
||||
}
|
||||
}
|
@ -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;
|
Loading…
Reference in New Issue
Block a user