Add BASIC import (#12)

* Add BASIC import

* Fix text to BASIC when line feed after token
This commit is contained in:
Terence Boldt 2022-12-27 17:24:25 -05:00 committed by GitHub
parent a649647739
commit 48aa7a9331
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 199 additions and 86 deletions

View File

@ -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

14
main.go
View File

@ -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

View File

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

76
prodos/basic_test.go Normal file
View File

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