mirror of
https://github.com/gogrlx/nats-server.git
synced 2026-04-02 03:38:42 -07:00
Added first pass parser
This commit is contained in:
177
conf/parse.go
Normal file
177
conf/parse.go
Normal file
@@ -0,0 +1,177 @@
|
||||
// Copyright 2013 Apcera Inc. All rights reserved.
|
||||
|
||||
// Parser will return a map of keys to interface{}, although concrete types
|
||||
// underly them. The values supported are string, bool, int64, float64, DateTime.
|
||||
// Arrays and nested Maps are also supported.
|
||||
|
||||
// The format supported is less restrictive than today's formats.
|
||||
// Supports mixed Arrays [], nexted Maps {}, multiple comment types (# and //)
|
||||
// Also supports key value assigments using '=' or ':' or whiteSpace()
|
||||
// e.g. foo = 2, foo : 2, foo 2
|
||||
// maps can be assigned with no key separator as well
|
||||
// semicolons as value terminators in key/value assignments are optional
|
||||
//
|
||||
// see parse_test.go for more examples.
|
||||
|
||||
package conf
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type parser struct {
|
||||
mapping map[string]interface{}
|
||||
lx *lexer
|
||||
|
||||
// The current scoped context, can be array or map
|
||||
ctx interface{}
|
||||
|
||||
// stack of contexts, either map or array/slice stack
|
||||
ctxs []interface{}
|
||||
|
||||
// Keys stack
|
||||
keys []string
|
||||
}
|
||||
|
||||
func Parse(data string) (map[string]interface{}, error) {
|
||||
p, err := parse(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p.mapping, nil
|
||||
}
|
||||
|
||||
func parse(data string) (p *parser, err error) {
|
||||
|
||||
p = &parser{
|
||||
mapping: make(map[string]interface{}),
|
||||
lx: lex(data),
|
||||
ctxs: make([]interface{}, 0, 4),
|
||||
keys: make([]string, 0, 4),
|
||||
}
|
||||
p.pushContext(p.mapping)
|
||||
|
||||
for {
|
||||
it := p.next()
|
||||
if it.typ == itemEOF {
|
||||
break
|
||||
}
|
||||
p.processItem(it)
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (p *parser) next() item {
|
||||
return p.lx.nextItem()
|
||||
}
|
||||
|
||||
func (p *parser) pushContext(ctx interface{}) {
|
||||
p.ctxs = append(p.ctxs, ctx)
|
||||
p.ctx = ctx
|
||||
}
|
||||
|
||||
func (p *parser) popContext() interface{} {
|
||||
if len(p.ctxs) == 0 {
|
||||
panic("BUG in parser, context stack empty")
|
||||
}
|
||||
li := len(p.ctxs) - 1
|
||||
last := p.ctxs[li]
|
||||
p.ctxs = p.ctxs[0:li]
|
||||
p.ctx = p.ctxs[len(p.ctxs)-1]
|
||||
return last
|
||||
}
|
||||
|
||||
func (p *parser) pushKey(key string) {
|
||||
p.keys = append(p.keys, key)
|
||||
}
|
||||
|
||||
func (p *parser) popKey() string {
|
||||
if len(p.keys) == 0 {
|
||||
panic("BUG in parser, keys stack empty")
|
||||
}
|
||||
li := len(p.keys) - 1
|
||||
last := p.keys[li]
|
||||
p.keys = p.keys[0:li]
|
||||
return last
|
||||
}
|
||||
|
||||
func (p *parser) processItem(it item) error {
|
||||
switch it.typ {
|
||||
case itemError:
|
||||
return fmt.Errorf("Parse error on line %d: '%s'", it.line, it.val)
|
||||
case itemKey:
|
||||
p.pushKey(it.val)
|
||||
case itemMapStart:
|
||||
newCtx := make(map[string]interface{})
|
||||
p.pushContext(newCtx)
|
||||
case itemMapEnd:
|
||||
p.setValue(p.popContext())
|
||||
case itemString:
|
||||
p.setValue(it.val) // FIXME(dlc) sanitize string?
|
||||
case itemInteger:
|
||||
num, err := strconv.ParseInt(it.val, 10, 64)
|
||||
if err != nil {
|
||||
if e, ok := err.(*strconv.NumError); ok &&
|
||||
e.Err == strconv.ErrRange {
|
||||
return fmt.Errorf("Integer '%s' is out of the range.", it.val)
|
||||
} else {
|
||||
return fmt.Errorf("Expected integer, but got '%s'.", it.val)
|
||||
}
|
||||
}
|
||||
p.setValue(num)
|
||||
case itemFloat:
|
||||
num, err := strconv.ParseFloat(it.val, 64)
|
||||
if err != nil {
|
||||
if e, ok := err.(*strconv.NumError); ok &&
|
||||
e.Err == strconv.ErrRange {
|
||||
return fmt.Errorf("Float '%s' is out of the range.", it.val)
|
||||
} else {
|
||||
return fmt.Errorf("Expected float, but got '%s'.", it.val)
|
||||
}
|
||||
}
|
||||
p.setValue(num)
|
||||
case itemBool:
|
||||
switch it.val {
|
||||
case "true":
|
||||
p.setValue(true)
|
||||
case "false":
|
||||
p.setValue(false)
|
||||
default:
|
||||
return fmt.Errorf("Expected boolean value, but got '%s'.", it.val)
|
||||
}
|
||||
case itemDatetime:
|
||||
dt, err := time.Parse("2006-01-02T15:04:05Z", it.val)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Expected Zulu formatted DateTime, but got '%s'.", it.val)
|
||||
}
|
||||
p.setValue(dt)
|
||||
case itemArrayStart:
|
||||
array := make([]interface{}, 0)
|
||||
p.pushContext(array)
|
||||
case itemArrayEnd:
|
||||
array := p.ctx
|
||||
p.popContext()
|
||||
p.setValue(array)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *parser) setValue(val interface{}) {
|
||||
// Test to see if we are on an array or a map
|
||||
|
||||
// Array processing
|
||||
if ctx, ok := p.ctx.([]interface{}); ok {
|
||||
p.ctx = append(ctx, val)
|
||||
}
|
||||
|
||||
// Map processing
|
||||
if ctx, ok := p.ctx.(map[string]interface{}); ok {
|
||||
key := p.popKey()
|
||||
// FIXME(dlc), make sure error if redefining same key?
|
||||
ctx[key] = val
|
||||
}
|
||||
}
|
||||
55
conf/parse_test.go
Normal file
55
conf/parse_test.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Test to make sure we get what we expect.
|
||||
|
||||
func test(t *testing.T, data string, ex map[string]interface{}) {
|
||||
m, err := Parse(data)
|
||||
if err != nil {
|
||||
t.Fatalf("Received err: %v\n", err)
|
||||
}
|
||||
if m == nil {
|
||||
t.Fatal("Received nil map")
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(m, ex) {
|
||||
t.Fatalf("Not Equal:\nReceived: '%+v'\nExpected: '%+v'\n", m, ex)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSimpleTopLevel(t *testing.T) {
|
||||
ex := map[string]interface{} {
|
||||
"foo":"1",
|
||||
"bar":float64(2.2),
|
||||
"baz":true,
|
||||
"boo":int64(22),
|
||||
}
|
||||
test(t, "foo='1'; bar=2.2; baz=true; boo=22", ex)
|
||||
}
|
||||
|
||||
var sample1 = `
|
||||
foo {
|
||||
host {
|
||||
ip = '127.0.0.1'
|
||||
port = 4242
|
||||
}
|
||||
servers = [ "a.com", "b.com", "c.com"]
|
||||
}
|
||||
`
|
||||
|
||||
func TestSample1(t *testing.T) {
|
||||
ex := map[string]interface{} {
|
||||
"foo": map[string]interface{} {
|
||||
"host": map[string]interface{} {
|
||||
"ip": "127.0.0.1",
|
||||
"port": int64(4242),
|
||||
},
|
||||
"servers": []interface{} {"a.com", "b.com", "c.com"},
|
||||
},
|
||||
}
|
||||
test(t, sample1, ex)
|
||||
}
|
||||
Reference in New Issue
Block a user