Signed-off-by: Akihiro Suda <suda.akihiro@lab.ntt.co.jp>
| ... | ... |
@@ -44,7 +44,7 @@ github.com/hashicorp/serf 598c54895cc5a7b1a24a398d635e8c0ea0959870 |
| 44 | 44 |
github.com/docker/libkv 1d8431073ae03cdaedb198a89722f3aab6d418ef |
| 45 | 45 |
github.com/vishvananda/netns 604eaf189ee867d8c147fafc28def2394e878d25 |
| 46 | 46 |
github.com/vishvananda/netlink b2de5d10e38ecce8607e6b438b6d174f389a004e |
| 47 |
-github.com/BurntSushi/toml f706d00e3de6abe700c994cdd545a1a4915af060 |
|
| 47 |
+github.com/BurntSushi/toml a368813c5e648fee92e5f6c30e3944ff9d5e8895 |
|
| 48 | 48 |
github.com/samuel/go-zookeeper d0e0d8e11f318e000a8cc434616d69e329edc374 |
| 49 | 49 |
github.com/deckarep/golang-set ef32fa3046d9f249d399f98ebaf9be944430fd1d |
| 50 | 50 |
github.com/coreos/etcd v3.2.1 |
| ... | ... |
@@ -1,14 +1,21 @@ |
| 1 |
- DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE |
|
| 2 |
- Version 2, December 2004 |
|
| 1 |
+The MIT License (MIT) |
|
| 3 | 2 |
|
| 4 |
- Copyright (C) 2004 Sam Hocevar <sam@hocevar.net> |
|
| 3 |
+Copyright (c) 2013 TOML authors |
|
| 5 | 4 |
|
| 6 |
- Everyone is permitted to copy and distribute verbatim or modified |
|
| 7 |
- copies of this license document, and changing it is allowed as long |
|
| 8 |
- as the name is changed. |
|
| 5 |
+Permission is hereby granted, free of charge, to any person obtaining a copy |
|
| 6 |
+of this software and associated documentation files (the "Software"), to deal |
|
| 7 |
+in the Software without restriction, including without limitation the rights |
|
| 8 |
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|
| 9 |
+copies of the Software, and to permit persons to whom the Software is |
|
| 10 |
+furnished to do so, subject to the following conditions: |
|
| 9 | 11 |
|
| 10 |
- DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE |
|
| 11 |
- TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION |
|
| 12 |
- |
|
| 13 |
- 0. You just DO WHAT THE FUCK YOU WANT TO. |
|
| 12 |
+The above copyright notice and this permission notice shall be included in |
|
| 13 |
+all copies or substantial portions of the Software. |
|
| 14 | 14 |
|
| 15 |
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
| 16 |
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
| 17 |
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|
| 18 |
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|
| 19 |
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|
| 20 |
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
|
| 21 |
+THE SOFTWARE. |
| ... | ... |
@@ -1,17 +1,17 @@ |
| 1 | 1 |
## TOML parser and encoder for Go with reflection |
| 2 | 2 |
|
| 3 | 3 |
TOML stands for Tom's Obvious, Minimal Language. This Go package provides a |
| 4 |
-reflection interface similar to Go's standard library `json` and `xml` |
|
| 4 |
+reflection interface similar to Go's standard library `json` and `xml` |
|
| 5 | 5 |
packages. This package also supports the `encoding.TextUnmarshaler` and |
| 6 |
-`encoding.TextMarshaler` interfaces so that you can define custom data |
|
| 6 |
+`encoding.TextMarshaler` interfaces so that you can define custom data |
|
| 7 | 7 |
representations. (There is an example of this below.) |
| 8 | 8 |
|
| 9 |
-Spec: https://github.com/mojombo/toml |
|
| 9 |
+Spec: https://github.com/toml-lang/toml |
|
| 10 | 10 |
|
| 11 | 11 |
Compatible with TOML version |
| 12 |
-[v0.2.0](https://github.com/mojombo/toml/blob/master/versions/toml-v0.2.0.md) |
|
| 12 |
+[v0.4.0](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.4.0.md) |
|
| 13 | 13 |
|
| 14 |
-Documentation: http://godoc.org/github.com/BurntSushi/toml |
|
| 14 |
+Documentation: https://godoc.org/github.com/BurntSushi/toml |
|
| 15 | 15 |
|
| 16 | 16 |
Installation: |
| 17 | 17 |
|
| ... | ... |
@@ -26,8 +26,7 @@ go get github.com/BurntSushi/toml/cmd/tomlv |
| 26 | 26 |
tomlv some-toml-file.toml |
| 27 | 27 |
``` |
| 28 | 28 |
|
| 29 |
-[](https://travis-ci.org/BurntSushi/toml) |
|
| 30 |
- |
|
| 29 |
+[](https://travis-ci.org/BurntSushi/toml) [](https://godoc.org/github.com/BurntSushi/toml) |
|
| 31 | 30 |
|
| 32 | 31 |
### Testing |
| 33 | 32 |
|
| ... | ... |
@@ -87,7 +86,7 @@ type TOML struct {
|
| 87 | 87 |
|
| 88 | 88 |
### Using the `encoding.TextUnmarshaler` interface |
| 89 | 89 |
|
| 90 |
-Here's an example that automatically parses duration strings into |
|
| 90 |
+Here's an example that automatically parses duration strings into |
|
| 91 | 91 |
`time.Duration` values: |
| 92 | 92 |
|
| 93 | 93 |
```toml |
| ... | ... |
@@ -120,7 +119,7 @@ for _, s := range favorites.Song {
|
| 120 | 120 |
} |
| 121 | 121 |
``` |
| 122 | 122 |
|
| 123 |
-And you'll also need a `duration` type that satisfies the |
|
| 123 |
+And you'll also need a `duration` type that satisfies the |
|
| 124 | 124 |
`encoding.TextUnmarshaler` interface: |
| 125 | 125 |
|
| 126 | 126 |
```go |
| ... | ... |
@@ -217,4 +216,3 @@ Note that a case insensitive match will be tried if an exact match can't be |
| 217 | 217 |
found. |
| 218 | 218 |
|
| 219 | 219 |
A working example of the above can be found in `_examples/example.{go,toml}`.
|
| 220 |
- |
| ... | ... |
@@ -10,7 +10,9 @@ import ( |
| 10 | 10 |
"time" |
| 11 | 11 |
) |
| 12 | 12 |
|
| 13 |
-var e = fmt.Errorf |
|
| 13 |
+func e(format string, args ...interface{}) error {
|
|
| 14 |
+ return fmt.Errorf("toml: "+format, args...)
|
|
| 15 |
+} |
|
| 14 | 16 |
|
| 15 | 17 |
// Unmarshaler is the interface implemented by objects that can unmarshal a |
| 16 | 18 |
// TOML description of themselves. |
| ... | ... |
@@ -103,6 +105,13 @@ func (md *MetaData) PrimitiveDecode(primValue Primitive, v interface{}) error {
|
| 103 | 103 |
// This decoder will not handle cyclic types. If a cyclic type is passed, |
| 104 | 104 |
// `Decode` will not terminate. |
| 105 | 105 |
func Decode(data string, v interface{}) (MetaData, error) {
|
| 106 |
+ rv := reflect.ValueOf(v) |
|
| 107 |
+ if rv.Kind() != reflect.Ptr {
|
|
| 108 |
+ return MetaData{}, e("Decode of non-pointer %s", reflect.TypeOf(v))
|
|
| 109 |
+ } |
|
| 110 |
+ if rv.IsNil() {
|
|
| 111 |
+ return MetaData{}, e("Decode of nil %s", reflect.TypeOf(v))
|
|
| 112 |
+ } |
|
| 106 | 113 |
p, err := parse(data) |
| 107 | 114 |
if err != nil {
|
| 108 | 115 |
return MetaData{}, err
|
| ... | ... |
@@ -111,7 +120,7 @@ func Decode(data string, v interface{}) (MetaData, error) {
|
| 111 | 111 |
p.mapping, p.types, p.ordered, |
| 112 | 112 |
make(map[string]bool, len(p.ordered)), nil, |
| 113 | 113 |
} |
| 114 |
- return md, md.unify(p.mapping, rvalue(v)) |
|
| 114 |
+ return md, md.unify(p.mapping, indirect(rv)) |
|
| 115 | 115 |
} |
| 116 | 116 |
|
| 117 | 117 |
// DecodeFile is just like Decode, except it will automatically read the |
| ... | ... |
@@ -211,7 +220,7 @@ func (md *MetaData) unify(data interface{}, rv reflect.Value) error {
|
| 211 | 211 |
case reflect.Interface: |
| 212 | 212 |
// we only support empty interfaces. |
| 213 | 213 |
if rv.NumMethod() > 0 {
|
| 214 |
- return e("Unsupported type '%s'.", rv.Kind())
|
|
| 214 |
+ return e("unsupported type %s", rv.Type())
|
|
| 215 | 215 |
} |
| 216 | 216 |
return md.unifyAnything(data, rv) |
| 217 | 217 |
case reflect.Float32: |
| ... | ... |
@@ -219,13 +228,17 @@ func (md *MetaData) unify(data interface{}, rv reflect.Value) error {
|
| 219 | 219 |
case reflect.Float64: |
| 220 | 220 |
return md.unifyFloat64(data, rv) |
| 221 | 221 |
} |
| 222 |
- return e("Unsupported type '%s'.", rv.Kind())
|
|
| 222 |
+ return e("unsupported type %s", rv.Kind())
|
|
| 223 | 223 |
} |
| 224 | 224 |
|
| 225 | 225 |
func (md *MetaData) unifyStruct(mapping interface{}, rv reflect.Value) error {
|
| 226 | 226 |
tmap, ok := mapping.(map[string]interface{})
|
| 227 | 227 |
if !ok {
|
| 228 |
- return mismatch(rv, "map", mapping) |
|
| 228 |
+ if mapping == nil {
|
|
| 229 |
+ return nil |
|
| 230 |
+ } |
|
| 231 |
+ return e("type mismatch for %s: expected table but found %T",
|
|
| 232 |
+ rv.Type().String(), mapping) |
|
| 229 | 233 |
} |
| 230 | 234 |
|
| 231 | 235 |
for key, datum := range tmap {
|
| ... | ... |
@@ -250,14 +263,13 @@ func (md *MetaData) unifyStruct(mapping interface{}, rv reflect.Value) error {
|
| 250 | 250 |
md.decoded[md.context.add(key).String()] = true |
| 251 | 251 |
md.context = append(md.context, key) |
| 252 | 252 |
if err := md.unify(datum, subv); err != nil {
|
| 253 |
- return e("Type mismatch for '%s.%s': %s",
|
|
| 254 |
- rv.Type().String(), f.name, err) |
|
| 253 |
+ return err |
|
| 255 | 254 |
} |
| 256 | 255 |
md.context = md.context[0 : len(md.context)-1] |
| 257 | 256 |
} else if f.name != "" {
|
| 258 | 257 |
// Bad user! No soup for you! |
| 259 |
- return e("Field '%s.%s' is unexported, and therefore cannot "+
|
|
| 260 |
- "be loaded with reflection.", rv.Type().String(), f.name) |
|
| 258 |
+ return e("cannot write unexported field %s.%s",
|
|
| 259 |
+ rv.Type().String(), f.name) |
|
| 261 | 260 |
} |
| 262 | 261 |
} |
| 263 | 262 |
} |
| ... | ... |
@@ -267,6 +279,9 @@ func (md *MetaData) unifyStruct(mapping interface{}, rv reflect.Value) error {
|
| 267 | 267 |
func (md *MetaData) unifyMap(mapping interface{}, rv reflect.Value) error {
|
| 268 | 268 |
tmap, ok := mapping.(map[string]interface{})
|
| 269 | 269 |
if !ok {
|
| 270 |
+ if tmap == nil {
|
|
| 271 |
+ return nil |
|
| 272 |
+ } |
|
| 270 | 273 |
return badtype("map", mapping)
|
| 271 | 274 |
} |
| 272 | 275 |
if rv.IsNil() {
|
| ... | ... |
@@ -292,6 +307,9 @@ func (md *MetaData) unifyMap(mapping interface{}, rv reflect.Value) error {
|
| 292 | 292 |
func (md *MetaData) unifyArray(data interface{}, rv reflect.Value) error {
|
| 293 | 293 |
datav := reflect.ValueOf(data) |
| 294 | 294 |
if datav.Kind() != reflect.Slice {
|
| 295 |
+ if !datav.IsValid() {
|
|
| 296 |
+ return nil |
|
| 297 |
+ } |
|
| 295 | 298 |
return badtype("slice", data)
|
| 296 | 299 |
} |
| 297 | 300 |
sliceLen := datav.Len() |
| ... | ... |
@@ -305,12 +323,16 @@ func (md *MetaData) unifyArray(data interface{}, rv reflect.Value) error {
|
| 305 | 305 |
func (md *MetaData) unifySlice(data interface{}, rv reflect.Value) error {
|
| 306 | 306 |
datav := reflect.ValueOf(data) |
| 307 | 307 |
if datav.Kind() != reflect.Slice {
|
| 308 |
+ if !datav.IsValid() {
|
|
| 309 |
+ return nil |
|
| 310 |
+ } |
|
| 308 | 311 |
return badtype("slice", data)
|
| 309 | 312 |
} |
| 310 |
- sliceLen := datav.Len() |
|
| 311 |
- if rv.IsNil() {
|
|
| 312 |
- rv.Set(reflect.MakeSlice(rv.Type(), sliceLen, sliceLen)) |
|
| 313 |
+ n := datav.Len() |
|
| 314 |
+ if rv.IsNil() || rv.Cap() < n {
|
|
| 315 |
+ rv.Set(reflect.MakeSlice(rv.Type(), n, n)) |
|
| 313 | 316 |
} |
| 317 |
+ rv.SetLen(n) |
|
| 314 | 318 |
return md.unifySliceArray(datav, rv) |
| 315 | 319 |
} |
| 316 | 320 |
|
| ... | ... |
@@ -365,15 +387,15 @@ func (md *MetaData) unifyInt(data interface{}, rv reflect.Value) error {
|
| 365 | 365 |
// No bounds checking necessary. |
| 366 | 366 |
case reflect.Int8: |
| 367 | 367 |
if num < math.MinInt8 || num > math.MaxInt8 {
|
| 368 |
- return e("Value '%d' is out of range for int8.", num)
|
|
| 368 |
+ return e("value %d is out of range for int8", num)
|
|
| 369 | 369 |
} |
| 370 | 370 |
case reflect.Int16: |
| 371 | 371 |
if num < math.MinInt16 || num > math.MaxInt16 {
|
| 372 |
- return e("Value '%d' is out of range for int16.", num)
|
|
| 372 |
+ return e("value %d is out of range for int16", num)
|
|
| 373 | 373 |
} |
| 374 | 374 |
case reflect.Int32: |
| 375 | 375 |
if num < math.MinInt32 || num > math.MaxInt32 {
|
| 376 |
- return e("Value '%d' is out of range for int32.", num)
|
|
| 376 |
+ return e("value %d is out of range for int32", num)
|
|
| 377 | 377 |
} |
| 378 | 378 |
} |
| 379 | 379 |
rv.SetInt(num) |
| ... | ... |
@@ -384,15 +406,15 @@ func (md *MetaData) unifyInt(data interface{}, rv reflect.Value) error {
|
| 384 | 384 |
// No bounds checking necessary. |
| 385 | 385 |
case reflect.Uint8: |
| 386 | 386 |
if num < 0 || unum > math.MaxUint8 {
|
| 387 |
- return e("Value '%d' is out of range for uint8.", num)
|
|
| 387 |
+ return e("value %d is out of range for uint8", num)
|
|
| 388 | 388 |
} |
| 389 | 389 |
case reflect.Uint16: |
| 390 | 390 |
if num < 0 || unum > math.MaxUint16 {
|
| 391 |
- return e("Value '%d' is out of range for uint16.", num)
|
|
| 391 |
+ return e("value %d is out of range for uint16", num)
|
|
| 392 | 392 |
} |
| 393 | 393 |
case reflect.Uint32: |
| 394 | 394 |
if num < 0 || unum > math.MaxUint32 {
|
| 395 |
- return e("Value '%d' is out of range for uint32.", num)
|
|
| 395 |
+ return e("value %d is out of range for uint32", num)
|
|
| 396 | 396 |
} |
| 397 | 397 |
} |
| 398 | 398 |
rv.SetUint(unum) |
| ... | ... |
@@ -458,7 +480,7 @@ func rvalue(v interface{}) reflect.Value {
|
| 458 | 458 |
// interest to us (like encoding.TextUnmarshaler). |
| 459 | 459 |
func indirect(v reflect.Value) reflect.Value {
|
| 460 | 460 |
if v.Kind() != reflect.Ptr {
|
| 461 |
- if v.CanAddr() {
|
|
| 461 |
+ if v.CanSet() {
|
|
| 462 | 462 |
pv := v.Addr() |
| 463 | 463 |
if _, ok := pv.Interface().(TextUnmarshaler); ok {
|
| 464 | 464 |
return pv |
| ... | ... |
@@ -483,10 +505,5 @@ func isUnifiable(rv reflect.Value) bool {
|
| 483 | 483 |
} |
| 484 | 484 |
|
| 485 | 485 |
func badtype(expected string, data interface{}) error {
|
| 486 |
- return e("Expected %s but found '%T'.", expected, data)
|
|
| 487 |
-} |
|
| 488 |
- |
|
| 489 |
-func mismatch(user reflect.Value, expected string, data interface{}) error {
|
|
| 490 |
- return e("Type mismatch for %s. Expected %s but found '%T'.",
|
|
| 491 |
- user.Type().String(), expected, data) |
|
| 486 |
+ return e("cannot load TOML value of type %T into a Go %s", data, expected)
|
|
| 492 | 487 |
} |
| ... | ... |
@@ -4,7 +4,7 @@ files via reflection. There is also support for delaying decoding with |
| 4 | 4 |
the Primitive type, and querying the set of keys in a TOML document with the |
| 5 | 5 |
MetaData type. |
| 6 | 6 |
|
| 7 |
-The specification implemented: https://github.com/mojombo/toml |
|
| 7 |
+The specification implemented: https://github.com/toml-lang/toml |
|
| 8 | 8 |
|
| 9 | 9 |
The sub-command github.com/BurntSushi/toml/cmd/tomlv can be used to verify |
| 10 | 10 |
whether a file is a valid TOML document. It can also be used to print the |
| ... | ... |
@@ -16,17 +16,17 @@ type tomlEncodeError struct{ error }
|
| 16 | 16 |
|
| 17 | 17 |
var ( |
| 18 | 18 |
errArrayMixedElementTypes = errors.New( |
| 19 |
- "can't encode array with mixed element types") |
|
| 19 |
+ "toml: cannot encode array with mixed element types") |
|
| 20 | 20 |
errArrayNilElement = errors.New( |
| 21 |
- "can't encode array with nil element") |
|
| 21 |
+ "toml: cannot encode array with nil element") |
|
| 22 | 22 |
errNonString = errors.New( |
| 23 |
- "can't encode a map with non-string key type") |
|
| 23 |
+ "toml: cannot encode a map with non-string key type") |
|
| 24 | 24 |
errAnonNonStruct = errors.New( |
| 25 |
- "can't encode an anonymous field that is not a struct") |
|
| 25 |
+ "toml: cannot encode an anonymous field that is not a struct") |
|
| 26 | 26 |
errArrayNoTable = errors.New( |
| 27 |
- "TOML array element can't contain a table") |
|
| 27 |
+ "toml: TOML array element cannot contain a table") |
|
| 28 | 28 |
errNoKey = errors.New( |
| 29 |
- "top-level values must be a Go map or struct") |
|
| 29 |
+ "toml: top-level values must be Go maps or structs") |
|
| 30 | 30 |
errAnything = errors.New("") // used in testing
|
| 31 | 31 |
) |
| 32 | 32 |
|
| ... | ... |
@@ -148,7 +148,7 @@ func (enc *Encoder) encode(key Key, rv reflect.Value) {
|
| 148 | 148 |
case reflect.Struct: |
| 149 | 149 |
enc.eTable(key, rv) |
| 150 | 150 |
default: |
| 151 |
- panic(e("Unsupported type for key '%s': %s", key, k))
|
|
| 151 |
+ panic(e("unsupported type for key '%s': %s", key, k))
|
|
| 152 | 152 |
} |
| 153 | 153 |
} |
| 154 | 154 |
|
| ... | ... |
@@ -160,7 +160,7 @@ func (enc *Encoder) eElement(rv reflect.Value) {
|
| 160 | 160 |
// Special case time.Time as a primitive. Has to come before |
| 161 | 161 |
// TextMarshaler below because time.Time implements |
| 162 | 162 |
// encoding.TextMarshaler, but we need to always use UTC. |
| 163 |
- enc.wf(v.In(time.FixedZone("UTC", 0)).Format("2006-01-02T15:04:05Z"))
|
|
| 163 |
+ enc.wf(v.UTC().Format("2006-01-02T15:04:05Z"))
|
|
| 164 | 164 |
return |
| 165 | 165 |
case TextMarshaler: |
| 166 | 166 |
// Special case. Use text marshaler if it's available for this value. |
| ... | ... |
@@ -191,7 +191,7 @@ func (enc *Encoder) eElement(rv reflect.Value) {
|
| 191 | 191 |
case reflect.String: |
| 192 | 192 |
enc.writeQuoted(rv.String()) |
| 193 | 193 |
default: |
| 194 |
- panic(e("Unexpected primitive type: %s", rv.Kind()))
|
|
| 194 |
+ panic(e("unexpected primitive type: %s", rv.Kind()))
|
|
| 195 | 195 |
} |
| 196 | 196 |
} |
| 197 | 197 |
|
| ... | ... |
@@ -241,7 +241,7 @@ func (enc *Encoder) eArrayOfTables(key Key, rv reflect.Value) {
|
| 241 | 241 |
func (enc *Encoder) eTable(key Key, rv reflect.Value) {
|
| 242 | 242 |
panicIfInvalidKey(key) |
| 243 | 243 |
if len(key) == 1 {
|
| 244 |
- // Output an extra new line between top-level tables. |
|
| 244 |
+ // Output an extra newline between top-level tables. |
|
| 245 | 245 |
// (The newline isn't written if nothing else has been written though.) |
| 246 | 246 |
enc.newline() |
| 247 | 247 |
} |
| ... | ... |
@@ -306,19 +306,36 @@ func (enc *Encoder) eStruct(key Key, rv reflect.Value) {
|
| 306 | 306 |
addFields = func(rt reflect.Type, rv reflect.Value, start []int) {
|
| 307 | 307 |
for i := 0; i < rt.NumField(); i++ {
|
| 308 | 308 |
f := rt.Field(i) |
| 309 |
- // skip unexporded fields |
|
| 310 |
- if f.PkgPath != "" {
|
|
| 309 |
+ // skip unexported fields |
|
| 310 |
+ if f.PkgPath != "" && !f.Anonymous {
|
|
| 311 | 311 |
continue |
| 312 | 312 |
} |
| 313 | 313 |
frv := rv.Field(i) |
| 314 | 314 |
if f.Anonymous {
|
| 315 |
- frv := eindirect(frv) |
|
| 316 |
- t := frv.Type() |
|
| 317 |
- if t.Kind() != reflect.Struct {
|
|
| 318 |
- encPanic(errAnonNonStruct) |
|
| 315 |
+ t := f.Type |
|
| 316 |
+ switch t.Kind() {
|
|
| 317 |
+ case reflect.Struct: |
|
| 318 |
+ // Treat anonymous struct fields with |
|
| 319 |
+ // tag names as though they are not |
|
| 320 |
+ // anonymous, like encoding/json does. |
|
| 321 |
+ if getOptions(f.Tag).name == "" {
|
|
| 322 |
+ addFields(t, frv, f.Index) |
|
| 323 |
+ continue |
|
| 324 |
+ } |
|
| 325 |
+ case reflect.Ptr: |
|
| 326 |
+ if t.Elem().Kind() == reflect.Struct && |
|
| 327 |
+ getOptions(f.Tag).name == "" {
|
|
| 328 |
+ if !frv.IsNil() {
|
|
| 329 |
+ addFields(t.Elem(), frv.Elem(), f.Index) |
|
| 330 |
+ } |
|
| 331 |
+ continue |
|
| 332 |
+ } |
|
| 333 |
+ // Fall through to the normal field encoding logic below |
|
| 334 |
+ // for non-struct anonymous fields. |
|
| 319 | 335 |
} |
| 320 |
- addFields(t, frv, f.Index) |
|
| 321 |
- } else if typeIsHash(tomlTypeOfGo(frv)) {
|
|
| 336 |
+ } |
|
| 337 |
+ |
|
| 338 |
+ if typeIsHash(tomlTypeOfGo(frv)) {
|
|
| 322 | 339 |
fieldsSub = append(fieldsSub, append(start, f.Index...)) |
| 323 | 340 |
} else {
|
| 324 | 341 |
fieldsDirect = append(fieldsDirect, append(start, f.Index...)) |
| ... | ... |
@@ -336,13 +353,21 @@ func (enc *Encoder) eStruct(key Key, rv reflect.Value) {
|
| 336 | 336 |
continue |
| 337 | 337 |
} |
| 338 | 338 |
|
| 339 |
- keyName := sft.Tag.Get("toml")
|
|
| 340 |
- if keyName == "-" {
|
|
| 339 |
+ opts := getOptions(sft.Tag) |
|
| 340 |
+ if opts.skip {
|
|
| 341 | 341 |
continue |
| 342 | 342 |
} |
| 343 |
- if keyName == "" {
|
|
| 344 |
- keyName = sft.Name |
|
| 343 |
+ keyName := sft.Name |
|
| 344 |
+ if opts.name != "" {
|
|
| 345 |
+ keyName = opts.name |
|
| 346 |
+ } |
|
| 347 |
+ if opts.omitempty && isEmpty(sf) {
|
|
| 348 |
+ continue |
|
| 345 | 349 |
} |
| 350 |
+ if opts.omitzero && isZero(sf) {
|
|
| 351 |
+ continue |
|
| 352 |
+ } |
|
| 353 |
+ |
|
| 346 | 354 |
enc.encode(key.add(keyName), sf) |
| 347 | 355 |
} |
| 348 | 356 |
} |
| ... | ... |
@@ -374,9 +399,8 @@ func tomlTypeOfGo(rv reflect.Value) tomlType {
|
| 374 | 374 |
case reflect.Array, reflect.Slice: |
| 375 | 375 |
if typeEqual(tomlHash, tomlArrayType(rv)) {
|
| 376 | 376 |
return tomlArrayHash |
| 377 |
- } else {
|
|
| 378 |
- return tomlArray |
|
| 379 | 377 |
} |
| 378 |
+ return tomlArray |
|
| 380 | 379 |
case reflect.Ptr, reflect.Interface: |
| 381 | 380 |
return tomlTypeOfGo(rv.Elem()) |
| 382 | 381 |
case reflect.String: |
| ... | ... |
@@ -433,6 +457,54 @@ func tomlArrayType(rv reflect.Value) tomlType {
|
| 433 | 433 |
return firstType |
| 434 | 434 |
} |
| 435 | 435 |
|
| 436 |
+type tagOptions struct {
|
|
| 437 |
+ skip bool // "-" |
|
| 438 |
+ name string |
|
| 439 |
+ omitempty bool |
|
| 440 |
+ omitzero bool |
|
| 441 |
+} |
|
| 442 |
+ |
|
| 443 |
+func getOptions(tag reflect.StructTag) tagOptions {
|
|
| 444 |
+ t := tag.Get("toml")
|
|
| 445 |
+ if t == "-" {
|
|
| 446 |
+ return tagOptions{skip: true}
|
|
| 447 |
+ } |
|
| 448 |
+ var opts tagOptions |
|
| 449 |
+ parts := strings.Split(t, ",") |
|
| 450 |
+ opts.name = parts[0] |
|
| 451 |
+ for _, s := range parts[1:] {
|
|
| 452 |
+ switch s {
|
|
| 453 |
+ case "omitempty": |
|
| 454 |
+ opts.omitempty = true |
|
| 455 |
+ case "omitzero": |
|
| 456 |
+ opts.omitzero = true |
|
| 457 |
+ } |
|
| 458 |
+ } |
|
| 459 |
+ return opts |
|
| 460 |
+} |
|
| 461 |
+ |
|
| 462 |
+func isZero(rv reflect.Value) bool {
|
|
| 463 |
+ switch rv.Kind() {
|
|
| 464 |
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: |
|
| 465 |
+ return rv.Int() == 0 |
|
| 466 |
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: |
|
| 467 |
+ return rv.Uint() == 0 |
|
| 468 |
+ case reflect.Float32, reflect.Float64: |
|
| 469 |
+ return rv.Float() == 0.0 |
|
| 470 |
+ } |
|
| 471 |
+ return false |
|
| 472 |
+} |
|
| 473 |
+ |
|
| 474 |
+func isEmpty(rv reflect.Value) bool {
|
|
| 475 |
+ switch rv.Kind() {
|
|
| 476 |
+ case reflect.Array, reflect.Slice, reflect.Map, reflect.String: |
|
| 477 |
+ return rv.Len() == 0 |
|
| 478 |
+ case reflect.Bool: |
|
| 479 |
+ return !rv.Bool() |
|
| 480 |
+ } |
|
| 481 |
+ return false |
|
| 482 |
+} |
|
| 483 |
+ |
|
| 436 | 484 |
func (enc *Encoder) newline() {
|
| 437 | 485 |
if enc.hasWritten {
|
| 438 | 486 |
enc.wf("\n")
|
| ... | ... |
@@ -3,6 +3,7 @@ package toml |
| 3 | 3 |
import ( |
| 4 | 4 |
"fmt" |
| 5 | 5 |
"strings" |
| 6 |
+ "unicode" |
|
| 6 | 7 |
"unicode/utf8" |
| 7 | 8 |
) |
| 8 | 9 |
|
| ... | ... |
@@ -29,24 +30,28 @@ const ( |
| 29 | 29 |
itemArrayTableEnd |
| 30 | 30 |
itemKeyStart |
| 31 | 31 |
itemCommentStart |
| 32 |
+ itemInlineTableStart |
|
| 33 |
+ itemInlineTableEnd |
|
| 32 | 34 |
) |
| 33 | 35 |
|
| 34 | 36 |
const ( |
| 35 |
- eof = 0 |
|
| 36 |
- tableStart = '[' |
|
| 37 |
- tableEnd = ']' |
|
| 38 |
- arrayTableStart = '[' |
|
| 39 |
- arrayTableEnd = ']' |
|
| 40 |
- tableSep = '.' |
|
| 41 |
- keySep = '=' |
|
| 42 |
- arrayStart = '[' |
|
| 43 |
- arrayEnd = ']' |
|
| 44 |
- arrayValTerm = ',' |
|
| 45 |
- commentStart = '#' |
|
| 46 |
- stringStart = '"' |
|
| 47 |
- stringEnd = '"' |
|
| 48 |
- rawStringStart = '\'' |
|
| 49 |
- rawStringEnd = '\'' |
|
| 37 |
+ eof = 0 |
|
| 38 |
+ comma = ',' |
|
| 39 |
+ tableStart = '[' |
|
| 40 |
+ tableEnd = ']' |
|
| 41 |
+ arrayTableStart = '[' |
|
| 42 |
+ arrayTableEnd = ']' |
|
| 43 |
+ tableSep = '.' |
|
| 44 |
+ keySep = '=' |
|
| 45 |
+ arrayStart = '[' |
|
| 46 |
+ arrayEnd = ']' |
|
| 47 |
+ commentStart = '#' |
|
| 48 |
+ stringStart = '"' |
|
| 49 |
+ stringEnd = '"' |
|
| 50 |
+ rawStringStart = '\'' |
|
| 51 |
+ rawStringEnd = '\'' |
|
| 52 |
+ inlineTableStart = '{'
|
|
| 53 |
+ inlineTableEnd = '}' |
|
| 50 | 54 |
) |
| 51 | 55 |
|
| 52 | 56 |
type stateFn func(lx *lexer) stateFn |
| ... | ... |
@@ -55,11 +60,18 @@ type lexer struct {
|
| 55 | 55 |
input string |
| 56 | 56 |
start int |
| 57 | 57 |
pos int |
| 58 |
- width int |
|
| 59 | 58 |
line int |
| 60 | 59 |
state stateFn |
| 61 | 60 |
items chan item |
| 62 | 61 |
|
| 62 |
+ // Allow for backing up up to three runes. |
|
| 63 |
+ // This is necessary because TOML contains 3-rune tokens (""" and ''').
|
|
| 64 |
+ prevWidths [3]int |
|
| 65 |
+ nprev int // how many of prevWidths are in use |
|
| 66 |
+ // If we emit an eof, we can still back up, but it is not OK to call |
|
| 67 |
+ // next again. |
|
| 68 |
+ atEOF bool |
|
| 69 |
+ |
|
| 63 | 70 |
// A stack of state functions used to maintain context. |
| 64 | 71 |
// The idea is to reuse parts of the state machine in various places. |
| 65 | 72 |
// For example, values can appear at the top level or within arbitrarily |
| ... | ... |
@@ -87,7 +99,7 @@ func (lx *lexer) nextItem() item {
|
| 87 | 87 |
|
| 88 | 88 |
func lex(input string) *lexer {
|
| 89 | 89 |
lx := &lexer{
|
| 90 |
- input: input + "\n", |
|
| 90 |
+ input: input, |
|
| 91 | 91 |
state: lexTop, |
| 92 | 92 |
line: 1, |
| 93 | 93 |
items: make(chan item, 10), |
| ... | ... |
@@ -102,7 +114,7 @@ func (lx *lexer) push(state stateFn) {
|
| 102 | 102 |
|
| 103 | 103 |
func (lx *lexer) pop() stateFn {
|
| 104 | 104 |
if len(lx.stack) == 0 {
|
| 105 |
- return lx.errorf("BUG in lexer: no states to pop.")
|
|
| 105 |
+ return lx.errorf("BUG in lexer: no states to pop")
|
|
| 106 | 106 |
} |
| 107 | 107 |
last := lx.stack[len(lx.stack)-1] |
| 108 | 108 |
lx.stack = lx.stack[0 : len(lx.stack)-1] |
| ... | ... |
@@ -124,16 +136,25 @@ func (lx *lexer) emitTrim(typ itemType) {
|
| 124 | 124 |
} |
| 125 | 125 |
|
| 126 | 126 |
func (lx *lexer) next() (r rune) {
|
| 127 |
+ if lx.atEOF {
|
|
| 128 |
+ panic("next called after EOF")
|
|
| 129 |
+ } |
|
| 127 | 130 |
if lx.pos >= len(lx.input) {
|
| 128 |
- lx.width = 0 |
|
| 131 |
+ lx.atEOF = true |
|
| 129 | 132 |
return eof |
| 130 | 133 |
} |
| 131 | 134 |
|
| 132 | 135 |
if lx.input[lx.pos] == '\n' {
|
| 133 | 136 |
lx.line++ |
| 134 | 137 |
} |
| 135 |
- r, lx.width = utf8.DecodeRuneInString(lx.input[lx.pos:]) |
|
| 136 |
- lx.pos += lx.width |
|
| 138 |
+ lx.prevWidths[2] = lx.prevWidths[1] |
|
| 139 |
+ lx.prevWidths[1] = lx.prevWidths[0] |
|
| 140 |
+ if lx.nprev < 3 {
|
|
| 141 |
+ lx.nprev++ |
|
| 142 |
+ } |
|
| 143 |
+ r, w := utf8.DecodeRuneInString(lx.input[lx.pos:]) |
|
| 144 |
+ lx.prevWidths[0] = w |
|
| 145 |
+ lx.pos += w |
|
| 137 | 146 |
return r |
| 138 | 147 |
} |
| 139 | 148 |
|
| ... | ... |
@@ -142,9 +163,20 @@ func (lx *lexer) ignore() {
|
| 142 | 142 |
lx.start = lx.pos |
| 143 | 143 |
} |
| 144 | 144 |
|
| 145 |
-// backup steps back one rune. Can be called only once per call of next. |
|
| 145 |
+// backup steps back one rune. Can be called only twice between calls to next. |
|
| 146 | 146 |
func (lx *lexer) backup() {
|
| 147 |
- lx.pos -= lx.width |
|
| 147 |
+ if lx.atEOF {
|
|
| 148 |
+ lx.atEOF = false |
|
| 149 |
+ return |
|
| 150 |
+ } |
|
| 151 |
+ if lx.nprev < 1 {
|
|
| 152 |
+ panic("backed up too far")
|
|
| 153 |
+ } |
|
| 154 |
+ w := lx.prevWidths[0] |
|
| 155 |
+ lx.prevWidths[0] = lx.prevWidths[1] |
|
| 156 |
+ lx.prevWidths[1] = lx.prevWidths[2] |
|
| 157 |
+ lx.nprev-- |
|
| 158 |
+ lx.pos -= w |
|
| 148 | 159 |
if lx.pos < len(lx.input) && lx.input[lx.pos] == '\n' {
|
| 149 | 160 |
lx.line-- |
| 150 | 161 |
} |
| ... | ... |
@@ -166,9 +198,22 @@ func (lx *lexer) peek() rune {
|
| 166 | 166 |
return r |
| 167 | 167 |
} |
| 168 | 168 |
|
| 169 |
+// skip ignores all input that matches the given predicate. |
|
| 170 |
+func (lx *lexer) skip(pred func(rune) bool) {
|
|
| 171 |
+ for {
|
|
| 172 |
+ r := lx.next() |
|
| 173 |
+ if pred(r) {
|
|
| 174 |
+ continue |
|
| 175 |
+ } |
|
| 176 |
+ lx.backup() |
|
| 177 |
+ lx.ignore() |
|
| 178 |
+ return |
|
| 179 |
+ } |
|
| 180 |
+} |
|
| 181 |
+ |
|
| 169 | 182 |
// errorf stops all lexing by emitting an error and returning `nil`. |
| 170 | 183 |
// Note that any value that is a character is escaped if it's a special |
| 171 |
-// character (new lines, tabs, etc.). |
|
| 184 |
+// character (newlines, tabs, etc.). |
|
| 172 | 185 |
func (lx *lexer) errorf(format string, values ...interface{}) stateFn {
|
| 173 | 186 |
lx.items <- item{
|
| 174 | 187 |
itemError, |
| ... | ... |
@@ -184,7 +229,6 @@ func lexTop(lx *lexer) stateFn {
|
| 184 | 184 |
if isWhitespace(r) || isNL(r) {
|
| 185 | 185 |
return lexSkip(lx, lexTop) |
| 186 | 186 |
} |
| 187 |
- |
|
| 188 | 187 |
switch r {
|
| 189 | 188 |
case commentStart: |
| 190 | 189 |
lx.push(lexTop) |
| ... | ... |
@@ -193,7 +237,7 @@ func lexTop(lx *lexer) stateFn {
|
| 193 | 193 |
return lexTableStart |
| 194 | 194 |
case eof: |
| 195 | 195 |
if lx.pos > lx.start {
|
| 196 |
- return lx.errorf("Unexpected EOF.")
|
|
| 196 |
+ return lx.errorf("unexpected EOF")
|
|
| 197 | 197 |
} |
| 198 | 198 |
lx.emit(itemEOF) |
| 199 | 199 |
return nil |
| ... | ... |
@@ -208,12 +252,12 @@ func lexTop(lx *lexer) stateFn {
|
| 208 | 208 |
|
| 209 | 209 |
// lexTopEnd is entered whenever a top-level item has been consumed. (A value |
| 210 | 210 |
// or a table.) It must see only whitespace, and will turn back to lexTop |
| 211 |
-// upon a new line. If it sees EOF, it will quit the lexer successfully. |
|
| 211 |
+// upon a newline. If it sees EOF, it will quit the lexer successfully. |
|
| 212 | 212 |
func lexTopEnd(lx *lexer) stateFn {
|
| 213 | 213 |
r := lx.next() |
| 214 | 214 |
switch {
|
| 215 | 215 |
case r == commentStart: |
| 216 |
- // a comment will read to a new line for us. |
|
| 216 |
+ // a comment will read to a newline for us. |
|
| 217 | 217 |
lx.push(lexTop) |
| 218 | 218 |
return lexCommentStart |
| 219 | 219 |
case isWhitespace(r): |
| ... | ... |
@@ -222,11 +266,11 @@ func lexTopEnd(lx *lexer) stateFn {
|
| 222 | 222 |
lx.ignore() |
| 223 | 223 |
return lexTop |
| 224 | 224 |
case r == eof: |
| 225 |
- lx.ignore() |
|
| 226 |
- return lexTop |
|
| 225 |
+ lx.emit(itemEOF) |
|
| 226 |
+ return nil |
|
| 227 | 227 |
} |
| 228 |
- return lx.errorf("Expected a top-level item to end with a new line, "+
|
|
| 229 |
- "comment or EOF, but got %q instead.", r) |
|
| 228 |
+ return lx.errorf("expected a top-level item to end with a newline, "+
|
|
| 229 |
+ "comment, or EOF, but got %q instead", r) |
|
| 230 | 230 |
} |
| 231 | 231 |
|
| 232 | 232 |
// lexTable lexes the beginning of a table. Namely, it makes sure that |
| ... | ... |
@@ -253,50 +297,47 @@ func lexTableEnd(lx *lexer) stateFn {
|
| 253 | 253 |
|
| 254 | 254 |
func lexArrayTableEnd(lx *lexer) stateFn {
|
| 255 | 255 |
if r := lx.next(); r != arrayTableEnd {
|
| 256 |
- return lx.errorf("Expected end of table array name delimiter %q, "+
|
|
| 257 |
- "but got %q instead.", arrayTableEnd, r) |
|
| 256 |
+ return lx.errorf("expected end of table array name delimiter %q, "+
|
|
| 257 |
+ "but got %q instead", arrayTableEnd, r) |
|
| 258 | 258 |
} |
| 259 | 259 |
lx.emit(itemArrayTableEnd) |
| 260 | 260 |
return lexTopEnd |
| 261 | 261 |
} |
| 262 | 262 |
|
| 263 | 263 |
func lexTableNameStart(lx *lexer) stateFn {
|
| 264 |
+ lx.skip(isWhitespace) |
|
| 264 | 265 |
switch r := lx.peek(); {
|
| 265 | 266 |
case r == tableEnd || r == eof: |
| 266 |
- return lx.errorf("Unexpected end of table name. (Table names cannot " +
|
|
| 267 |
- "be empty.)") |
|
| 267 |
+ return lx.errorf("unexpected end of table name " +
|
|
| 268 |
+ "(table names cannot be empty)") |
|
| 268 | 269 |
case r == tableSep: |
| 269 |
- return lx.errorf("Unexpected table separator. (Table names cannot " +
|
|
| 270 |
- "be empty.)") |
|
| 270 |
+ return lx.errorf("unexpected table separator " +
|
|
| 271 |
+ "(table names cannot be empty)") |
|
| 271 | 272 |
case r == stringStart || r == rawStringStart: |
| 272 | 273 |
lx.ignore() |
| 273 | 274 |
lx.push(lexTableNameEnd) |
| 274 | 275 |
return lexValue // reuse string lexing |
| 275 |
- case isWhitespace(r): |
|
| 276 |
- return lexTableNameStart |
|
| 277 | 276 |
default: |
| 278 | 277 |
return lexBareTableName |
| 279 | 278 |
} |
| 280 | 279 |
} |
| 281 | 280 |
|
| 282 |
-// lexTableName lexes the name of a table. It assumes that at least one |
|
| 281 |
+// lexBareTableName lexes the name of a table. It assumes that at least one |
|
| 283 | 282 |
// valid character for the table has already been read. |
| 284 | 283 |
func lexBareTableName(lx *lexer) stateFn {
|
| 285 |
- switch r := lx.next(); {
|
|
| 286 |
- case isBareKeyChar(r): |
|
| 284 |
+ r := lx.next() |
|
| 285 |
+ if isBareKeyChar(r) {
|
|
| 287 | 286 |
return lexBareTableName |
| 288 |
- case r == tableSep || r == tableEnd: |
|
| 289 |
- lx.backup() |
|
| 290 |
- lx.emitTrim(itemText) |
|
| 291 |
- return lexTableNameEnd |
|
| 292 |
- default: |
|
| 293 |
- return lx.errorf("Bare keys cannot contain %q.", r)
|
|
| 294 | 287 |
} |
| 288 |
+ lx.backup() |
|
| 289 |
+ lx.emit(itemText) |
|
| 290 |
+ return lexTableNameEnd |
|
| 295 | 291 |
} |
| 296 | 292 |
|
| 297 | 293 |
// lexTableNameEnd reads the end of a piece of a table name, optionally |
| 298 | 294 |
// consuming whitespace. |
| 299 | 295 |
func lexTableNameEnd(lx *lexer) stateFn {
|
| 296 |
+ lx.skip(isWhitespace) |
|
| 300 | 297 |
switch r := lx.next(); {
|
| 301 | 298 |
case isWhitespace(r): |
| 302 | 299 |
return lexTableNameEnd |
| ... | ... |
@@ -306,8 +347,8 @@ func lexTableNameEnd(lx *lexer) stateFn {
|
| 306 | 306 |
case r == tableEnd: |
| 307 | 307 |
return lx.pop() |
| 308 | 308 |
default: |
| 309 |
- return lx.errorf("Expected '.' or ']' to end table name, but got %q "+
|
|
| 310 |
- "instead.", r) |
|
| 309 |
+ return lx.errorf("expected '.' or ']' to end table name, "+
|
|
| 310 |
+ "but got %q instead", r) |
|
| 311 | 311 |
} |
| 312 | 312 |
} |
| 313 | 313 |
|
| ... | ... |
@@ -317,7 +358,7 @@ func lexKeyStart(lx *lexer) stateFn {
|
| 317 | 317 |
r := lx.peek() |
| 318 | 318 |
switch {
|
| 319 | 319 |
case r == keySep: |
| 320 |
- return lx.errorf("Unexpected key separator %q.", keySep)
|
|
| 320 |
+ return lx.errorf("unexpected key separator %q", keySep)
|
|
| 321 | 321 |
case isWhitespace(r) || isNL(r): |
| 322 | 322 |
lx.next() |
| 323 | 323 |
return lexSkip(lx, lexKeyStart) |
| ... | ... |
@@ -340,14 +381,15 @@ func lexBareKey(lx *lexer) stateFn {
|
| 340 | 340 |
case isBareKeyChar(r): |
| 341 | 341 |
return lexBareKey |
| 342 | 342 |
case isWhitespace(r): |
| 343 |
- lx.emitTrim(itemText) |
|
| 343 |
+ lx.backup() |
|
| 344 |
+ lx.emit(itemText) |
|
| 344 | 345 |
return lexKeyEnd |
| 345 | 346 |
case r == keySep: |
| 346 | 347 |
lx.backup() |
| 347 |
- lx.emitTrim(itemText) |
|
| 348 |
+ lx.emit(itemText) |
|
| 348 | 349 |
return lexKeyEnd |
| 349 | 350 |
default: |
| 350 |
- return lx.errorf("Bare keys cannot contain %q.", r)
|
|
| 351 |
+ return lx.errorf("bare keys cannot contain %q", r)
|
|
| 351 | 352 |
} |
| 352 | 353 |
} |
| 353 | 354 |
|
| ... | ... |
@@ -360,7 +402,7 @@ func lexKeyEnd(lx *lexer) stateFn {
|
| 360 | 360 |
case isWhitespace(r): |
| 361 | 361 |
return lexSkip(lx, lexKeyEnd) |
| 362 | 362 |
default: |
| 363 |
- return lx.errorf("Expected key separator %q, but got %q instead.",
|
|
| 363 |
+ return lx.errorf("expected key separator %q, but got %q instead",
|
|
| 364 | 364 |
keySep, r) |
| 365 | 365 |
} |
| 366 | 366 |
} |
| ... | ... |
@@ -369,20 +411,26 @@ func lexKeyEnd(lx *lexer) stateFn {
|
| 369 | 369 |
// lexValue will ignore whitespace. |
| 370 | 370 |
// After a value is lexed, the last state on the next is popped and returned. |
| 371 | 371 |
func lexValue(lx *lexer) stateFn {
|
| 372 |
- // We allow whitespace to precede a value, but NOT new lines. |
|
| 373 |
- // In array syntax, the array states are responsible for ignoring new |
|
| 374 |
- // lines. |
|
| 372 |
+ // We allow whitespace to precede a value, but NOT newlines. |
|
| 373 |
+ // In array syntax, the array states are responsible for ignoring newlines. |
|
| 375 | 374 |
r := lx.next() |
| 376 |
- if isWhitespace(r) {
|
|
| 375 |
+ switch {
|
|
| 376 |
+ case isWhitespace(r): |
|
| 377 | 377 |
return lexSkip(lx, lexValue) |
| 378 |
+ case isDigit(r): |
|
| 379 |
+ lx.backup() // avoid an extra state and use the same as above |
|
| 380 |
+ return lexNumberOrDateStart |
|
| 378 | 381 |
} |
| 379 |
- |
|
| 380 |
- switch {
|
|
| 381 |
- case r == arrayStart: |
|
| 382 |
+ switch r {
|
|
| 383 |
+ case arrayStart: |
|
| 382 | 384 |
lx.ignore() |
| 383 | 385 |
lx.emit(itemArray) |
| 384 | 386 |
return lexArrayValue |
| 385 |
- case r == stringStart: |
|
| 387 |
+ case inlineTableStart: |
|
| 388 |
+ lx.ignore() |
|
| 389 |
+ lx.emit(itemInlineTableStart) |
|
| 390 |
+ return lexInlineTableValue |
|
| 391 |
+ case stringStart: |
|
| 386 | 392 |
if lx.accept(stringStart) {
|
| 387 | 393 |
if lx.accept(stringStart) {
|
| 388 | 394 |
lx.ignore() // Ignore """ |
| ... | ... |
@@ -392,7 +440,7 @@ func lexValue(lx *lexer) stateFn {
|
| 392 | 392 |
} |
| 393 | 393 |
lx.ignore() // ignore the '"' |
| 394 | 394 |
return lexString |
| 395 |
- case r == rawStringStart: |
|
| 395 |
+ case rawStringStart: |
|
| 396 | 396 |
if lx.accept(rawStringStart) {
|
| 397 | 397 |
if lx.accept(rawStringStart) {
|
| 398 | 398 |
lx.ignore() // Ignore """ |
| ... | ... |
@@ -402,23 +450,24 @@ func lexValue(lx *lexer) stateFn {
|
| 402 | 402 |
} |
| 403 | 403 |
lx.ignore() // ignore the "'" |
| 404 | 404 |
return lexRawString |
| 405 |
- case r == 't': |
|
| 406 |
- return lexTrue |
|
| 407 |
- case r == 'f': |
|
| 408 |
- return lexFalse |
|
| 409 |
- case r == '-': |
|
| 405 |
+ case '+', '-': |
|
| 410 | 406 |
return lexNumberStart |
| 411 |
- case isDigit(r): |
|
| 412 |
- lx.backup() // avoid an extra state and use the same as above |
|
| 413 |
- return lexNumberOrDateStart |
|
| 414 |
- case r == '.': // special error case, be kind to users |
|
| 415 |
- return lx.errorf("Floats must start with a digit, not '.'.")
|
|
| 407 |
+ case '.': // special error case, be kind to users |
|
| 408 |
+ return lx.errorf("floats must start with a digit, not '.'")
|
|
| 409 |
+ } |
|
| 410 |
+ if unicode.IsLetter(r) {
|
|
| 411 |
+ // Be permissive here; lexBool will give a nice error if the |
|
| 412 |
+ // user wrote something like |
|
| 413 |
+ // x = foo |
|
| 414 |
+ // (i.e. not 'true' or 'false' but is something else word-like.) |
|
| 415 |
+ lx.backup() |
|
| 416 |
+ return lexBool |
|
| 416 | 417 |
} |
| 417 |
- return lx.errorf("Expected value but found %q instead.", r)
|
|
| 418 |
+ return lx.errorf("expected value but found %q instead", r)
|
|
| 418 | 419 |
} |
| 419 | 420 |
|
| 420 | 421 |
// lexArrayValue consumes one value in an array. It assumes that '[' or ',' |
| 421 |
-// have already been consumed. All whitespace and new lines are ignored. |
|
| 422 |
+// have already been consumed. All whitespace and newlines are ignored. |
|
| 422 | 423 |
func lexArrayValue(lx *lexer) stateFn {
|
| 423 | 424 |
r := lx.next() |
| 424 | 425 |
switch {
|
| ... | ... |
@@ -427,10 +476,11 @@ func lexArrayValue(lx *lexer) stateFn {
|
| 427 | 427 |
case r == commentStart: |
| 428 | 428 |
lx.push(lexArrayValue) |
| 429 | 429 |
return lexCommentStart |
| 430 |
- case r == arrayValTerm: |
|
| 431 |
- return lx.errorf("Unexpected array value terminator %q.",
|
|
| 432 |
- arrayValTerm) |
|
| 430 |
+ case r == comma: |
|
| 431 |
+ return lx.errorf("unexpected comma")
|
|
| 433 | 432 |
case r == arrayEnd: |
| 433 |
+ // NOTE(caleb): The spec isn't clear about whether you can have |
|
| 434 |
+ // a trailing comma or not, so we'll allow it. |
|
| 434 | 435 |
return lexArrayEnd |
| 435 | 436 |
} |
| 436 | 437 |
|
| ... | ... |
@@ -439,8 +489,9 @@ func lexArrayValue(lx *lexer) stateFn {
|
| 439 | 439 |
return lexValue |
| 440 | 440 |
} |
| 441 | 441 |
|
| 442 |
-// lexArrayValueEnd consumes the cruft between values of an array. Namely, |
|
| 443 |
-// it ignores whitespace and expects either a ',' or a ']'. |
|
| 442 |
+// lexArrayValueEnd consumes everything between the end of an array value and |
|
| 443 |
+// the next value (or the end of the array): it ignores whitespace and newlines |
|
| 444 |
+// and expects either a ',' or a ']'. |
|
| 444 | 445 |
func lexArrayValueEnd(lx *lexer) stateFn {
|
| 445 | 446 |
r := lx.next() |
| 446 | 447 |
switch {
|
| ... | ... |
@@ -449,31 +500,88 @@ func lexArrayValueEnd(lx *lexer) stateFn {
|
| 449 | 449 |
case r == commentStart: |
| 450 | 450 |
lx.push(lexArrayValueEnd) |
| 451 | 451 |
return lexCommentStart |
| 452 |
- case r == arrayValTerm: |
|
| 452 |
+ case r == comma: |
|
| 453 | 453 |
lx.ignore() |
| 454 | 454 |
return lexArrayValue // move on to the next value |
| 455 | 455 |
case r == arrayEnd: |
| 456 | 456 |
return lexArrayEnd |
| 457 | 457 |
} |
| 458 |
- return lx.errorf("Expected an array value terminator %q or an array "+
|
|
| 459 |
- "terminator %q, but got %q instead.", arrayValTerm, arrayEnd, r) |
|
| 458 |
+ return lx.errorf( |
|
| 459 |
+ "expected a comma or array terminator %q, but got %q instead", |
|
| 460 |
+ arrayEnd, r, |
|
| 461 |
+ ) |
|
| 460 | 462 |
} |
| 461 | 463 |
|
| 462 |
-// lexArrayEnd finishes the lexing of an array. It assumes that a ']' has |
|
| 463 |
-// just been consumed. |
|
| 464 |
+// lexArrayEnd finishes the lexing of an array. |
|
| 465 |
+// It assumes that a ']' has just been consumed. |
|
| 464 | 466 |
func lexArrayEnd(lx *lexer) stateFn {
|
| 465 | 467 |
lx.ignore() |
| 466 | 468 |
lx.emit(itemArrayEnd) |
| 467 | 469 |
return lx.pop() |
| 468 | 470 |
} |
| 469 | 471 |
|
| 472 |
+// lexInlineTableValue consumes one key/value pair in an inline table. |
|
| 473 |
+// It assumes that '{' or ',' have already been consumed. Whitespace is ignored.
|
|
| 474 |
+func lexInlineTableValue(lx *lexer) stateFn {
|
|
| 475 |
+ r := lx.next() |
|
| 476 |
+ switch {
|
|
| 477 |
+ case isWhitespace(r): |
|
| 478 |
+ return lexSkip(lx, lexInlineTableValue) |
|
| 479 |
+ case isNL(r): |
|
| 480 |
+ return lx.errorf("newlines not allowed within inline tables")
|
|
| 481 |
+ case r == commentStart: |
|
| 482 |
+ lx.push(lexInlineTableValue) |
|
| 483 |
+ return lexCommentStart |
|
| 484 |
+ case r == comma: |
|
| 485 |
+ return lx.errorf("unexpected comma")
|
|
| 486 |
+ case r == inlineTableEnd: |
|
| 487 |
+ return lexInlineTableEnd |
|
| 488 |
+ } |
|
| 489 |
+ lx.backup() |
|
| 490 |
+ lx.push(lexInlineTableValueEnd) |
|
| 491 |
+ return lexKeyStart |
|
| 492 |
+} |
|
| 493 |
+ |
|
| 494 |
+// lexInlineTableValueEnd consumes everything between the end of an inline table |
|
| 495 |
+// key/value pair and the next pair (or the end of the table): |
|
| 496 |
+// it ignores whitespace and expects either a ',' or a '}'. |
|
| 497 |
+func lexInlineTableValueEnd(lx *lexer) stateFn {
|
|
| 498 |
+ r := lx.next() |
|
| 499 |
+ switch {
|
|
| 500 |
+ case isWhitespace(r): |
|
| 501 |
+ return lexSkip(lx, lexInlineTableValueEnd) |
|
| 502 |
+ case isNL(r): |
|
| 503 |
+ return lx.errorf("newlines not allowed within inline tables")
|
|
| 504 |
+ case r == commentStart: |
|
| 505 |
+ lx.push(lexInlineTableValueEnd) |
|
| 506 |
+ return lexCommentStart |
|
| 507 |
+ case r == comma: |
|
| 508 |
+ lx.ignore() |
|
| 509 |
+ return lexInlineTableValue |
|
| 510 |
+ case r == inlineTableEnd: |
|
| 511 |
+ return lexInlineTableEnd |
|
| 512 |
+ } |
|
| 513 |
+ return lx.errorf("expected a comma or an inline table terminator %q, "+
|
|
| 514 |
+ "but got %q instead", inlineTableEnd, r) |
|
| 515 |
+} |
|
| 516 |
+ |
|
| 517 |
+// lexInlineTableEnd finishes the lexing of an inline table. |
|
| 518 |
+// It assumes that a '}' has just been consumed. |
|
| 519 |
+func lexInlineTableEnd(lx *lexer) stateFn {
|
|
| 520 |
+ lx.ignore() |
|
| 521 |
+ lx.emit(itemInlineTableEnd) |
|
| 522 |
+ return lx.pop() |
|
| 523 |
+} |
|
| 524 |
+ |
|
| 470 | 525 |
// lexString consumes the inner contents of a string. It assumes that the |
| 471 | 526 |
// beginning '"' has already been consumed and ignored. |
| 472 | 527 |
func lexString(lx *lexer) stateFn {
|
| 473 | 528 |
r := lx.next() |
| 474 | 529 |
switch {
|
| 530 |
+ case r == eof: |
|
| 531 |
+ return lx.errorf("unexpected EOF")
|
|
| 475 | 532 |
case isNL(r): |
| 476 |
- return lx.errorf("Strings cannot contain new lines.")
|
|
| 533 |
+ return lx.errorf("strings cannot contain newlines")
|
|
| 477 | 534 |
case r == '\\': |
| 478 | 535 |
lx.push(lexString) |
| 479 | 536 |
return lexStringEscape |
| ... | ... |
@@ -490,11 +598,12 @@ func lexString(lx *lexer) stateFn {
|
| 490 | 490 |
// lexMultilineString consumes the inner contents of a string. It assumes that |
| 491 | 491 |
// the beginning '"""' has already been consumed and ignored. |
| 492 | 492 |
func lexMultilineString(lx *lexer) stateFn {
|
| 493 |
- r := lx.next() |
|
| 494 |
- switch {
|
|
| 495 |
- case r == '\\': |
|
| 493 |
+ switch lx.next() {
|
|
| 494 |
+ case eof: |
|
| 495 |
+ return lx.errorf("unexpected EOF")
|
|
| 496 |
+ case '\\': |
|
| 496 | 497 |
return lexMultilineStringEscape |
| 497 |
- case r == stringEnd: |
|
| 498 |
+ case stringEnd: |
|
| 498 | 499 |
if lx.accept(stringEnd) {
|
| 499 | 500 |
if lx.accept(stringEnd) {
|
| 500 | 501 |
lx.backup() |
| ... | ... |
@@ -518,8 +627,10 @@ func lexMultilineString(lx *lexer) stateFn {
|
| 518 | 518 |
func lexRawString(lx *lexer) stateFn {
|
| 519 | 519 |
r := lx.next() |
| 520 | 520 |
switch {
|
| 521 |
+ case r == eof: |
|
| 522 |
+ return lx.errorf("unexpected EOF")
|
|
| 521 | 523 |
case isNL(r): |
| 522 |
- return lx.errorf("Strings cannot contain new lines.")
|
|
| 524 |
+ return lx.errorf("strings cannot contain newlines")
|
|
| 523 | 525 |
case r == rawStringEnd: |
| 524 | 526 |
lx.backup() |
| 525 | 527 |
lx.emit(itemRawString) |
| ... | ... |
@@ -531,12 +642,13 @@ func lexRawString(lx *lexer) stateFn {
|
| 531 | 531 |
} |
| 532 | 532 |
|
| 533 | 533 |
// lexMultilineRawString consumes a raw string. Nothing can be escaped in such |
| 534 |
-// a string. It assumes that the beginning "'" has already been consumed and |
|
| 534 |
+// a string. It assumes that the beginning "'''" has already been consumed and |
|
| 535 | 535 |
// ignored. |
| 536 | 536 |
func lexMultilineRawString(lx *lexer) stateFn {
|
| 537 |
- r := lx.next() |
|
| 538 |
- switch {
|
|
| 539 |
- case r == rawStringEnd: |
|
| 537 |
+ switch lx.next() {
|
|
| 538 |
+ case eof: |
|
| 539 |
+ return lx.errorf("unexpected EOF")
|
|
| 540 |
+ case rawStringEnd: |
|
| 540 | 541 |
if lx.accept(rawStringEnd) {
|
| 541 | 542 |
if lx.accept(rawStringEnd) {
|
| 542 | 543 |
lx.backup() |
| ... | ... |
@@ -560,13 +672,11 @@ func lexMultilineRawString(lx *lexer) stateFn {
|
| 560 | 560 |
func lexMultilineStringEscape(lx *lexer) stateFn {
|
| 561 | 561 |
// Handle the special case first: |
| 562 | 562 |
if isNL(lx.next()) {
|
| 563 |
- lx.next() |
|
| 564 | 563 |
return lexMultilineString |
| 565 |
- } else {
|
|
| 566 |
- lx.backup() |
|
| 567 |
- lx.push(lexMultilineString) |
|
| 568 |
- return lexStringEscape(lx) |
|
| 569 | 564 |
} |
| 565 |
+ lx.backup() |
|
| 566 |
+ lx.push(lexMultilineString) |
|
| 567 |
+ return lexStringEscape(lx) |
|
| 570 | 568 |
} |
| 571 | 569 |
|
| 572 | 570 |
func lexStringEscape(lx *lexer) stateFn {
|
| ... | ... |
@@ -591,10 +701,9 @@ func lexStringEscape(lx *lexer) stateFn {
|
| 591 | 591 |
case 'U': |
| 592 | 592 |
return lexLongUnicodeEscape |
| 593 | 593 |
} |
| 594 |
- return lx.errorf("Invalid escape character %q. Only the following "+
|
|
| 594 |
+ return lx.errorf("invalid escape character %q; only the following "+
|
|
| 595 | 595 |
"escape characters are allowed: "+ |
| 596 |
- "\\b, \\t, \\n, \\f, \\r, \\\", \\/, \\\\, "+ |
|
| 597 |
- "\\uXXXX and \\UXXXXXXXX.", r) |
|
| 596 |
+ `\b, \t, \n, \f, \r, \", \\, \uXXXX, and \UXXXXXXXX`, r) |
|
| 598 | 597 |
} |
| 599 | 598 |
|
| 600 | 599 |
func lexShortUnicodeEscape(lx *lexer) stateFn {
|
| ... | ... |
@@ -602,8 +711,8 @@ func lexShortUnicodeEscape(lx *lexer) stateFn {
|
| 602 | 602 |
for i := 0; i < 4; i++ {
|
| 603 | 603 |
r = lx.next() |
| 604 | 604 |
if !isHexadecimal(r) {
|
| 605 |
- return lx.errorf("Expected four hexadecimal digits after '\\u', "+
|
|
| 606 |
- "but got '%s' instead.", lx.current()) |
|
| 605 |
+ return lx.errorf(`expected four hexadecimal digits after '\u', `+ |
|
| 606 |
+ "but got %q instead", lx.current()) |
|
| 607 | 607 |
} |
| 608 | 608 |
} |
| 609 | 609 |
return lx.pop() |
| ... | ... |
@@ -614,40 +723,43 @@ func lexLongUnicodeEscape(lx *lexer) stateFn {
|
| 614 | 614 |
for i := 0; i < 8; i++ {
|
| 615 | 615 |
r = lx.next() |
| 616 | 616 |
if !isHexadecimal(r) {
|
| 617 |
- return lx.errorf("Expected eight hexadecimal digits after '\\U', "+
|
|
| 618 |
- "but got '%s' instead.", lx.current()) |
|
| 617 |
+ return lx.errorf(`expected eight hexadecimal digits after '\U', `+ |
|
| 618 |
+ "but got %q instead", lx.current()) |
|
| 619 | 619 |
} |
| 620 | 620 |
} |
| 621 | 621 |
return lx.pop() |
| 622 | 622 |
} |
| 623 | 623 |
|
| 624 |
-// lexNumberOrDateStart consumes either a (positive) integer, float or |
|
| 625 |
-// datetime. It assumes that NO negative sign has been consumed. |
|
| 624 |
+// lexNumberOrDateStart consumes either an integer, a float, or datetime. |
|
| 626 | 625 |
func lexNumberOrDateStart(lx *lexer) stateFn {
|
| 627 | 626 |
r := lx.next() |
| 628 |
- if !isDigit(r) {
|
|
| 629 |
- if r == '.' {
|
|
| 630 |
- return lx.errorf("Floats must start with a digit, not '.'.")
|
|
| 631 |
- } else {
|
|
| 632 |
- return lx.errorf("Expected a digit but got %q.", r)
|
|
| 633 |
- } |
|
| 627 |
+ if isDigit(r) {
|
|
| 628 |
+ return lexNumberOrDate |
|
| 629 |
+ } |
|
| 630 |
+ switch r {
|
|
| 631 |
+ case '_': |
|
| 632 |
+ return lexNumber |
|
| 633 |
+ case 'e', 'E': |
|
| 634 |
+ return lexFloat |
|
| 635 |
+ case '.': |
|
| 636 |
+ return lx.errorf("floats must start with a digit, not '.'")
|
|
| 634 | 637 |
} |
| 635 |
- return lexNumberOrDate |
|
| 638 |
+ return lx.errorf("expected a digit but got %q", r)
|
|
| 636 | 639 |
} |
| 637 | 640 |
|
| 638 |
-// lexNumberOrDate consumes either a (positive) integer, float or datetime. |
|
| 641 |
+// lexNumberOrDate consumes either an integer, float or datetime. |
|
| 639 | 642 |
func lexNumberOrDate(lx *lexer) stateFn {
|
| 640 | 643 |
r := lx.next() |
| 641 |
- switch {
|
|
| 642 |
- case r == '-': |
|
| 643 |
- if lx.pos-lx.start != 5 {
|
|
| 644 |
- return lx.errorf("All ISO8601 dates must be in full Zulu form.")
|
|
| 645 |
- } |
|
| 646 |
- return lexDateAfterYear |
|
| 647 |
- case isDigit(r): |
|
| 644 |
+ if isDigit(r) {
|
|
| 648 | 645 |
return lexNumberOrDate |
| 649 |
- case r == '.': |
|
| 650 |
- return lexFloatStart |
|
| 646 |
+ } |
|
| 647 |
+ switch r {
|
|
| 648 |
+ case '-': |
|
| 649 |
+ return lexDatetime |
|
| 650 |
+ case '_': |
|
| 651 |
+ return lexNumber |
|
| 652 |
+ case '.', 'e', 'E': |
|
| 653 |
+ return lexFloat |
|
| 651 | 654 |
} |
| 652 | 655 |
|
| 653 | 656 |
lx.backup() |
| ... | ... |
@@ -655,46 +767,34 @@ func lexNumberOrDate(lx *lexer) stateFn {
|
| 655 | 655 |
return lx.pop() |
| 656 | 656 |
} |
| 657 | 657 |
|
| 658 |
-// lexDateAfterYear consumes a full Zulu Datetime in ISO8601 format. |
|
| 659 |
-// It assumes that "YYYY-" has already been consumed. |
|
| 660 |
-func lexDateAfterYear(lx *lexer) stateFn {
|
|
| 661 |
- formats := []rune{
|
|
| 662 |
- // digits are '0'. |
|
| 663 |
- // everything else is direct equality. |
|
| 664 |
- '0', '0', '-', '0', '0', |
|
| 665 |
- 'T', |
|
| 666 |
- '0', '0', ':', '0', '0', ':', '0', '0', |
|
| 667 |
- 'Z', |
|
| 658 |
+// lexDatetime consumes a Datetime, to a first approximation. |
|
| 659 |
+// The parser validates that it matches one of the accepted formats. |
|
| 660 |
+func lexDatetime(lx *lexer) stateFn {
|
|
| 661 |
+ r := lx.next() |
|
| 662 |
+ if isDigit(r) {
|
|
| 663 |
+ return lexDatetime |
|
| 668 | 664 |
} |
| 669 |
- for _, f := range formats {
|
|
| 670 |
- r := lx.next() |
|
| 671 |
- if f == '0' {
|
|
| 672 |
- if !isDigit(r) {
|
|
| 673 |
- return lx.errorf("Expected digit in ISO8601 datetime, "+
|
|
| 674 |
- "but found %q instead.", r) |
|
| 675 |
- } |
|
| 676 |
- } else if f != r {
|
|
| 677 |
- return lx.errorf("Expected %q in ISO8601 datetime, "+
|
|
| 678 |
- "but found %q instead.", f, r) |
|
| 679 |
- } |
|
| 665 |
+ switch r {
|
|
| 666 |
+ case '-', 'T', ':', '.', 'Z', '+': |
|
| 667 |
+ return lexDatetime |
|
| 680 | 668 |
} |
| 669 |
+ |
|
| 670 |
+ lx.backup() |
|
| 681 | 671 |
lx.emit(itemDatetime) |
| 682 | 672 |
return lx.pop() |
| 683 | 673 |
} |
| 684 | 674 |
|
| 685 |
-// lexNumberStart consumes either an integer or a float. It assumes that |
|
| 686 |
-// a negative sign has already been read, but that *no* digits have been |
|
| 687 |
-// consumed. lexNumberStart will move to the appropriate integer or float |
|
| 688 |
-// states. |
|
| 675 |
+// lexNumberStart consumes either an integer or a float. It assumes that a sign |
|
| 676 |
+// has already been read, but that *no* digits have been consumed. |
|
| 677 |
+// lexNumberStart will move to the appropriate integer or float states. |
|
| 689 | 678 |
func lexNumberStart(lx *lexer) stateFn {
|
| 690 |
- // we MUST see a digit. Even floats have to start with a digit. |
|
| 679 |
+ // We MUST see a digit. Even floats have to start with a digit. |
|
| 691 | 680 |
r := lx.next() |
| 692 | 681 |
if !isDigit(r) {
|
| 693 | 682 |
if r == '.' {
|
| 694 |
- return lx.errorf("Floats must start with a digit, not '.'.")
|
|
| 695 |
- } else {
|
|
| 696 |
- return lx.errorf("Expected a digit but got %q.", r)
|
|
| 683 |
+ return lx.errorf("floats must start with a digit, not '.'")
|
|
| 697 | 684 |
} |
| 685 |
+ return lx.errorf("expected a digit but got %q", r)
|
|
| 698 | 686 |
} |
| 699 | 687 |
return lexNumber |
| 700 | 688 |
} |
| ... | ... |
@@ -702,11 +802,14 @@ func lexNumberStart(lx *lexer) stateFn {
|
| 702 | 702 |
// lexNumber consumes an integer or a float after seeing the first digit. |
| 703 | 703 |
func lexNumber(lx *lexer) stateFn {
|
| 704 | 704 |
r := lx.next() |
| 705 |
- switch {
|
|
| 706 |
- case isDigit(r): |
|
| 705 |
+ if isDigit(r) {
|
|
| 707 | 706 |
return lexNumber |
| 708 |
- case r == '.': |
|
| 709 |
- return lexFloatStart |
|
| 707 |
+ } |
|
| 708 |
+ switch r {
|
|
| 709 |
+ case '_': |
|
| 710 |
+ return lexNumber |
|
| 711 |
+ case '.', 'e', 'E': |
|
| 712 |
+ return lexFloat |
|
| 710 | 713 |
} |
| 711 | 714 |
|
| 712 | 715 |
lx.backup() |
| ... | ... |
@@ -714,60 +817,42 @@ func lexNumber(lx *lexer) stateFn {
|
| 714 | 714 |
return lx.pop() |
| 715 | 715 |
} |
| 716 | 716 |
|
| 717 |
-// lexFloatStart starts the consumption of digits of a float after a '.'. |
|
| 718 |
-// Namely, at least one digit is required. |
|
| 719 |
-func lexFloatStart(lx *lexer) stateFn {
|
|
| 720 |
- r := lx.next() |
|
| 721 |
- if !isDigit(r) {
|
|
| 722 |
- return lx.errorf("Floats must have a digit after the '.', but got "+
|
|
| 723 |
- "%q instead.", r) |
|
| 724 |
- } |
|
| 725 |
- return lexFloat |
|
| 726 |
-} |
|
| 727 |
- |
|
| 728 |
-// lexFloat consumes the digits of a float after a '.'. |
|
| 729 |
-// Assumes that one digit has been consumed after a '.' already. |
|
| 717 |
+// lexFloat consumes the elements of a float. It allows any sequence of |
|
| 718 |
+// float-like characters, so floats emitted by the lexer are only a first |
|
| 719 |
+// approximation and must be validated by the parser. |
|
| 730 | 720 |
func lexFloat(lx *lexer) stateFn {
|
| 731 | 721 |
r := lx.next() |
| 732 | 722 |
if isDigit(r) {
|
| 733 | 723 |
return lexFloat |
| 734 | 724 |
} |
| 725 |
+ switch r {
|
|
| 726 |
+ case '_', '.', '-', '+', 'e', 'E': |
|
| 727 |
+ return lexFloat |
|
| 728 |
+ } |
|
| 735 | 729 |
|
| 736 | 730 |
lx.backup() |
| 737 | 731 |
lx.emit(itemFloat) |
| 738 | 732 |
return lx.pop() |
| 739 | 733 |
} |
| 740 | 734 |
|
| 741 |
-// lexConst consumes the s[1:] in s. It assumes that s[0] has already been |
|
| 742 |
-// consumed. |
|
| 743 |
-func lexConst(lx *lexer, s string) stateFn {
|
|
| 744 |
- for i := range s[1:] {
|
|
| 745 |
- if r := lx.next(); r != rune(s[i+1]) {
|
|
| 746 |
- return lx.errorf("Expected %q, but found %q instead.", s[:i+1],
|
|
| 747 |
- s[:i]+string(r)) |
|
| 735 |
+// lexBool consumes a bool string: 'true' or 'false. |
|
| 736 |
+func lexBool(lx *lexer) stateFn {
|
|
| 737 |
+ var rs []rune |
|
| 738 |
+ for {
|
|
| 739 |
+ r := lx.next() |
|
| 740 |
+ if !unicode.IsLetter(r) {
|
|
| 741 |
+ lx.backup() |
|
| 742 |
+ break |
|
| 748 | 743 |
} |
| 744 |
+ rs = append(rs, r) |
|
| 749 | 745 |
} |
| 750 |
- return nil |
|
| 751 |
-} |
|
| 752 |
- |
|
| 753 |
-// lexTrue consumes the "rue" in "true". It assumes that 't' has already |
|
| 754 |
-// been consumed. |
|
| 755 |
-func lexTrue(lx *lexer) stateFn {
|
|
| 756 |
- if fn := lexConst(lx, "true"); fn != nil {
|
|
| 757 |
- return fn |
|
| 758 |
- } |
|
| 759 |
- lx.emit(itemBool) |
|
| 760 |
- return lx.pop() |
|
| 761 |
-} |
|
| 762 |
- |
|
| 763 |
-// lexFalse consumes the "alse" in "false". It assumes that 'f' has already |
|
| 764 |
-// been consumed. |
|
| 765 |
-func lexFalse(lx *lexer) stateFn {
|
|
| 766 |
- if fn := lexConst(lx, "false"); fn != nil {
|
|
| 767 |
- return fn |
|
| 746 |
+ s := string(rs) |
|
| 747 |
+ switch s {
|
|
| 748 |
+ case "true", "false": |
|
| 749 |
+ lx.emit(itemBool) |
|
| 750 |
+ return lx.pop() |
|
| 768 | 751 |
} |
| 769 |
- lx.emit(itemBool) |
|
| 770 |
- return lx.pop() |
|
| 752 |
+ return lx.errorf("expected value but found %q instead", s)
|
|
| 771 | 753 |
} |
| 772 | 754 |
|
| 773 | 755 |
// lexCommentStart begins the lexing of a comment. It will emit |
| ... | ... |
@@ -779,7 +864,7 @@ func lexCommentStart(lx *lexer) stateFn {
|
| 779 | 779 |
} |
| 780 | 780 |
|
| 781 | 781 |
// lexComment lexes an entire comment. It assumes that '#' has been consumed. |
| 782 |
-// It will consume *up to* the first new line character, and pass control |
|
| 782 |
+// It will consume *up to* the first newline character, and pass control |
|
| 783 | 783 |
// back to the last state on the stack. |
| 784 | 784 |
func lexComment(lx *lexer) stateFn {
|
| 785 | 785 |
r := lx.peek() |
| ... | ... |
@@ -837,13 +922,7 @@ func (itype itemType) String() string {
|
| 837 | 837 |
return "EOF" |
| 838 | 838 |
case itemText: |
| 839 | 839 |
return "Text" |
| 840 |
- case itemString: |
|
| 841 |
- return "String" |
|
| 842 |
- case itemRawString: |
|
| 843 |
- return "String" |
|
| 844 |
- case itemMultilineString: |
|
| 845 |
- return "String" |
|
| 846 |
- case itemRawMultilineString: |
|
| 840 |
+ case itemString, itemRawString, itemMultilineString, itemRawMultilineString: |
|
| 847 | 841 |
return "String" |
| 848 | 842 |
case itemBool: |
| 849 | 843 |
return "Bool" |
| ... | ... |
@@ -2,7 +2,6 @@ package toml |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 | 4 |
"fmt" |
| 5 |
- "log" |
|
| 6 | 5 |
"strconv" |
| 7 | 6 |
"strings" |
| 8 | 7 |
"time" |
| ... | ... |
@@ -81,7 +80,7 @@ func (p *parser) next() item {
|
| 81 | 81 |
} |
| 82 | 82 |
|
| 83 | 83 |
func (p *parser) bug(format string, v ...interface{}) {
|
| 84 |
- log.Fatalf("BUG: %s\n\n", fmt.Sprintf(format, v...))
|
|
| 84 |
+ panic(fmt.Sprintf("BUG: "+format+"\n\n", v...))
|
|
| 85 | 85 |
} |
| 86 | 86 |
|
| 87 | 87 |
func (p *parser) expect(typ itemType) item {
|
| ... | ... |
@@ -179,10 +178,18 @@ func (p *parser) value(it item) (interface{}, tomlType) {
|
| 179 | 179 |
} |
| 180 | 180 |
p.bug("Expected boolean value, but got '%s'.", it.val)
|
| 181 | 181 |
case itemInteger: |
| 182 |
- num, err := strconv.ParseInt(it.val, 10, 64) |
|
| 182 |
+ if !numUnderscoresOK(it.val) {
|
|
| 183 |
+ p.panicf("Invalid integer %q: underscores must be surrounded by digits",
|
|
| 184 |
+ it.val) |
|
| 185 |
+ } |
|
| 186 |
+ val := strings.Replace(it.val, "_", "", -1) |
|
| 187 |
+ num, err := strconv.ParseInt(val, 10, 64) |
|
| 183 | 188 |
if err != nil {
|
| 184 |
- // See comment below for floats describing why we make a |
|
| 185 |
- // distinction between a bug and a user error. |
|
| 189 |
+ // Distinguish integer values. Normally, it'd be a bug if the lexer |
|
| 190 |
+ // provides an invalid integer, but it's possible that the number is |
|
| 191 |
+ // out of range of valid values (which the lexer cannot determine). |
|
| 192 |
+ // So mark the former as a bug but the latter as a legitimate user |
|
| 193 |
+ // error. |
|
| 186 | 194 |
if e, ok := err.(*strconv.NumError); ok && |
| 187 | 195 |
e.Err == strconv.ErrRange {
|
| 188 | 196 |
|
| ... | ... |
@@ -194,29 +201,57 @@ func (p *parser) value(it item) (interface{}, tomlType) {
|
| 194 | 194 |
} |
| 195 | 195 |
return num, p.typeOfPrimitive(it) |
| 196 | 196 |
case itemFloat: |
| 197 |
- num, err := strconv.ParseFloat(it.val, 64) |
|
| 197 |
+ parts := strings.FieldsFunc(it.val, func(r rune) bool {
|
|
| 198 |
+ switch r {
|
|
| 199 |
+ case '.', 'e', 'E': |
|
| 200 |
+ return true |
|
| 201 |
+ } |
|
| 202 |
+ return false |
|
| 203 |
+ }) |
|
| 204 |
+ for _, part := range parts {
|
|
| 205 |
+ if !numUnderscoresOK(part) {
|
|
| 206 |
+ p.panicf("Invalid float %q: underscores must be "+
|
|
| 207 |
+ "surrounded by digits", it.val) |
|
| 208 |
+ } |
|
| 209 |
+ } |
|
| 210 |
+ if !numPeriodsOK(it.val) {
|
|
| 211 |
+ // As a special case, numbers like '123.' or '1.e2', |
|
| 212 |
+ // which are valid as far as Go/strconv are concerned, |
|
| 213 |
+ // must be rejected because TOML says that a fractional |
|
| 214 |
+ // part consists of '.' followed by 1+ digits. |
|
| 215 |
+ p.panicf("Invalid float %q: '.' must be followed "+
|
|
| 216 |
+ "by one or more digits", it.val) |
|
| 217 |
+ } |
|
| 218 |
+ val := strings.Replace(it.val, "_", "", -1) |
|
| 219 |
+ num, err := strconv.ParseFloat(val, 64) |
|
| 198 | 220 |
if err != nil {
|
| 199 |
- // Distinguish float values. Normally, it'd be a bug if the lexer |
|
| 200 |
- // provides an invalid float, but it's possible that the float is |
|
| 201 |
- // out of range of valid values (which the lexer cannot determine). |
|
| 202 |
- // So mark the former as a bug but the latter as a legitimate user |
|
| 203 |
- // error. |
|
| 204 |
- // |
|
| 205 |
- // This is also true for integers. |
|
| 206 | 221 |
if e, ok := err.(*strconv.NumError); ok && |
| 207 | 222 |
e.Err == strconv.ErrRange {
|
| 208 | 223 |
|
| 209 | 224 |
p.panicf("Float '%s' is out of the range of 64-bit "+
|
| 210 | 225 |
"IEEE-754 floating-point numbers.", it.val) |
| 211 | 226 |
} else {
|
| 212 |
- p.bug("Expected float value, but got '%s'.", it.val)
|
|
| 227 |
+ p.panicf("Invalid float value: %q", it.val)
|
|
| 213 | 228 |
} |
| 214 | 229 |
} |
| 215 | 230 |
return num, p.typeOfPrimitive(it) |
| 216 | 231 |
case itemDatetime: |
| 217 |
- t, err := time.Parse("2006-01-02T15:04:05Z", it.val)
|
|
| 218 |
- if err != nil {
|
|
| 219 |
- p.bug("Expected Zulu formatted DateTime, but got '%s'.", it.val)
|
|
| 232 |
+ var t time.Time |
|
| 233 |
+ var ok bool |
|
| 234 |
+ var err error |
|
| 235 |
+ for _, format := range []string{
|
|
| 236 |
+ "2006-01-02T15:04:05Z07:00", |
|
| 237 |
+ "2006-01-02T15:04:05", |
|
| 238 |
+ "2006-01-02", |
|
| 239 |
+ } {
|
|
| 240 |
+ t, err = time.ParseInLocation(format, it.val, time.Local) |
|
| 241 |
+ if err == nil {
|
|
| 242 |
+ ok = true |
|
| 243 |
+ break |
|
| 244 |
+ } |
|
| 245 |
+ } |
|
| 246 |
+ if !ok {
|
|
| 247 |
+ p.panicf("Invalid TOML Datetime: %q.", it.val)
|
|
| 220 | 248 |
} |
| 221 | 249 |
return t, p.typeOfPrimitive(it) |
| 222 | 250 |
case itemArray: |
| ... | ... |
@@ -234,11 +269,75 @@ func (p *parser) value(it item) (interface{}, tomlType) {
|
| 234 | 234 |
types = append(types, typ) |
| 235 | 235 |
} |
| 236 | 236 |
return array, p.typeOfArray(types) |
| 237 |
+ case itemInlineTableStart: |
|
| 238 |
+ var ( |
|
| 239 |
+ hash = make(map[string]interface{})
|
|
| 240 |
+ outerContext = p.context |
|
| 241 |
+ outerKey = p.currentKey |
|
| 242 |
+ ) |
|
| 243 |
+ |
|
| 244 |
+ p.context = append(p.context, p.currentKey) |
|
| 245 |
+ p.currentKey = "" |
|
| 246 |
+ for it := p.next(); it.typ != itemInlineTableEnd; it = p.next() {
|
|
| 247 |
+ if it.typ != itemKeyStart {
|
|
| 248 |
+ p.bug("Expected key start but instead found %q, around line %d",
|
|
| 249 |
+ it.val, p.approxLine) |
|
| 250 |
+ } |
|
| 251 |
+ if it.typ == itemCommentStart {
|
|
| 252 |
+ p.expect(itemText) |
|
| 253 |
+ continue |
|
| 254 |
+ } |
|
| 255 |
+ |
|
| 256 |
+ // retrieve key |
|
| 257 |
+ k := p.next() |
|
| 258 |
+ p.approxLine = k.line |
|
| 259 |
+ kname := p.keyString(k) |
|
| 260 |
+ |
|
| 261 |
+ // retrieve value |
|
| 262 |
+ p.currentKey = kname |
|
| 263 |
+ val, typ := p.value(p.next()) |
|
| 264 |
+ // make sure we keep metadata up to date |
|
| 265 |
+ p.setType(kname, typ) |
|
| 266 |
+ p.ordered = append(p.ordered, p.context.add(p.currentKey)) |
|
| 267 |
+ hash[kname] = val |
|
| 268 |
+ } |
|
| 269 |
+ p.context = outerContext |
|
| 270 |
+ p.currentKey = outerKey |
|
| 271 |
+ return hash, tomlHash |
|
| 237 | 272 |
} |
| 238 | 273 |
p.bug("Unexpected value type: %s", it.typ)
|
| 239 | 274 |
panic("unreachable")
|
| 240 | 275 |
} |
| 241 | 276 |
|
| 277 |
+// numUnderscoresOK checks whether each underscore in s is surrounded by |
|
| 278 |
+// characters that are not underscores. |
|
| 279 |
+func numUnderscoresOK(s string) bool {
|
|
| 280 |
+ accept := false |
|
| 281 |
+ for _, r := range s {
|
|
| 282 |
+ if r == '_' {
|
|
| 283 |
+ if !accept {
|
|
| 284 |
+ return false |
|
| 285 |
+ } |
|
| 286 |
+ accept = false |
|
| 287 |
+ continue |
|
| 288 |
+ } |
|
| 289 |
+ accept = true |
|
| 290 |
+ } |
|
| 291 |
+ return accept |
|
| 292 |
+} |
|
| 293 |
+ |
|
| 294 |
+// numPeriodsOK checks whether every period in s is followed by a digit. |
|
| 295 |
+func numPeriodsOK(s string) bool {
|
|
| 296 |
+ period := false |
|
| 297 |
+ for _, r := range s {
|
|
| 298 |
+ if period && !isDigit(r) {
|
|
| 299 |
+ return false |
|
| 300 |
+ } |
|
| 301 |
+ period = r == '.' |
|
| 302 |
+ } |
|
| 303 |
+ return !period |
|
| 304 |
+} |
|
| 305 |
+ |
|
| 242 | 306 |
// establishContext sets the current context of the parser, |
| 243 | 307 |
// where the context is either a hash or an array of hashes. Which one is |
| 244 | 308 |
// set depends on the value of the `array` parameter. |
| ... | ... |
@@ -401,7 +500,7 @@ func stripFirstNewline(s string) string {
|
| 401 | 401 |
if len(s) == 0 || s[0] != '\n' {
|
| 402 | 402 |
return s |
| 403 | 403 |
} |
| 404 |
- return s[1:len(s)] |
|
| 404 |
+ return s[1:] |
|
| 405 | 405 |
} |
| 406 | 406 |
|
| 407 | 407 |
func stripEscapedWhitespace(s string) string {
|
| ... | ... |
@@ -481,12 +580,7 @@ func (p *parser) asciiEscapeToUnicode(bs []byte) rune {
|
| 481 | 481 |
p.bug("Could not parse '%s' as a hexadecimal number, but the "+
|
| 482 | 482 |
"lexer claims it's OK: %s", s, err) |
| 483 | 483 |
} |
| 484 |
- |
|
| 485 |
- // BUG(burntsushi) |
|
| 486 |
- // I honestly don't understand how this works. I can't seem |
|
| 487 |
- // to find a way to make this fail. I figured this would fail on invalid |
|
| 488 |
- // UTF-8 characters like U+DCFF, but it doesn't. |
|
| 489 |
- if !utf8.ValidString(string(rune(hex))) {
|
|
| 484 |
+ if !utf8.ValidRune(rune(hex)) {
|
|
| 490 | 485 |
p.panicf("Escaped character '\\u%s' is not valid UTF-8.", s)
|
| 491 | 486 |
} |
| 492 | 487 |
return rune(hex) |
| ... | ... |
@@ -92,11 +92,11 @@ func typeFields(t reflect.Type) []field {
|
| 92 | 92 |
// Scan f.typ for fields to include. |
| 93 | 93 |
for i := 0; i < f.typ.NumField(); i++ {
|
| 94 | 94 |
sf := f.typ.Field(i) |
| 95 |
- if sf.PkgPath != "" { // unexported
|
|
| 95 |
+ if sf.PkgPath != "" && !sf.Anonymous { // unexported
|
|
| 96 | 96 |
continue |
| 97 | 97 |
} |
| 98 |
- name := sf.Tag.Get("toml")
|
|
| 99 |
- if name == "-" {
|
|
| 98 |
+ opts := getOptions(sf.Tag) |
|
| 99 |
+ if opts.skip {
|
|
| 100 | 100 |
continue |
| 101 | 101 |
} |
| 102 | 102 |
index := make([]int, len(f.index)+1) |
| ... | ... |
@@ -110,8 +110,9 @@ func typeFields(t reflect.Type) []field {
|
| 110 | 110 |
} |
| 111 | 111 |
|
| 112 | 112 |
// Record found field and index sequence. |
| 113 |
- if name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct {
|
|
| 114 |
- tagged := name != "" |
|
| 113 |
+ if opts.name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct {
|
|
| 114 |
+ tagged := opts.name != "" |
|
| 115 |
+ name := opts.name |
|
| 115 | 116 |
if name == "" {
|
| 116 | 117 |
name = sf.Name |
| 117 | 118 |
} |