mirror of
https://github.com/tjboldt/ProDOS-Utilities.git
synced 2024-11-28 12:51:35 +00:00
98ab2d4f0a
* Fix #27 basic parsing * Update version
292 lines
5.2 KiB
Go
292 lines
5.2 KiB
Go
// Copyright Terence J. Boldt (c)2021-2023
|
|
// Use of this source code is governed by an MIT
|
|
// license that can be found in the LICENSE file.
|
|
|
|
// This file provides conversion between BASIC and text
|
|
|
|
package prodos
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
var tokens = map[byte]string{
|
|
0x80: "END",
|
|
0x81: "FOR",
|
|
0x82: "NEXT",
|
|
0x83: "DATA",
|
|
0x84: "INPUT",
|
|
0x85: "DEL",
|
|
0x86: "DIM",
|
|
0x87: "READ",
|
|
0x88: "GR",
|
|
0x89: "TEXT",
|
|
0x8A: "PR#",
|
|
0x8B: "IN#",
|
|
0x8C: "CALL",
|
|
0x8D: "PLOT",
|
|
0x8E: "HLIN",
|
|
0x8F: "VLIN",
|
|
0x90: "HGR2",
|
|
0x91: "HGR",
|
|
0x92: "HCOLOR=",
|
|
0x93: "HPLOT",
|
|
0x94: "DRAW",
|
|
0x95: "XDRAW",
|
|
0x96: "HTAB",
|
|
0x97: "HOME",
|
|
0x98: "ROT=",
|
|
0x99: "SCALE=",
|
|
0x9A: "SHLOAD",
|
|
0x9B: "TRACE",
|
|
0x9C: "NOTRACE",
|
|
0x9D: "NORMAL",
|
|
0x9E: "INVERSE",
|
|
0x9F: "FLASH",
|
|
0xA0: "COLOR=",
|
|
0xA1: "POP",
|
|
0xA2: "VTAB",
|
|
0xA3: "HIMEM:",
|
|
0xA4: "LOMEM:",
|
|
0xA5: "ONERR",
|
|
0xA6: "RESUME",
|
|
0xA7: "RECALL",
|
|
0xA8: "STORE",
|
|
0xA9: "SPEED=",
|
|
0xAA: "LET",
|
|
0xAB: "GOTO",
|
|
0xAC: "RUN",
|
|
0xAD: "IF",
|
|
0xAE: "RESTORE",
|
|
0xAF: "&",
|
|
0xB0: "GOSUB",
|
|
0xB1: "RETURN",
|
|
0xB2: "REM",
|
|
0xB3: "STOP",
|
|
0xB4: "ON",
|
|
0xB5: "WAIT",
|
|
0xB6: "LOAD",
|
|
0xB7: "SAVE",
|
|
0xB8: "DEF FN",
|
|
0xB9: "POKE",
|
|
0xBA: "PRINT",
|
|
0xBB: "CONT",
|
|
0xBC: "LIST",
|
|
0xBD: "CLEAR",
|
|
0xBE: "GET",
|
|
0xBF: "NEW",
|
|
0xC0: "TAB",
|
|
0xC1: "TO",
|
|
0xC2: "FN",
|
|
0xC3: "SPC(",
|
|
0xC4: "THEN",
|
|
0xC5: "AT",
|
|
0xC6: "NOT",
|
|
0xC7: "STEP",
|
|
0xC8: "+",
|
|
0xC9: "-",
|
|
0xCA: "*",
|
|
0xCB: "/",
|
|
//0xCC: ";", // fails if this is there
|
|
0xCD: "AND",
|
|
0xCE: "OR",
|
|
0xCF: ">",
|
|
0xD0: "=",
|
|
0xD1: "<",
|
|
0xD2: "SGN",
|
|
0xD3: "INT",
|
|
0xD4: "ABS",
|
|
0xD5: "USR",
|
|
0xD6: "FRE",
|
|
0xD7: "SCRN(",
|
|
0xD8: "PDL",
|
|
0xD9: "POS",
|
|
0xDA: "SQR",
|
|
0xDB: "RND",
|
|
0xDC: "LOG",
|
|
0xDD: "EXP",
|
|
0xDE: "COS",
|
|
0xDF: "SIN",
|
|
0xE0: "TAN",
|
|
0xE1: "ATN",
|
|
0xE2: "PEEK",
|
|
0xE3: "LEN",
|
|
0xE4: "STR$",
|
|
0xE5: "VAL",
|
|
0xE6: "ASC",
|
|
0xE7: "CHR$",
|
|
0xE8: "LEFT$",
|
|
0xE9: "RIGHT$",
|
|
0xEA: "MID$",
|
|
}
|
|
|
|
// ConvertBasicToText converts AppleSoft BASIC to text
|
|
func ConvertBasicToText(basic []byte) string {
|
|
var builder strings.Builder
|
|
|
|
i := 0
|
|
|
|
for {
|
|
lo := basic[i]
|
|
i++
|
|
hi := basic[i]
|
|
i++
|
|
|
|
if lo == 0 && hi == 0 {
|
|
return builder.String()
|
|
}
|
|
|
|
line := int(basic[i]) + int(basic[i+1])*256
|
|
i += 2
|
|
|
|
fmt.Fprintf(&builder, "%d ", line)
|
|
|
|
for {
|
|
t := basic[i]
|
|
if t == 0 {
|
|
builder.WriteString("\n")
|
|
i++
|
|
break
|
|
}
|
|
if t > 127 {
|
|
builder.WriteString(" ")
|
|
builder.WriteString(tokens[t])
|
|
builder.WriteString(" ")
|
|
} else {
|
|
builder.WriteString(string(t))
|
|
}
|
|
i++
|
|
}
|
|
}
|
|
}
|
|
|
|
// ConvertTextToBasic converts text to AppleSoft BASIC
|
|
func ConvertTextToBasic(text string) ([]byte, error) {
|
|
// convert line endings
|
|
text = strings.Replace(text, "\r\n", "\n", -1)
|
|
text = strings.Replace(text, "\r", "\n", -1)
|
|
|
|
starting := true
|
|
parsingLineNumber := false
|
|
parsingData := false
|
|
parsingString := false
|
|
parsingRem := false
|
|
foundToken := false
|
|
|
|
currentByte := 0x0801
|
|
var lineNumberString string
|
|
|
|
basicFile := new(bytes.Buffer)
|
|
basicLine := new(bytes.Buffer)
|
|
|
|
skipChars := 0
|
|
|
|
// parse character by character
|
|
for index, c := range text {
|
|
|
|
// skip initial whitespace and look for the start of a line number
|
|
if starting {
|
|
if c == '\n' { // skip blank lines
|
|
continue
|
|
}
|
|
if c == ' ' {
|
|
continue
|
|
}
|
|
if c >= '0' && c <= '9' {
|
|
starting = false
|
|
parsingLineNumber = true
|
|
} else {
|
|
return nil, errors.New("unexpected character before line number")
|
|
}
|
|
}
|
|
|
|
if skipChars > 0 && c != '\n' {
|
|
skipChars--
|
|
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 = ""
|
|
}
|
|
}
|
|
|
|
if !parsingLineNumber {
|
|
if c == '\n' {
|
|
starting = true
|
|
parsingLineNumber = false
|
|
parsingData = false
|
|
parsingRem = false
|
|
parsingString = false
|
|
foundToken = false
|
|
skipChars = 0
|
|
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
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if foundToken {
|
|
foundToken = false
|
|
} else {
|
|
basicLine.WriteByte(byte(c))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
basicFile.WriteByte(0x00)
|
|
basicFile.WriteByte(0x00)
|
|
|
|
return basicFile.Bytes(), nil
|
|
}
|