mirror of
https://github.com/Luzifer/sii.git
synced 2024-12-20 16:11:17 +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")
|
||||
}
|
||||
|
||||
func parseSIIPlainFile(r io.Reader) (*Unit, error) {
|
||||
// FIXME: Implement this
|
||||
return nil, errors.New("Not implemented")
|
||||
}
|
||||
|
||||
func readFTHeader(f io.ReaderAt) ([]byte, error) {
|
||||
var ftHeader = make([]byte, 4)
|
||||
if n, err := f.ReadAt(ftHeader, 0); err != nil || n != 4 {
|
||||
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")
|
||||
}
|
||||
|
|
15
unit.go
15
unit.go
|
@ -1,8 +1,9 @@
|
|||
package sii
|
||||
|
||||
type Block interface {
|
||||
Class() string
|
||||
Init(class, name string)
|
||||
Name() string
|
||||
Type() string
|
||||
}
|
||||
|
||||
type Marshaler interface {
|
||||
|
@ -16,14 +17,18 @@ type Unmarshaler interface {
|
|||
type RawBlock struct {
|
||||
Data []byte
|
||||
|
||||
blockName string
|
||||
blockType string
|
||||
blockName 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) Name() string { return r.blockName }
|
||||
func (r RawBlock) Type() string { return r.blockType }
|
||||
func (r *RawBlock) UnmarshalSII(blockName, blockType string, in []byte) error {
|
||||
func (r RawBlock) Class() string { return r.blockClass }
|
||||
func (r *RawBlock) UnmarshalSII(in []byte) error {
|
||||
r.Data = in
|
||||
return nil
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue