1
0
Fork 0
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:
Knut Ahlers 2019-11-05 23:00:57 +01:00
parent 2812974bc6
commit 995cb1dafa
Signed by: luzifer
GPG key ID: DC2729FDD34BE99E
6 changed files with 269 additions and 11 deletions

23
block_save_container.go Normal file
View 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
View 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
View 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
View 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
View file

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

15
unit.go
View file

@ -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 {
@ -16,14 +17,18 @@ type Unmarshaler interface {
type RawBlock struct { 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
} }