diff --git a/.gitignore b/.gitignore index a4e14b8..ac460f0 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ build/ # In case we run it locally, AppleCommander files we don't want to commit AppleCommander.preferences +*.bas # Ignoring all disk images *.dsk diff --git a/src/main/java/com/webcodepro/applecommander/ui/ac.java b/src/main/java/com/webcodepro/applecommander/ui/ac.java index 86e140e..5e26b2f 100644 --- a/src/main/java/com/webcodepro/applecommander/ui/ac.java +++ b/src/main/java/com/webcodepro/applecommander/ui/ac.java @@ -30,7 +30,9 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintStream; +import java.util.Arrays; import java.util.List; +import java.util.Queue; import com.webcodepro.applecommander.storage.DirectoryEntry; import com.webcodepro.applecommander.storage.Disk; @@ -53,6 +55,10 @@ import com.webcodepro.applecommander.util.AppleSingle.ProdosFileInfo; 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; /** * ac provides a command line interface to key AppleCommander functions. Text @@ -90,6 +96,8 @@ import com.webcodepro.applecommander.util.TextBundle; * -pas800 <imagename> <volname> create an 800K Pascal image. * -convert <filename> <imagename> uncompress a ShrinkIt file or disk image * or convert a DiskCopy 4.2 image into a ProDOS disk image. + * -bas <imagename> <filename> import an AppleSoft basic file from text + * back to it's tokenized format. * * * @author John B. Matthews @@ -155,6 +163,8 @@ public class ac { convert(args[1], args[2], Integer.parseInt(args[3])); else convert(args[1], args[2]); + } else if ("-bas".equalsIgnoreCase(args[0])) { + putAppleSoft(args[1], args[2]); } else { help(); } @@ -165,6 +175,44 @@ public class ac { help(); } } + + /** + * Convert the AppleSoft BASIC program from text into it's "native" tokenized format. + * Note that we try to infer the BASIC type dynamically and hard-code the start address + * to 0x801. + */ + public static void putAppleSoft(String imageName, String fileName) throws IOException, DiskException { + Queue tokens = TokenReader.tokenize(System.in); + Parser parser = new Parser(tokens); + Program program = parser.parse(); + int address = 0x801; + byte[] data = program.toBytes(address); + + Name name = new Name(fileName); + File file = new File(imageName); + if (!file.canRead()){ + throw new IOException("Unable to read input file named "+imageName+"."); + } + + Disk disk = new Disk(imageName); + FormattedDisk[] formattedDisks = disk.getFormattedDisks(); + FormattedDisk formattedDisk = formattedDisks[0]; + // Look through the supplied types and try to pick AppleSoft. Otherwise, let's try "A". + String fileType = Arrays.asList(formattedDisk.getFiletypes()).stream() + .filter(ft -> "A".equalsIgnoreCase(ft) || "BAS".equalsIgnoreCase(ft)) + .findFirst() + .orElse("A"); + FileEntry entry = name.createEntry(formattedDisk); + if (entry != null) { + entry.setFiletype(fileType); + entry.setFilename(name.name); + entry.setFileData(data); + if (entry.needsAddress()) { + entry.setAddress(address); + } + formattedDisk.save(); + } + } /** * Put fileName from the local filesytem into the file named fileOnImageName on the disk named imageName; diff --git a/src/main/java/com/webcodepro/applecommander/util/applesoft/ApplesoftKeyword.java b/src/main/java/com/webcodepro/applecommander/util/applesoft/ApplesoftKeyword.java new file mode 100644 index 0000000..0bc5504 --- /dev/null +++ b/src/main/java/com/webcodepro/applecommander/util/applesoft/ApplesoftKeyword.java @@ -0,0 +1,201 @@ +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 new file mode 100644 index 0000000..3467341 --- /dev/null +++ b/src/main/java/com/webcodepro/applecommander/util/applesoft/Line.java @@ -0,0 +1,48 @@ +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 new file mode 100644 index 0000000..0796759 --- /dev/null +++ b/src/main/java/com/webcodepro/applecommander/util/applesoft/Parser.java @@ -0,0 +1,63 @@ +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 new file mode 100644 index 0000000..0152cb6 --- /dev/null +++ b/src/main/java/com/webcodepro/applecommander/util/applesoft/Program.java @@ -0,0 +1,26 @@ +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 new file mode 100644 index 0000000..75c47a6 --- /dev/null +++ b/src/main/java/com/webcodepro/applecommander/util/applesoft/Statement.java @@ -0,0 +1,22 @@ +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 new file mode 100644 index 0000000..a7438d5 --- /dev/null +++ b/src/main/java/com/webcodepro/applecommander/util/applesoft/Token.java @@ -0,0 +1,133 @@ +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 new file mode 100644 index 0000000..62e4650 --- /dev/null +++ b/src/main/java/com/webcodepro/applecommander/util/applesoft/TokenReader.java @@ -0,0 +1,132 @@ +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 new file mode 100644 index 0000000..2674183 --- /dev/null +++ b/src/main/java/com/webcodepro/applecommander/util/applesoft/package-info.java @@ -0,0 +1,8 @@ +/** + * 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 diff --git a/src/main/resources/com/webcodepro/applecommander/ui/UiBundle.properties b/src/main/resources/com/webcodepro/applecommander/ui/UiBundle.properties index 4dde6fd..fda64c0 100644 --- a/src/main/resources/com/webcodepro/applecommander/ui/UiBundle.properties +++ b/src/main/resources/com/webcodepro/applecommander/ui/UiBundle.properties @@ -102,7 +102,7 @@ CreateDirectoryMenuItem=Create Directory... CommandLineErrorMessage = Error: {0} CommandLineNoMatchMessage = {0}: No match. CommandLineStatus = {0} format; {1} bytes free; {2} bytes used. -CommandLineHelp = CommandLineHelp = AppleCommander command line options [{0}]:\n-i [] display information about image(s).\n-ls [] list brief directory of image(s).\n-l [] list directory of image(s).\n-ll [] list detailed directory of image(s).\n-e [] export file from image to stdout\n or to an output file.\n-x [] extract all files from image to directory.\n-g [] get raw file from image to stdout\n or to an output file.\n-p [[$|0x]] put stdin\n in filename on image, using file type and address [0x2000].\n-d delete file from image.\n-k lock file on image.\n-u unlock file on image.\n-n change volume name (ProDOS or Pascal).\n-dos put stdin with DOS header\n in filename on image, using file type and address from header.\n-as [] put stdin with AppleSingle format\n in filename on image, using file type, address, and (optionally) name\n from the AppleSingle file.\n-geos interpret stdin as a GEOS conversion file and\n place it on image (ProDOS only).\n-dos140 create a 140K DOS 3.3 image.\n-pro140 create a 140K ProDOS image.\n-pro800 create an 800K ProDOS image.\n-pas140 create a 140K Pascal image.\n-pas800 create an 800K Pascal image.\n-convert [] uncompress a ShrinkIt or Binary\n II file; or convert a DiskCopy 4.2 image into a ProDOS disk image. +CommandLineHelp = CommandLineHelp = AppleCommander command line options [{0}]:\n-i [] display information about image(s).\n-ls [] list brief directory of image(s).\n-l [] list directory of image(s).\n-ll [] list detailed directory of image(s).\n-e [] export file from image to stdout\n or to an output file.\n-x [] extract all files from image to directory.\n-g [] get raw file from image to stdout\n or to an output file.\n-p [[$|0x]] put stdin\n in filename on image, using file type and address [0x2000].\n-d delete file from image.\n-k lock file on image.\n-u unlock file on image.\n-n change volume name (ProDOS or Pascal).\n-dos put stdin with DOS header\n in filename on image, using file type and address from header.\n-as [] put stdin with AppleSingle format\n in filename on image, using file type, address, and (optionally) name\n from the AppleSingle file.\n-geos interpret stdin as a GEOS conversion file and\n place it on image (ProDOS only).\n-dos140 create a 140K DOS 3.3 image.\n-pro140 create a 140K ProDOS image.\n-pro800 create an 800K ProDOS image.\n-pas140 create a 140K Pascal image.\n-pas800 create an 800K Pascal image.\n-convert [] uncompress a ShrinkIt or Binary\n II file; or convert a DiskCopy 4.2 image into a ProDOS disk image.\n-bas import an AppleSoft basic file from text\n back to it's tokenized format. CommandLineSDKReadOnly = SDK, SHK, and DC42 files are read-only. Use the convert option on them first. CommandLineDC42Bad = Unable to interpret this DiskCopy 42 image.