Add stubbed-out delete, supermon symbol encoding

This commit is contained in:
Zellyn Hunter 2016-11-22 22:38:20 -05:00
parent e6508a39b4
commit 0fdf6f05c4
7 changed files with 203 additions and 7 deletions

68
cmd/delete.go Normal file
View File

@ -0,0 +1,68 @@
// Copyright © 2016 Zellyn Hunter <zellyn@gmail.com>
package cmd
import (
"fmt"
"os"
"github.com/spf13/cobra"
"github.com/zellyn/diskii/lib/disk"
_ "github.com/zellyn/diskii/lib/dos3"
_ "github.com/zellyn/diskii/lib/supermon"
)
// deleteCmd represents the delete command, used to delete a file.
var deleteCmd = &cobra.Command{
Use: "delete",
Short: "delete a file",
Long: `Delete a file.
delete disk-image.dsk HELLO
`,
Run: func(cmd *cobra.Command, args []string) {
if err := runDelete(args); err != nil {
fmt.Fprintln(os.Stderr, err.Error())
os.Exit(-1)
}
},
}
func init() {
RootCmd.AddCommand(deleteCmd)
}
// runDelete performs the actual delete logic.
func runDelete(args []string) error {
if len(args) != 2 {
return fmt.Errorf("delete expects a disk image filename, and a filename")
}
sd, err := disk.Open(args[0])
if err != nil {
return err
}
op, err := disk.OperatorFor(sd)
if err != nil {
return err
}
deleted, err := op.Delete(args[1])
if err != nil {
return err
}
if !deleted {
// TODO(zellyn): implement -f flag to not warn on nonexistence.
return fmt.Errorf("file %q not found", args[1])
}
f, err := os.Create(args[0])
if err != nil {
return err
}
_, err = sd.Write(f)
if err != nil {
return err
}
if err = f.Close(); err != nil {
return err
}
return nil
}

View File

@ -6,6 +6,7 @@ package disk
import ( import (
"fmt" "fmt"
"io"
"path" "path"
"strings" "strings"
) )
@ -49,6 +50,8 @@ type SectorDisk interface {
Sectors() byte Sectors() byte
// Tracks returns the number of tracks on the SectorDisk // Tracks returns the number of tracks on the SectorDisk
Tracks() byte Tracks() byte
// Write writes the disk contents to the given file.
Write(io.Writer) (int, error)
} }
type LogicalSectorDisk interface { type LogicalSectorDisk interface {
@ -62,6 +65,8 @@ type LogicalSectorDisk interface {
Sectors() byte Sectors() byte
// Tracks returns the number of tracks on the SectorDisk // Tracks returns the number of tracks on the SectorDisk
Tracks() byte Tracks() byte
// Write writes the disk contents to the given file.
Write(io.Writer) (int, error)
} }
// MappedDisk wraps a SectorDisk as a LogicalSectorDisk, handling the // MappedDisk wraps a SectorDisk as a LogicalSectorDisk, handling the
@ -125,6 +130,11 @@ func (md MappedDisk) Tracks() byte {
return md.sectorDisk.Tracks() return md.sectorDisk.Tracks()
} }
// Write writes the disk contents to the given file.
func (md MappedDisk) Write(w io.Writer) (n int, err error) {
return md.sectorDisk.Write(w)
}
// Open opens a disk image by filename. // Open opens a disk image by filename.
func Open(filename string) (SectorDisk, error) { func Open(filename string) (SectorDisk, error) {
ext := strings.ToLower(path.Ext(filename)) ext := strings.ToLower(path.Ext(filename))

View File

@ -6,6 +6,7 @@ package disk
import ( import (
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
) )
@ -84,3 +85,8 @@ func (d DSK) Sectors() byte {
func (d DSK) Tracks() byte { func (d DSK) Tracks() byte {
return d.tracks return d.tracks
} }
// Write writes the disk contents to the given file.
func (d DSK) Write(w io.Writer) (n int, err error) {
return w.Write(d.data)
}

View File

@ -83,6 +83,9 @@ type Operator interface {
Catalog(subdir string) ([]Descriptor, error) Catalog(subdir string) ([]Descriptor, error)
// GetFile retrieves a file by name. // GetFile retrieves a file by name.
GetFile(filename string) (FileInfo, error) GetFile(filename string) (FileInfo, error)
// Delete deletes a file by name. It returns true if the file was
// deleted, false if it didn't exist.
Delete(filename string) (bool, error)
} }
// FileInfo represents a file descriptor plus the content. // FileInfo represents a file descriptor plus the content.

View File

@ -651,6 +651,12 @@ func (o operator) GetFile(filename string) (disk.FileInfo, error) {
return disk.FileInfo{}, fmt.Errorf("%s does not yet implement `GetFile` for filetype %s", operatorName, errType) return disk.FileInfo{}, fmt.Errorf("%s does not yet implement `GetFile` for filetype %s", operatorName, errType)
} }
// Delete deletes a file by name. It returns true if the file was
// deleted, false if it didn't exist.
func (o operator) Delete(filename string) (bool, error) {
return false, fmt.Errorf("%s does not implement Delete yet", operatorName)
}
// operatorFactory is the factory that returns dos3 operators given // operatorFactory is the factory that returns dos3 operators given
// disk images. // disk images.
func operatorFactory(sd disk.SectorDisk) (disk.Operator, error) { func operatorFactory(sd disk.SectorDisk) (disk.Operator, error) {

View File

@ -4,9 +4,8 @@
// structures of NakedOS/Super-Mon disks. // structures of NakedOS/Super-Mon disks.
package supermon package supermon
// TODO(zellyn): remove panics.
import ( import (
"encoding/binary"
"fmt" "fmt"
"strconv" "strconv"
"strings" "strings"
@ -152,6 +151,44 @@ func decodeSymbol(five []byte, extra byte) string {
return result return result
} }
// encodeSymbol encodes a symbol name into the five+1 bytes used in a
// Super-Mon encoded symbol table entry. The returned byte array will
// always be six bytes long. If it can't be encoded, it returns an
// error.
func encodeSymbol(name string) (six []byte, err error) {
if len(name) > 9 {
return nil, fmt.Errorf("invalid Super-Mon symbol %q: too long", name)
}
if len(name) < 3 {
return nil, fmt.Errorf("invalid Super-Mon symbol %q: too short", name)
}
nm := []byte(strings.ToUpper(name))
value := uint64(0)
bits := 0
for i := len(nm) - 1; i >= 0; i-- {
ch := nm[i]
switch {
case 'A' <= ch && ch <= 'Z':
value = value<<5 + uint64(ch-'@')
bits += 5
case '0' <= ch && ch <= '4':
value = value<<6 + 0x1b + uint64(ch-'0')
bits += 6
case '5' <= ch && ch <= '9':
value = value<<6 + 0x3b + uint64(ch-'5')
bits += 6
}
if bits > 48 {
return nil, fmt.Errorf("invalid Super-Mon symbol %q: too long", name)
}
}
eight := make([]byte, 8)
six = make([]byte, 6)
binary.LittleEndian.PutUint64(eight, value)
copy(six, eight)
return six, nil
}
// SymbolTable represents an entire Super-Mon symbol table. It'll // SymbolTable represents an entire Super-Mon symbol table. It'll
// always be 819 entries long, because it includes blanks. // always be 819 entries long, because it includes blanks.
type SymbolTable []Symbol type SymbolTable []Symbol
@ -192,7 +229,7 @@ func (sm SectorMap) ReadSymbolTable(sd disk.SectorDisk) (SymbolTable, error) {
if (linkAddr-0xD000)%5 != 0 { if (linkAddr-0xD000)%5 != 0 {
return nil, fmt.Errorf("Expected symbol table link address to 0xD000+5x; got 0x%04X", linkAddr) return nil, fmt.Errorf("Expected symbol table link address to 0xD000+5x; got 0x%04X", linkAddr)
} }
link = (int(linkAddr) - 0xD000) % 5 link = (int(linkAddr) - 0xD000) / 5
} }
extra := symtbl1[i+4] extra := symtbl1[i+4]
copy(five, symtbl2[i:i+5]) copy(five, symtbl2[i:i+5])
@ -205,11 +242,27 @@ func (sm SectorMap) ReadSymbolTable(sd disk.SectorDisk) (SymbolTable, error) {
table = append(table, symbol) table = append(table, symbol)
} }
// TODO(zellyn): check link addresses. for i, sym := range table {
if sym.Address != 0 && sym.Link != -1 {
if sym.Link == i {
return nil, fmt.Errorf("Symbol %q (0x%04X) links to itself", sym.Name, sym.Address)
}
linkSym := table[sym.Link]
if addrHash(sym.Address) != addrHash(linkSym.Address) {
return nil, fmt.Errorf("Symbol %q (0x%04X) with hash 0x%02X links to symbol %q (0x%04X) with hash 0x%02X",
sym.Name, sym.Address, addrHash(sym.Address), linkSym.Name, linkSym.Address, addrHash(linkSym.Address))
}
}
}
return table, nil return table, nil
} }
// addrHash computes the SuperMon hash for an address.
func addrHash(addr uint16) byte {
return (byte(addr) ^ byte(addr>>8)) & 0x7f
}
// SymbolsByAddress returns a map of addresses to slices of symbols. // SymbolsByAddress returns a map of addresses to slices of symbols.
func (st SymbolTable) SymbolsByAddress() map[uint16][]Symbol { func (st SymbolTable) SymbolsByAddress() map[uint16][]Symbol {
result := map[uint16][]Symbol{} result := map[uint16][]Symbol{}
@ -319,12 +372,21 @@ func (o operator) GetFile(filename string) (disk.FileInfo, error) {
Length: len(data), Length: len(data),
Locked: false, Locked: false,
Type: disk.FiletypeBinary, Type: disk.FiletypeBinary,
// TODO(zellyn): Set StartAddress if we know it.
} }
return disk.FileInfo{ fi := disk.FileInfo{
Descriptor: desc, Descriptor: desc,
Data: data, Data: data,
}, nil }
if file == 1 {
fi.StartAddress = 0x1800
}
return fi, nil
}
// Delete deletes a file by name. It returns true if the file was
// deleted, false if it didn't exist.
func (o operator) Delete(filename string) (bool, error) {
return false, fmt.Errorf("%s does not implement Delete yet", operatorName)
} }
// operatorFactory is the factory that returns supermon operators // operatorFactory is the factory that returns supermon operators

View File

@ -3,6 +3,7 @@
package supermon package supermon
import ( import (
"strings"
"testing" "testing"
"github.com/zellyn/diskii/lib/disk" "github.com/zellyn/diskii/lib/disk"
@ -137,3 +138,43 @@ No more; and by a sleep, to say we end
t.Errorf("Incorrect result for GetFile(\"TOBE\"): want %q; got %q", want, got) t.Errorf("Incorrect result for GetFile(\"TOBE\"): want %q; got %q", want, got)
} }
} }
// TestEncodeDecode tests encoding and decoding of Super-Mon symbol
// table entries.
func TestEncodeDecode(t *testing.T) {
testdata := []struct {
sym string
valid bool
}{
{"ABC", true},
{"abc", true},
{"ABCDEFGHI", true},
{"abcdefghi", true},
{"ABCDEF123", true},
{"abcdef123", true},
{"AB", false},
{"ab", false},
{"ABCDE1234", false},
{"abcde1234", false},
}
for _, tt := range testdata {
if !tt.valid {
if _, err := encodeSymbol(tt.sym); err == nil {
t.Errorf("Expected symbol %q to be invalid, but wansn't", tt.sym)
}
continue
}
bytes, err := encodeSymbol(tt.sym)
if err != nil {
t.Errorf("Unexpected error encoding symbol %q", tt.sym)
continue
}
sym := decodeSymbol(bytes[:5], bytes[5])
if sym != strings.ToUpper(tt.sym) {
t.Errorf("Symbol %q encodes to %q", tt.sym, sym)
}
}
}