go-studybox/build-script/parser.go

181 lines
3.5 KiB
Go

package build
import (
"fmt"
"bufio"
"os"
"io"
"testing"
"strings"
"unicode"
)
type parseFunc func(key, values string) (Token, error)
var parseFuncs = map[string]parseFunc{
"rom": parseStrValue,
"fullaudio": parseStrValue,
"audiooffsets": parseAudioOffsets,
"page": parseNumValue,
"padding": parseNumValue,
"version": parseNumValue,
"delay": parseDelay,
"script": parseData,
"tiles": parseData,
"pattern": parseData,
}
func ParseFile(filename string) ([]Token, error) {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()
return Parse(file)
}
func Parse(r io.Reader) ([]Token, error) {
items := []Token{}
scanner := bufio.NewScanner(r)
prev := ""
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
// blanks and comments
if line == "" || strings.HasPrefix(line, "#") {
continue
}
splitln := strings.SplitN(line, " ", 2)
if len(splitln) != 2 {
return nil, fmt.Errorf("invalid line: %q", line)
}
var itm Token
var err error
// TODO: Some of these will need to be unique in the file
// (rom, version, fullaudio), and probably exclusive/ordered.
// IE, rom, version, & fullaudio must come before the first
// page ##, and must only appear once. if a page has an
// audio line, all pages must have one and there can be no
// fullaudio.
if fn, ok := parseFuncs[splitln[0]]; ok {
itm, err = fn(splitln[0], splitln[1])
} else {
return nil, fmt.Errorf("unknown line: %s\n", splitln[0])
}
if err != nil {
return nil, err
}
if !itm.ValidAfter(prev) {
if prev == "" {
prev = "[empty]"
}
return nil, fmt.Errorf("%s not valid after %s", itm.Type(), prev)
}
prev = itm.Type()
items = append(items, itm)
}
return items, nil
}
var t *testing.T
func pKeyVals(values string) (map[string]string, error) {
m := map[string]string{}
start := 0
runes := []rune(values)
currentKey := ""
quote := false
tlog("[pKeyVals] start")
var i int
for i = 0; i < len(runes); i++ {
tlogf("[pKeyVals] rune: %c\n", runes[i])
if !quote && runes[i] == '"' {
quote = true
tlog("[pKeyVals] start quote")
continue
}
if !quote && unicode.IsSpace(runes[i]) {
tlog("[pKeyVals] !quote && IsSpace()")
if currentKey == "" {
tlog("[pKeyVals] currentKey empty")
start = i+1
} else {
tlog("[pKeyVals] currentKey not empty")
m[currentKey] = string(runes[start:i])
currentKey = ""
start = i+1
}
continue
}
if quote && runes[i] == '"' {
tlog("[pKeyVals] quote && rune == \"")
quote = false
if currentKey == "" {
currentKey = string(runes[start+1:i])
i++
start = i+1
tlogf("[pKeyVals] currentKey empty; set to %s\n", currentKey)
} else {
m[currentKey] = string(runes[start+1:i])
currentKey = ""
tlog("[pKeyVals] currentKey not empty")
}
continue
}
if runes[i] == ':' {
tlog("[pKeyVals] rune == :")
if i == start {
return nil, fmt.Errorf("missing key")
}
currentKey = string(runes[start:i])
start = i+1
continue
}
}
if quote {
return nil, fmt.Errorf("missmatched quote")
}
if currentKey != "" {
//return nil, fmt.Errorf("missing value for %q", currentKey)
tlogf("[pKeyVals] outside loop assign m[%s] = %s\n", currentKey, string(runes[start:i]))
m[currentKey] = string(runes[start:i])
}
return m, nil
}
func tlog(args ...any) {
if t != nil {
t.Log(args...)
}
}
func tlogf(format string, args ...any) {
if t != nil {
t.Logf(format, args...)
}
}