diff --git a/README.md b/README.md index f5ecfd3..8f8c024 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ There are binaries here: ## Current TODO list 1. Allow > 128 KB file support 2. Create/Delete directories -3. Add file/directory/basic tests +3. Add file/directory tests 4. Add rename 5. Add in-place file/directory moves diff --git a/main.go b/main.go index ef324ce..9b7aef6 100644 --- a/main.go +++ b/main.go @@ -39,7 +39,7 @@ func main() { flag.IntVar(&volumeSize, "s", 65535, "Number of blocks to create the volume with (default 65535, 64 to 65535, 0x0040 to 0xFFFF hex input accepted)") flag.StringVar(&volumeName, "v", "NO.NAME", "Specifiy a name for the volume from 1 to 15 characters") flag.IntVar(&blockNumber, "b", 0, "A block number to read/write from 0 to 65535 (0x0000 to 0xFFFF hex input accepted)") - flag.IntVar(&fileType, "t", 6, "ProDOS FileType: 0x04 for TXT, 0x06 for BIN, 0xFA for BAS, 0xFF for SYS etc.") + flag.IntVar(&fileType, "t", 6, "ProDOS FileType: 0x04 for TXT, 0x06 for BIN, 0xFC for BAS, 0xFF for SYS etc.") flag.IntVar(&auxType, "a", 0x2000, "ProDOS AuxType from 0 to 65535 (0x0000 to 0xFFFF hex input accepted)") flag.Parse() @@ -117,9 +117,15 @@ func main() { fmt.Printf("Failed to open input file %s: %s", inFileName, err) os.Exit(1) } + if strings.HasSuffix(strings.ToLower(inFileName), ".bas") { + inFile, err = prodos.ConvertTextToBasic(string(inFile)) + fileType = 0xFC + auxType = 0x0801 + } + // Check for an AppleSingle file as produced by cc65 - if // Magic number - binary.BigEndian.Uint32(inFile[0x00:]) == 0x00051600 && + if // Magic number + binary.BigEndian.Uint32(inFile[0x00:]) == 0x00051600 && // Version number binary.BigEndian.Uint32(inFile[0x04:]) == 0x00020000 && // Number of entries @@ -129,7 +135,7 @@ func main() { // Offset binary.BigEndian.Uint32(inFile[0x1E:]) == 0x0000003A && // Length - binary.BigEndian.Uint32(inFile[0x22:]) == uint32(len(inFile)) - 0x3A && + binary.BigEndian.Uint32(inFile[0x22:]) == uint32(len(inFile))-0x3A && // ProDOS File Info ID binary.BigEndian.Uint32(inFile[0x26:]) == 0x0000000B && // Offset diff --git a/prodos/basic.go b/prodos/basic.go index d5daf8f..953d784 100644 --- a/prodos/basic.go +++ b/prodos/basic.go @@ -7,7 +7,10 @@ package prodos import ( + "bytes" + "errors" "fmt" + "strconv" "strings" ) @@ -88,7 +91,7 @@ var tokens = map[byte]string{ 0xC9: "-", 0xCA: "*", 0xCB: "/", - 0xCC: ";", + //0xCC: ";", // fails if this is there 0xCD: "AND", 0xCE: "OR", 0xCF: ">", @@ -161,95 +164,123 @@ func ConvertBasicToText(basic []byte) string { } } -// func ConvertTextToBasic(text string) ([]byte, error) { -// // convert line endings -// text = strings.Replace(text, "\r\n", "\n", -1) -// text = strings.Replace(text, "\r", "\n", -1) +func ConvertTextToBasic(text string) ([]byte, error) { + // convert line endings + text = strings.Replace(text, "\r\n", "\n", -1) + text = strings.Replace(text, "\r", "\n", -1) -// const startState = 0 -// const lineNumberState = 1 -// const tokenCheckState = 2 -// const literalState = 3 -// const stringState = 4 -// const dataState = 5 -// const endOfLineState = 6 + starting := true + parsingLineNumber := false + parsingData := false + parsingString := false + parsingRem := false + foundToken := false -// state := startState + currentByte := 0x0801 + var lineNumberString string -// currentByte := 0x0801 -// var lineNumberString string -// var tokenString string + basicFile := new(bytes.Buffer) + basicLine := new(bytes.Buffer) -// basicFile := new(bytes.Buffer) -// basicLine := new(bytes.Buffer) + skipChars := 0 -// // parse character by character -// for _, c := range text { + // parse character by character + for index, c := range text { -// // skip initial whitespace and look for the start of a line number -// if state == startState { -// if c == ' ' { -// continue -// } -// if c >= '0' && c <= '9' { -// state = lineNumberState -// } else { -// return nil, errors.New("unexpected character before line number") -// } -// } + // skip initial whitespace and look for the start of a line number + if starting { + if c == ' ' { + continue + } + if c >= '0' && c <= '9' { + starting = false + parsingLineNumber = true + } else { + return nil, errors.New("unexpected character before line number") + } + } -// // parse line number -// if state == lineNumberState { -// if c >= '0' && c <= '9' { -// lineNumberString += string(c) -// } else { -// lineNumber, err := strconv.ParseUint(lineNumberString, 10, 16) -// if err != nil { -// return nil, err -// } -// basicLine.WriteByte(byte(lineNumber % 256)) // low byte -// basicLine.WriteByte(byte(lineNumber / 256)) // high byte -// tokenString = "" -// tokenByte = 0 -// state = tokenCheckState -// } -// } + if skipChars > 0 && c != '\n' { + skipChars-- + continue + } -// if state == tokenCheckState { -// // skip initial whitespace -// if c == ' ' && len(tokenString) == 0 { -// continue -// } + // parse line number + if parsingLineNumber { + if c >= '0' && c <= '9' { + lineNumberString += string(c) + } else { + lineNumber, err := strconv.ParseUint(lineNumberString, 10, 16) + if err != nil { + return nil, err + } + basicLine.WriteByte(byte(lineNumber % 256)) // low byte + basicLine.WriteByte(byte(lineNumber / 256)) // high byte + parsingLineNumber = false + lineNumberString = "" + } + } -// // finish parsing token if -// if c == '\n' { -// state = endOfLineState -// } else if c == '"' { -// state = stringState -// } + if !parsingLineNumber { + if c == '\n' { + starting = true + parsingLineNumber = false + parsingData = false + parsingRem = false + parsingString = false + foundToken = false + currentByte += basicLine.Len() + currentByte += 3 + // write address of next line + basicFile.WriteByte(byte(currentByte % 256)) + basicFile.WriteByte(byte(currentByte / 256)) + // write the line + basicFile.Write(basicLine.Bytes()) + basicFile.WriteByte(0x00) + basicLine.Reset() + } else if parsingData { + basicLine.WriteByte(byte(c)) + } else if parsingRem { + basicLine.WriteByte(byte(c)) + } else if parsingString { + basicLine.WriteByte(byte(c)) + if c == '"' { + parsingString = false + } + } else if c == '"' { + parsingString = true + basicLine.WriteByte(byte(c)) + } else { + if c == ' ' { + continue + } -// } -// } + for key, token := range tokens { + if index < len(text)-len(token) { + if text[index:index+len(token)] == token { + basicLine.WriteByte(byte(key)) + skipChars = len(token) + foundToken = true + if key == 0x83 { + parsingData = true + } + if key == 0xB2 { + parsingRem = true + } + } + } + } -// return basicFile.Bytes(), nil -// } + if foundToken { + foundToken = false + } else { + basicLine.WriteByte(byte(c)) + } + } + } + } + basicFile.WriteByte(0x00) + basicFile.WriteByte(0x00) -// func writeTokenOrBytes(parseString string, basicBytes []byte) bool { -// if len(parseString) == 0 { -// return false -// } - -// upperToken := strings.ToUpper(parseString) - -// for tokenByte, token := range tokens { -// if upperToken == token { -// return tokenByte -// } -// } - -// if tokenByte > 0 { -// basicBytes.WriteByte(tokenByte) -// } - -// return 0 -// } + return basicFile.Bytes(), nil +} diff --git a/prodos/basic_test.go b/prodos/basic_test.go new file mode 100644 index 0000000..55d95b4 --- /dev/null +++ b/prodos/basic_test.go @@ -0,0 +1,76 @@ +package prodos + +import ( + "bytes" + "testing" +) + +func TestConvertBasicToText(t *testing.T) { + var tests = []struct { + name string + basic []byte + want string + }{ + { + "Simple", + []byte{ + 0x14, 0x08, 0x0A, 0x00, 0xBA, 0x22, 0x48, 0x45, 0x4C, 0x4C, 0x4F, 0x20, 0x57, 0x4F, 0x52, 0x4C, 0x44, 0x22, 0x00, + 0x1A, 0x08, 0x14, 0x00, 0x80, 0x00, + 0x00, 0x00}, + "10 PRINT \"HELLO WORLD\"\n20 END \n"}, + } + + for _, tt := range tests { + testname := tt.name + t.Run(testname, func(t *testing.T) { + text := ConvertBasicToText(tt.basic) + if text != tt.want { + t.Errorf("%s\ngot '%#v'\nwant '%#v'\n", testname, []byte(text), []byte(tt.want)) + } + }) + } +} + +func TestConvertTextToBasic(t *testing.T) { + var tests = []struct { + name string + basicText string + want []byte + }{ + { + "Hello world", + "10 PRINT \"HELLO WORLD\"\n20 END \n", + []byte{ + 0x14, 0x08, 0x0A, 0x00, 0xBA, 0x22, 0x48, 0x45, 0x4C, 0x4C, 0x4F, 0x20, 0x57, 0x4F, 0x52, 0x4C, 0x44, 0x22, 0x00, + 0x1A, 0x08, 0x14, 0x00, 0x80, 0x00, + 0x00, 0x00}}, + { + "Variables", + "10 A = 1\n", + []byte{ + 0x09, 0x08, 0x0A, 0x00, 0x41, 0xD0, 0x31, 0x00, + 0x00, 0x00}}, + { + "Rem", + "10 REM x y z\n", + []byte{ + 0x0C, 0x08, 0x0A, 0x00, 0xB2, 0x78, 0x20, 0x79, 0x20, 0x7A, 0x00, + 0x00, 0x00}}, + { + "Punctuation", + "1 PRINT ;: PRINT\n", + []byte{ + 0x0A, 0x08, 0x01, 0x00, 0xBA, 0x3B, 0x3A, 0xBA, 0x00, + 0x00, 0x00}}, + } + + for _, tt := range tests { + testname := tt.name + t.Run(testname, func(t *testing.T) { + basic, _ := ConvertTextToBasic(tt.basicText) + if bytes.Compare(basic, tt.want) != 0 { + t.Errorf("%s\ngot '%#v'\nwant '%#v'\n", testname, basic, tt.want) + } + }) + } +}