mirror of
https://github.com/Luzifer/sii.git
synced 2024-12-21 00:21:15 +00:00
Implement basic parser
Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
parent
2812974bc6
commit
995cb1dafa
6 changed files with 269 additions and 11 deletions
23
block_save_container.go
Normal file
23
block_save_container.go
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
package sii
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterBlock(&SaveContainer{})
|
||||||
|
}
|
||||||
|
|
||||||
|
type SaveContainer struct {
|
||||||
|
SaveName string `sii:"name"`
|
||||||
|
Time int64 `sii:"time"`
|
||||||
|
FileTime uint64 `sii:"file_time"`
|
||||||
|
Version int `sii:"version"`
|
||||||
|
Dependencies []string `sii:"dependencies"`
|
||||||
|
|
||||||
|
blockName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (SaveContainer) Class() string { return "save_container" }
|
||||||
|
|
||||||
|
func (s *SaveContainer) Init(class, name string) {
|
||||||
|
s.blockName = name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s SaveContainer) Name() string { return s.blockName }
|
138
parser.go
Normal file
138
parser.go
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
package sii
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var blockStartRegex = regexp.MustCompile(`^([^\s:]+)\s?:\s?([^\s]+)(?:\s?\{)?$`)
|
||||||
|
|
||||||
|
func parseSIIPlainFile(r io.Reader) (*Unit, error) {
|
||||||
|
var (
|
||||||
|
blockContent []byte
|
||||||
|
blockName string
|
||||||
|
blockClass string
|
||||||
|
inBlock = false
|
||||||
|
inComment = false
|
||||||
|
inUnit = false
|
||||||
|
scanner = bufio.NewScanner(r)
|
||||||
|
unit = &Unit{}
|
||||||
|
)
|
||||||
|
|
||||||
|
for scanner.Scan() {
|
||||||
|
var line = strings.TrimSpace(scanner.Text())
|
||||||
|
|
||||||
|
switch {
|
||||||
|
|
||||||
|
case line == "{":
|
||||||
|
if !inUnit {
|
||||||
|
inUnit = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !inBlock {
|
||||||
|
if blockClass == "" || blockName == "" {
|
||||||
|
return nil, errors.New("Unpexpected block open without unit class / name")
|
||||||
|
}
|
||||||
|
|
||||||
|
inBlock = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("Unexpected opening braces")
|
||||||
|
|
||||||
|
case line == "}":
|
||||||
|
if inBlock {
|
||||||
|
if err := processBlock(unit, blockClass, blockName, blockContent); err != nil {
|
||||||
|
return nil, errors.Wrap(err, "Unable to process block")
|
||||||
|
}
|
||||||
|
|
||||||
|
inBlock = false
|
||||||
|
blockClass = ""
|
||||||
|
blockName = ""
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if inUnit {
|
||||||
|
inUnit = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("Unexpected closing braces")
|
||||||
|
|
||||||
|
case blockStartRegex.MatchString(line) && !inBlock:
|
||||||
|
if !inUnit {
|
||||||
|
return nil, errors.New("Unexpected block start outside unit")
|
||||||
|
}
|
||||||
|
|
||||||
|
groups := blockStartRegex.FindStringSubmatch(line)
|
||||||
|
|
||||||
|
blockClass = groups[1]
|
||||||
|
blockName = groups[2]
|
||||||
|
|
||||||
|
if strings.HasSuffix(line, `{`) {
|
||||||
|
inBlock = true
|
||||||
|
}
|
||||||
|
|
||||||
|
case (strings.HasPrefix(line, `\*`) && strings.HasSuffix(line, `*\`)) || strings.HasPrefix(line, `#`) || strings.HasPrefix(line, `//`):
|
||||||
|
// one-line-comment, just drop
|
||||||
|
|
||||||
|
case strings.HasPrefix(line, `/*`):
|
||||||
|
inComment = true
|
||||||
|
|
||||||
|
case strings.HasSuffix(line, `*/`):
|
||||||
|
inComment = false
|
||||||
|
|
||||||
|
default:
|
||||||
|
if inComment {
|
||||||
|
// Inside multi-line-comment, just drop
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !inBlock {
|
||||||
|
// Outside block, drop line
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append line to block content
|
||||||
|
blockContent = bytes.Join([][]byte{
|
||||||
|
blockContent,
|
||||||
|
scanner.Bytes(),
|
||||||
|
}, []byte{'\n'})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if scanner.Err() != nil {
|
||||||
|
return nil, errors.Wrap(scanner.Err(), "Unable to scan file")
|
||||||
|
}
|
||||||
|
|
||||||
|
return unit, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func processBlock(unit *Unit, blockClass, blockName string, blockContent []byte) error {
|
||||||
|
block := getBlockInstance(blockClass)
|
||||||
|
block.Init(blockClass, blockName)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if reflect.TypeOf(block).Implements(reflect.TypeOf((*Unmarshaler)(nil)).Elem()) {
|
||||||
|
err = block.(Unmarshaler).UnmarshalSII(blockContent)
|
||||||
|
} else {
|
||||||
|
// TODO: Add generic unmarshal
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "Unable to unmarshal block content")
|
||||||
|
}
|
||||||
|
|
||||||
|
unit.Entries = append(unit.Entries, block)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
64
parser_test.go
Normal file
64
parser_test.go
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
package sii
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var testSii = `SiiNunit
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Multi-line comment
|
||||||
|
*/
|
||||||
|
some_unit : .my_mod.unit
|
||||||
|
{
|
||||||
|
// Single line comment
|
||||||
|
# Single line comment
|
||||||
|
/*
|
||||||
|
* In-Block multi-line string
|
||||||
|
*/
|
||||||
|
attribute_number: 40
|
||||||
|
attribute_string: "TEST STRING"
|
||||||
|
attribute_unit: test.unit
|
||||||
|
attribute_float3: (1.0, 1.0, 1.0)
|
||||||
|
attribute_float_number_ieee754: &40490f5a
|
||||||
|
}
|
||||||
|
|
||||||
|
save_container : _nameless.1c57,b4b0 {
|
||||||
|
name: ""
|
||||||
|
time: 96931
|
||||||
|
file_time: 1572907597
|
||||||
|
version: 42
|
||||||
|
dependencies: 14
|
||||||
|
dependencies[0]: "mod|promods-assets-v242|ProMods Assets Package"
|
||||||
|
dependencies[1]: "mod|promods-model1-v242|ProMods Models Package 1"
|
||||||
|
dependencies[2]: "mod|promods-model2-v242|ProMods Models Package 2"
|
||||||
|
dependencies[3]: "mod|promods-model3-v242|ProMods Models Package 3"
|
||||||
|
dependencies[4]: "mod|promods-media-v242|ProMods Media Package"
|
||||||
|
dependencies[5]: "mod|promods-map-v242|ProMods Map Package"
|
||||||
|
dependencies[6]: "mod|promods-def-v242|ProMods Definition Package"
|
||||||
|
dependencies[7]: "dlc|eut2_balt|DLC - Beyond the Baltic Sea"
|
||||||
|
dependencies[8]: "dlc|eut2_east|DLC - Going East!"
|
||||||
|
dependencies[9]: "dlc|eut2_fr|DLC - Vive la France !"
|
||||||
|
dependencies[10]: "dlc|eut2_it|DLC - Italia"
|
||||||
|
dependencies[11]: "rdlc|eut2_metallics|DLC - Metallic Paint Jobs"
|
||||||
|
dependencies[12]: "dlc|eut2_north|DLC - Scandinavia"
|
||||||
|
dependencies[13]: "rdlc|eut2_rocket_league|DLC - Rocket League"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
func TestParseUnit(t *testing.T) {
|
||||||
|
unit, err := parseSIIPlainFile(strings.NewReader(testSii))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parseSIIPlainFile caused an error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(unit.Entries) != 2 {
|
||||||
|
t.Errorf("Expected 1 block, got %d", len(unit.Entries))
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("%#v", unit)
|
||||||
|
t.Logf("%#v", unit.Entries[0])
|
||||||
|
t.Logf("%#v", unit.Entries[1])
|
||||||
|
}
|
33
registry.go
Normal file
33
registry.go
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
package sii
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
blockClass = map[string]reflect.Type{}
|
||||||
|
blockClassLock = new(sync.RWMutex)
|
||||||
|
defaultBlockType = reflect.TypeOf(RawBlock{})
|
||||||
|
)
|
||||||
|
|
||||||
|
func RegisterBlock(b Block) {
|
||||||
|
blockClassLock.Lock()
|
||||||
|
defer blockClassLock.Unlock()
|
||||||
|
|
||||||
|
blockClass[b.Class()] = reflect.TypeOf(b).Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBlockInstance(t string) Block {
|
||||||
|
blockClassLock.RLock()
|
||||||
|
defer blockClassLock.RUnlock()
|
||||||
|
|
||||||
|
if rt, ok := blockClass[t]; ok {
|
||||||
|
v := reflect.New(rt).Interface()
|
||||||
|
if b, ok := v.(Block); ok {
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return reflect.New(defaultBlockType).Interface().(Block)
|
||||||
|
}
|
7
sii.go
7
sii.go
|
@ -121,16 +121,11 @@ func WriteUnitFile(filename string, encrypt bool, data *Unit) error {
|
||||||
return errors.New("Not implemented")
|
return errors.New("Not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseSIIPlainFile(r io.Reader) (*Unit, error) {
|
|
||||||
// FIXME: Implement this
|
|
||||||
return nil, errors.New("Not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
func readFTHeader(f io.ReaderAt) ([]byte, error) {
|
func readFTHeader(f io.ReaderAt) ([]byte, error) {
|
||||||
var ftHeader = make([]byte, 4)
|
var ftHeader = make([]byte, 4)
|
||||||
if n, err := f.ReadAt(ftHeader, 0); err != nil || n != 4 {
|
if n, err := f.ReadAt(ftHeader, 0); err != nil || n != 4 {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = errors.Errorf("Received %d / 4 byte header")
|
err = errors.Errorf("Received %d / 4 byte header", n)
|
||||||
}
|
}
|
||||||
return nil, errors.Wrap(err, "Unable to read 4-byte file header")
|
return nil, errors.Wrap(err, "Unable to read 4-byte file header")
|
||||||
}
|
}
|
||||||
|
|
13
unit.go
13
unit.go
|
@ -1,8 +1,9 @@
|
||||||
package sii
|
package sii
|
||||||
|
|
||||||
type Block interface {
|
type Block interface {
|
||||||
|
Class() string
|
||||||
|
Init(class, name string)
|
||||||
Name() string
|
Name() string
|
||||||
Type() string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Marshaler interface {
|
type Marshaler interface {
|
||||||
|
@ -17,13 +18,17 @@ type RawBlock struct {
|
||||||
Data []byte
|
Data []byte
|
||||||
|
|
||||||
blockName string
|
blockName string
|
||||||
blockType string
|
blockClass string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *RawBlock) Init(class, name string) {
|
||||||
|
r.blockClass = class
|
||||||
|
r.blockName = name
|
||||||
|
}
|
||||||
func (r RawBlock) MarshalSII() []byte { return r.Data }
|
func (r RawBlock) MarshalSII() []byte { return r.Data }
|
||||||
func (r RawBlock) Name() string { return r.blockName }
|
func (r RawBlock) Name() string { return r.blockName }
|
||||||
func (r RawBlock) Type() string { return r.blockType }
|
func (r RawBlock) Class() string { return r.blockClass }
|
||||||
func (r *RawBlock) UnmarshalSII(blockName, blockType string, in []byte) error {
|
func (r *RawBlock) UnmarshalSII(in []byte) error {
|
||||||
r.Data = in
|
r.Data = in
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue