From b4bb91fc138f59583d64f5a23d85801d62d54567 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Sat, 30 Jan 2021 09:07:55 -0500 Subject: [PATCH 001/228] test --- Dockerfile | 11 - LICENSE | 21 - Makefile | 29 - benchmark.sh | 35 - benchmark/benchmark.json | 164 -- benchmark/benchmark.toml | 244 -- benchmark/benchmark.yml | 121 - benchmark/benchmark_test.go | 194 -- benchmark/go.mod | 11 - benchmark/go.sum | 8 - cmd/jsontoml/main.go | 82 - cmd/jsontoml/main_test.go | 92 - cmd/tomljson/main.go | 71 - cmd/tomljson/main_test.go | 90 - cmd/tomll/main.go | 65 - cmd/tomltestgen/main.go | 219 -- doc.go | 23 - doc_test.go | 170 -- example-crlf.toml | 30 - example.toml | 30 - fuzz.go | 31 - fuzz.sh | 15 - go.mod | 6 +- go.sum | 10 + keysparsing.go | 112 - keysparsing_test.go | 79 - lexer.go | 1031 -------- lexer_test.go | 1247 ---------- localtime.go | 281 --- localtime_test.go | 446 ---- marshal.go | 1293 ---------- marshal_OrderPreserve_test.toml | 39 - marshal_test.go | 4054 ------------------------------- marshal_test.toml | 39 - parser.go | 508 ---- parser_test.go | 1166 --------- position.go | 29 - position_test.go | 29 - query/README.md | 201 -- query/doc.go | 173 -- query/lexer.go | 357 --- query/lexer_test.go | 179 -- query/match.go | 311 --- query/match_test.go | 213 -- query/parser.go | 278 --- query/parser_test.go | 613 ----- query/query.go | 158 -- query/query_test.go | 151 -- query/tokens.go | 106 - token.go | 136 -- token_test.go | 69 - toml.go | 529 ---- toml_test.go | 261 -- toml_testgen_support_test.go | 119 - toml_testgen_test.go | 928 ------- tomlpub.go | 71 - tomltree_create.go | 155 -- tomltree_create_test.go | 243 -- tomltree_write.go | 535 ---- tomltree_write_test.go | 437 ---- 60 files changed, 13 insertions(+), 18335 deletions(-) delete mode 100644 Dockerfile delete mode 100644 LICENSE delete mode 100644 Makefile delete mode 100755 benchmark.sh delete mode 100644 benchmark/benchmark.json delete mode 100644 benchmark/benchmark.toml delete mode 100644 benchmark/benchmark.yml delete mode 100644 benchmark/benchmark_test.go delete mode 100644 benchmark/go.mod delete mode 100644 benchmark/go.sum delete mode 100644 cmd/jsontoml/main.go delete mode 100644 cmd/jsontoml/main_test.go delete mode 100644 cmd/tomljson/main.go delete mode 100644 cmd/tomljson/main_test.go delete mode 100644 cmd/tomll/main.go delete mode 100644 cmd/tomltestgen/main.go delete mode 100644 doc.go delete mode 100644 doc_test.go delete mode 100644 example-crlf.toml delete mode 100644 example.toml delete mode 100644 fuzz.go delete mode 100755 fuzz.sh delete mode 100644 keysparsing.go delete mode 100644 keysparsing_test.go delete mode 100644 lexer.go delete mode 100644 lexer_test.go delete mode 100644 localtime.go delete mode 100644 localtime_test.go delete mode 100644 marshal.go delete mode 100644 marshal_OrderPreserve_test.toml delete mode 100644 marshal_test.go delete mode 100644 marshal_test.toml delete mode 100644 parser.go delete mode 100644 parser_test.go delete mode 100644 position.go delete mode 100644 position_test.go delete mode 100644 query/README.md delete mode 100644 query/doc.go delete mode 100644 query/lexer.go delete mode 100644 query/lexer_test.go delete mode 100644 query/match.go delete mode 100644 query/match_test.go delete mode 100644 query/parser.go delete mode 100644 query/parser_test.go delete mode 100644 query/query.go delete mode 100644 query/query_test.go delete mode 100644 query/tokens.go delete mode 100644 token.go delete mode 100644 token_test.go delete mode 100644 toml.go delete mode 100644 toml_test.go delete mode 100644 toml_testgen_support_test.go delete mode 100644 toml_testgen_test.go delete mode 100644 tomlpub.go delete mode 100644 tomltree_create.go delete mode 100644 tomltree_create_test.go delete mode 100644 tomltree_write.go delete mode 100644 tomltree_write_test.go diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index fffdb016..00000000 --- a/Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -FROM golang:1.12-alpine3.9 as builder -WORKDIR /go/src/github.com/pelletier/go-toml -COPY . . -ENV CGO_ENABLED=0 -ENV GOOS=linux -RUN go install ./... - -FROM scratch -COPY --from=builder /go/bin/tomll /usr/bin/tomll -COPY --from=builder /go/bin/tomljson /usr/bin/tomljson -COPY --from=builder /go/bin/jsontoml /usr/bin/jsontoml diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 3a38ac28..00000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2013 - 2021 Thomas Pelletier, Eric Anderton - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/Makefile b/Makefile deleted file mode 100644 index 9e4503ae..00000000 --- a/Makefile +++ /dev/null @@ -1,29 +0,0 @@ -export CGO_ENABLED=0 -go := go -go.goos ?= $(shell echo `go version`|cut -f4 -d ' '|cut -d '/' -f1) -go.goarch ?= $(shell echo `go version`|cut -f4 -d ' '|cut -d '/' -f2) - -out.tools := tomll tomljson jsontoml -out.dist := $(out.tools:=_$(go.goos)_$(go.goarch).tar.xz) -sources := $(wildcard **/*.go) - - -.PHONY: -tools: $(out.tools) - -$(out.tools): $(sources) - GOOS=$(go.goos) GOARCH=$(go.goarch) $(go) build ./cmd/$@ - -.PHONY: -dist: $(out.dist) - -$(out.dist):%_$(go.goos)_$(go.goarch).tar.xz: % - if [ "$(go.goos)" = "windows" ]; then \ - tar -cJf $@ $^.exe; \ - else \ - tar -cJf $@ $^; \ - fi - -.PHONY: -clean: - rm -rf $(out.tools) $(out.dist) diff --git a/benchmark.sh b/benchmark.sh deleted file mode 100755 index a69d3040..00000000 --- a/benchmark.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/bash - -set -ex - -reference_ref=${1:-master} -reference_git=${2:-.} - -if ! `hash benchstat 2>/dev/null`; then - echo "Installing benchstat" - go get golang.org/x/perf/cmd/benchstat -fi - -tempdir=`mktemp -d /tmp/go-toml-benchmark-XXXXXX` -ref_tempdir="${tempdir}/ref" -ref_benchmark="${ref_tempdir}/benchmark-`echo -n ${reference_ref}|tr -s '/' '-'`.txt" -local_benchmark="`pwd`/benchmark-local.txt" - -echo "=== ${reference_ref} (${ref_tempdir})" -git clone ${reference_git} ${ref_tempdir} >/dev/null 2>/dev/null -pushd ${ref_tempdir} >/dev/null -git checkout ${reference_ref} >/dev/null 2>/dev/null -go test -bench=. -benchmem | tee ${ref_benchmark} -cd benchmark -go test -bench=. -benchmem | tee -a ${ref_benchmark} -popd >/dev/null - -echo "" -echo "=== local" -go test -bench=. -benchmem | tee ${local_benchmark} -cd benchmark -go test -bench=. -benchmem | tee -a ${local_benchmark} - -echo "" -echo "=== diff" -benchstat -delta-test=none ${ref_benchmark} ${local_benchmark} diff --git a/benchmark/benchmark.json b/benchmark/benchmark.json deleted file mode 100644 index 86f99c6a..00000000 --- a/benchmark/benchmark.json +++ /dev/null @@ -1,164 +0,0 @@ -{ - "array": { - "key1": [ - 1, - 2, - 3 - ], - "key2": [ - "red", - "yellow", - "green" - ], - "key3": [ - [ - 1, - 2 - ], - [ - 3, - 4, - 5 - ] - ], - "key4": [ - [ - 1, - 2 - ], - [ - "a", - "b", - "c" - ] - ], - "key5": [ - 1, - 2, - 3 - ], - "key6": [ - 1, - 2 - ] - }, - "boolean": { - "False": false, - "True": true - }, - "datetime": { - "key1": "1979-05-27T07:32:00Z", - "key2": "1979-05-27T00:32:00-07:00", - "key3": "1979-05-27T00:32:00.999999-07:00" - }, - "float": { - "both": { - "key": 6.626e-34 - }, - "exponent": { - "key1": 5e+22, - "key2": 1000000, - "key3": -0.02 - }, - "fractional": { - "key1": 1, - "key2": 3.1415, - "key3": -0.01 - }, - "underscores": { - "key1": 9224617.445991227, - "key2": 1e+100 - } - }, - "fruit": [{ - "name": "apple", - "physical": { - "color": "red", - "shape": "round" - }, - "variety": [{ - "name": "red delicious" - }, - { - "name": "granny smith" - } - ] - }, - { - "name": "banana", - "variety": [{ - "name": "plantain" - }] - } - ], - "integer": { - "key1": 99, - "key2": 42, - "key3": 0, - "key4": -17, - "underscores": { - "key1": 1000, - "key2": 5349221, - "key3": 12345 - } - }, - "products": [{ - "name": "Hammer", - "sku": 738594937 - }, - {}, - { - "color": "gray", - "name": "Nail", - "sku": 284758393 - } - ], - "string": { - "basic": { - "basic": "I'm a string. \"You can quote me\". Name\tJosé\nLocation\tSF." - }, - "literal": { - "multiline": { - "lines": "The first newline is\ntrimmed in raw strings.\n All other whitespace\n is preserved.\n", - "regex2": "I [dw]on't need \\d{2} apples" - }, - "quoted": "Tom \"Dubs\" Preston-Werner", - "regex": "\u003c\\i\\c*\\s*\u003e", - "winpath": "C:\\Users\\nodejs\\templates", - "winpath2": "\\\\ServerX\\admin$\\system32\\" - }, - "multiline": { - "continued": { - "key1": "The quick brown fox jumps over the lazy dog.", - "key2": "The quick brown fox jumps over the lazy dog.", - "key3": "The quick brown fox jumps over the lazy dog." - }, - "key1": "One\nTwo", - "key2": "One\nTwo", - "key3": "One\nTwo" - } - }, - "table": { - "inline": { - "name": { - "first": "Tom", - "last": "Preston-Werner" - }, - "point": { - "x": 1, - "y": 2 - } - }, - "key": "value", - "subtable": { - "key": "another value" - } - }, - "x": { - "y": { - "z": { - "w": {} - } - } - } -} diff --git a/benchmark/benchmark.toml b/benchmark/benchmark.toml deleted file mode 100644 index dfd77e09..00000000 --- a/benchmark/benchmark.toml +++ /dev/null @@ -1,244 +0,0 @@ -################################################################################ -## Comment - -# Speak your mind with the hash symbol. They go from the symbol to the end of -# the line. - - -################################################################################ -## Table - -# Tables (also known as hash tables or dictionaries) are collections of -# key/value pairs. They appear in square brackets on a line by themselves. - -[table] - -key = "value" # Yeah, you can do this. - -# Nested tables are denoted by table names with dots in them. Name your tables -# whatever crap you please, just don't use #, ., [ or ]. - -[table.subtable] - -key = "another value" - -# You don't need to specify all the super-tables if you don't want to. TOML -# knows how to do it for you. - -# [x] you -# [x.y] don't -# [x.y.z] need these -[x.y.z.w] # for this to work - - -################################################################################ -## Inline Table - -# Inline tables provide a more compact syntax for expressing tables. They are -# especially useful for grouped data that can otherwise quickly become verbose. -# Inline tables are enclosed in curly braces `{` and `}`. No newlines are -# allowed between the curly braces unless they are valid within a value. - -[table.inline] - -name = { first = "Tom", last = "Preston-Werner" } -point = { x = 1, y = 2 } - - -################################################################################ -## String - -# There are four ways to express strings: basic, multi-line basic, literal, and -# multi-line literal. All strings must contain only valid UTF-8 characters. - -[string.basic] - -basic = "I'm a string. \"You can quote me\". Name\tJos\u00E9\nLocation\tSF." - -[string.multiline] - -# The following strings are byte-for-byte equivalent: -key1 = "One\nTwo" -key2 = """One\nTwo""" -key3 = """ -One -Two""" - -[string.multiline.continued] - -# The following strings are byte-for-byte equivalent: -key1 = "The quick brown fox jumps over the lazy dog." - -key2 = """ -The quick brown \ - - - fox jumps over \ - the lazy dog.""" - -key3 = """\ - The quick brown \ - fox jumps over \ - the lazy dog.\ - """ - -[string.literal] - -# What you see is what you get. -winpath = 'C:\Users\nodejs\templates' -winpath2 = '\\ServerX\admin$\system32\' -quoted = 'Tom "Dubs" Preston-Werner' -regex = '<\i\c*\s*>' - - -[string.literal.multiline] - -regex2 = '''I [dw]on't need \d{2} apples''' -lines = ''' -The first newline is -trimmed in raw strings. - All other whitespace - is preserved. -''' - - -################################################################################ -## Integer - -# Integers are whole numbers. Positive numbers may be prefixed with a plus sign. -# Negative numbers are prefixed with a minus sign. - -[integer] - -key1 = +99 -key2 = 42 -key3 = 0 -key4 = -17 - -[integer.underscores] - -# For large numbers, you may use underscores to enhance readability. Each -# underscore must be surrounded by at least one digit. -key1 = 1_000 -key2 = 5_349_221 -key3 = 1_2_3_4_5 # valid but inadvisable - - -################################################################################ -## Float - -# A float consists of an integer part (which may be prefixed with a plus or -# minus sign) followed by a fractional part and/or an exponent part. - -[float.fractional] - -key1 = +1.0 -key2 = 3.1415 -key3 = -0.01 - -[float.exponent] - -key1 = 5e+22 -key2 = 1e6 -key3 = -2E-2 - -[float.both] - -key = 6.626e-34 - -[float.underscores] - -key1 = 9_224_617.445_991_228_313 -key2 = 1e1_00 - - -################################################################################ -## Boolean - -# Booleans are just the tokens you're used to. Always lowercase. - -[boolean] - -True = true -False = false - - -################################################################################ -## Datetime - -# Datetimes are RFC 3339 dates. - -[datetime] - -key1 = 1979-05-27T07:32:00Z -key2 = 1979-05-27T00:32:00-07:00 -key3 = 1979-05-27T00:32:00.999999-07:00 - - -################################################################################ -## Array - -# Arrays are square brackets with other primitives inside. Whitespace is -# ignored. Elements are separated by commas. Data types may not be mixed. - -[array] - -key1 = [ 1, 2, 3 ] -key2 = [ "red", "yellow", "green" ] -key3 = [ [ 1, 2 ], [3, 4, 5] ] -#key4 = [ [ 1, 2 ], ["a", "b", "c"] ] # this is ok - -# Arrays can also be multiline. So in addition to ignoring whitespace, arrays -# also ignore newlines between the brackets. Terminating commas are ok before -# the closing bracket. - -key5 = [ - 1, 2, 3 -] -key6 = [ - 1, - 2, # this is ok -] - - -################################################################################ -## Array of Tables - -# These can be expressed by using a table name in double brackets. Each table -# with the same double bracketed name will be an element in the array. The -# tables are inserted in the order encountered. - -[[products]] - -name = "Hammer" -sku = 738594937 - -[[products]] - -[[products]] - -name = "Nail" -sku = 284758393 -color = "gray" - - -# You can create nested arrays of tables as well. - -[[fruit]] - name = "apple" - - [fruit.physical] - color = "red" - shape = "round" - - [[fruit.variety]] - name = "red delicious" - - [[fruit.variety]] - name = "granny smith" - -[[fruit]] - name = "banana" - - [[fruit.variety]] - name = "plantain" diff --git a/benchmark/benchmark.yml b/benchmark/benchmark.yml deleted file mode 100644 index 0bd19f08..00000000 --- a/benchmark/benchmark.yml +++ /dev/null @@ -1,121 +0,0 @@ ---- -array: - key1: - - 1 - - 2 - - 3 - key2: - - red - - yellow - - green - key3: - - - 1 - - 2 - - - 3 - - 4 - - 5 - key4: - - - 1 - - 2 - - - a - - b - - c - key5: - - 1 - - 2 - - 3 - key6: - - 1 - - 2 -boolean: - 'False': false - 'True': true -datetime: - key1: '1979-05-27T07:32:00Z' - key2: '1979-05-27T00:32:00-07:00' - key3: '1979-05-27T00:32:00.999999-07:00' -float: - both: - key: 6.626e-34 - exponent: - key1: 5.0e+22 - key2: 1000000 - key3: -0.02 - fractional: - key1: 1 - key2: 3.1415 - key3: -0.01 - underscores: - key1: 9224617.445991227 - key2: 1.0e+100 -fruit: -- name: apple - physical: - color: red - shape: round - variety: - - name: red delicious - - name: granny smith -- name: banana - variety: - - name: plantain -integer: - key1: 99 - key2: 42 - key3: 0 - key4: -17 - underscores: - key1: 1000 - key2: 5349221 - key3: 12345 -products: -- name: Hammer - sku: 738594937 -- {} -- color: gray - name: Nail - sku: 284758393 -string: - basic: - basic: "I'm a string. \"You can quote me\". Name\tJosé\nLocation\tSF." - literal: - multiline: - lines: | - The first newline is - trimmed in raw strings. - All other whitespace - is preserved. - regex2: I [dw]on't need \d{2} apples - quoted: Tom "Dubs" Preston-Werner - regex: "<\\i\\c*\\s*>" - winpath: C:\Users\nodejs\templates - winpath2: "\\\\ServerX\\admin$\\system32\\" - multiline: - continued: - key1: The quick brown fox jumps over the lazy dog. - key2: The quick brown fox jumps over the lazy dog. - key3: The quick brown fox jumps over the lazy dog. - key1: |- - One - Two - key2: |- - One - Two - key3: |- - One - Two -table: - inline: - name: - first: Tom - last: Preston-Werner - point: - x: 1 - y: 2 - key: value - subtable: - key: another value -x: - y: - z: - w: {} diff --git a/benchmark/benchmark_test.go b/benchmark/benchmark_test.go deleted file mode 100644 index faf2da88..00000000 --- a/benchmark/benchmark_test.go +++ /dev/null @@ -1,194 +0,0 @@ -package benchmark - -import ( - "bytes" - "encoding/json" - "io/ioutil" - "testing" - "time" - - burntsushi "github.com/BurntSushi/toml" - "github.com/pelletier/go-toml" - "gopkg.in/yaml.v2" -) - -type benchmarkDoc struct { - Table struct { - Key string - Subtable struct { - Key string - } - Inline struct { - Name struct { - First string - Last string - } - Point struct { - X int64 - U int64 - } - } - } - String struct { - Basic struct { - Basic string - } - Multiline struct { - Key1 string - Key2 string - Key3 string - Continued struct { - Key1 string - Key2 string - Key3 string - } - } - Literal struct { - Winpath string - Winpath2 string - Quoted string - Regex string - Multiline struct { - Regex2 string - Lines string - } - } - } - Integer struct { - Key1 int64 - Key2 int64 - Key3 int64 - Key4 int64 - Underscores struct { - Key1 int64 - Key2 int64 - Key3 int64 - } - } - Float struct { - Fractional struct { - Key1 float64 - Key2 float64 - Key3 float64 - } - Exponent struct { - Key1 float64 - Key2 float64 - Key3 float64 - } - Both struct { - Key float64 - } - Underscores struct { - Key1 float64 - Key2 float64 - } - } - Boolean struct { - True bool - False bool - } - Datetime struct { - Key1 time.Time - Key2 time.Time - Key3 time.Time - } - Array struct { - Key1 []int64 - Key2 []string - Key3 [][]int64 - // TODO: Key4 not supported by go-toml's Unmarshal - Key5 []int64 - Key6 []int64 - } - Products []struct { - Name string - Sku int64 - Color string - } - Fruit []struct { - Name string - Physical struct { - Color string - Shape string - Variety []struct { - Name string - } - } - } -} - -func BenchmarkParseToml(b *testing.B) { - fileBytes, err := ioutil.ReadFile("benchmark.toml") - if err != nil { - b.Fatal(err) - } - b.ResetTimer() - for i := 0; i < b.N; i++ { - _, err := toml.LoadReader(bytes.NewReader(fileBytes)) - if err != nil { - b.Fatal(err) - } - } -} - -func BenchmarkUnmarshalToml(b *testing.B) { - bytes, err := ioutil.ReadFile("benchmark.toml") - if err != nil { - b.Fatal(err) - } - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - target := benchmarkDoc{} - err := toml.Unmarshal(bytes, &target) - if err != nil { - b.Fatal(err) - } - } -} - -func BenchmarkUnmarshalBurntSushiToml(b *testing.B) { - bytes, err := ioutil.ReadFile("benchmark.toml") - if err != nil { - b.Fatal(err) - } - b.ResetTimer() - for i := 0; i < b.N; i++ { - target := benchmarkDoc{} - err := burntsushi.Unmarshal(bytes, &target) - if err != nil { - b.Fatal(err) - } - } -} - -func BenchmarkUnmarshalJson(b *testing.B) { - bytes, err := ioutil.ReadFile("benchmark.json") - if err != nil { - b.Fatal(err) - } - b.ResetTimer() - for i := 0; i < b.N; i++ { - target := benchmarkDoc{} - err := json.Unmarshal(bytes, &target) - if err != nil { - b.Fatal(err) - } - } -} - -func BenchmarkUnmarshalYaml(b *testing.B) { - bytes, err := ioutil.ReadFile("benchmark.yml") - if err != nil { - b.Fatal(err) - } - b.ResetTimer() - for i := 0; i < b.N; i++ { - target := benchmarkDoc{} - err := yaml.Unmarshal(bytes, &target) - if err != nil { - b.Fatal(err) - } - } -} diff --git a/benchmark/go.mod b/benchmark/go.mod deleted file mode 100644 index 82713039..00000000 --- a/benchmark/go.mod +++ /dev/null @@ -1,11 +0,0 @@ -module github.com/pelletier/go-toml/benchmark - -go 1.12 - -require ( - github.com/BurntSushi/toml v0.3.1 - github.com/pelletier/go-toml v0.0.0 - gopkg.in/yaml.v2 v2.3.0 -) - -replace github.com/pelletier/go-toml => ../ diff --git a/benchmark/go.sum b/benchmark/go.sum deleted file mode 100644 index 6772ccc4..00000000 --- a/benchmark/go.sum +++ /dev/null @@ -1,8 +0,0 @@ -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/cmd/jsontoml/main.go b/cmd/jsontoml/main.go deleted file mode 100644 index 0acc61ee..00000000 --- a/cmd/jsontoml/main.go +++ /dev/null @@ -1,82 +0,0 @@ -// Jsontoml reads JSON and converts to TOML. -// -// Usage: -// cat file.toml | jsontoml > file.json -// jsontoml file1.toml > file.json -package main - -import ( - "encoding/json" - "flag" - "fmt" - "io" - "io/ioutil" - "os" - - "github.com/pelletier/go-toml" -) - -func main() { - flag.Usage = func() { - fmt.Fprintln(os.Stderr, "jsontoml can be used in two ways:") - fmt.Fprintln(os.Stderr, "Writing to STDIN and reading from STDOUT:") - fmt.Fprintln(os.Stderr, "") - fmt.Fprintln(os.Stderr, "") - fmt.Fprintln(os.Stderr, "Reading from a file name:") - fmt.Fprintln(os.Stderr, " tomljson file.toml") - } - flag.Parse() - os.Exit(processMain(flag.Args(), os.Stdin, os.Stdout, os.Stderr)) -} - -func processMain(files []string, defaultInput io.Reader, output io.Writer, errorOutput io.Writer) int { - // read from stdin and print to stdout - inputReader := defaultInput - - if len(files) > 0 { - file, err := os.Open(files[0]) - if err != nil { - printError(err, errorOutput) - return -1 - } - inputReader = file - defer file.Close() - } - s, err := reader(inputReader) - if err != nil { - printError(err, errorOutput) - return -1 - } - io.WriteString(output, s) - return 0 -} - -func printError(err error, output io.Writer) { - io.WriteString(output, err.Error()+"\n") -} - -func reader(r io.Reader) (string, error) { - jsonMap := make(map[string]interface{}) - jsonBytes, err := ioutil.ReadAll(r) - if err != nil { - return "", err - } - err = json.Unmarshal(jsonBytes, &jsonMap) - if err != nil { - return "", err - } - - tree, err := toml.TreeFromMap(jsonMap) - if err != nil { - return "", err - } - return mapToTOML(tree) -} - -func mapToTOML(t *toml.Tree) (string, error) { - tomlBytes, err := t.ToTomlString() - if err != nil { - return "", err - } - return string(tomlBytes[:]), nil -} diff --git a/cmd/jsontoml/main_test.go b/cmd/jsontoml/main_test.go deleted file mode 100644 index c591c66e..00000000 --- a/cmd/jsontoml/main_test.go +++ /dev/null @@ -1,92 +0,0 @@ -package main - -import ( - "bytes" - "io/ioutil" - "os" - "runtime" - "strings" - "testing" -) - -func expectBufferEquality(t *testing.T, name string, buffer *bytes.Buffer, expected string) { - output := buffer.String() - if output != expected { - t.Errorf("incorrect %s: \n%sexpected %s: \n%s", name, output, name, expected) - t.Log([]rune(output)) - t.Log([]rune(expected)) - } -} - -func expectProcessMainResults(t *testing.T, input string, args []string, exitCode int, expectedOutput string, expectedError string) { - inputReader := strings.NewReader(input) - - outputBuffer := new(bytes.Buffer) - errorBuffer := new(bytes.Buffer) - - returnCode := processMain(args, inputReader, outputBuffer, errorBuffer) - - expectBufferEquality(t, "output", outputBuffer, expectedOutput) - expectBufferEquality(t, "error", errorBuffer, expectedError) - - if returnCode != exitCode { - t.Error("incorrect return code:", returnCode, "expected", exitCode) - } -} - -func TestProcessMainReadFromStdin(t *testing.T) { - expectedOutput := ` -[mytoml] - a = 42.0 -` - input := `{ - "mytoml": { - "a": 42 - } -} -` - expectedError := `` - expectedExitCode := 0 - - expectProcessMainResults(t, input, []string{}, expectedExitCode, expectedOutput, expectedError) -} - -func TestProcessMainReadFromFile(t *testing.T) { - input := `{ - "mytoml": { - "a": 42 - } -} -` - tmpfile, err := ioutil.TempFile("", "example.json") - if err != nil { - t.Fatal(err) - } - if _, err := tmpfile.Write([]byte(input)); err != nil { - t.Fatal(err) - } - - defer os.Remove(tmpfile.Name()) - - expectedOutput := ` -[mytoml] - a = 42.0 -` - expectedError := `` - expectedExitCode := 0 - - expectProcessMainResults(t, ``, []string{tmpfile.Name()}, expectedExitCode, expectedOutput, expectedError) -} - -func TestProcessMainReadFromMissingFile(t *testing.T) { - var expectedError string - if runtime.GOOS == "windows" { - expectedError = `open /this/file/does/not/exist: The system cannot find the path specified. -` - } else { - expectedError = `open /this/file/does/not/exist: no such file or directory -` - } - - expectProcessMainResults(t, ``, []string{"/this/file/does/not/exist"}, -1, ``, expectedError) -} diff --git a/cmd/tomljson/main.go b/cmd/tomljson/main.go deleted file mode 100644 index 322315b5..00000000 --- a/cmd/tomljson/main.go +++ /dev/null @@ -1,71 +0,0 @@ -// Tomljson reads TOML and converts to JSON. -// -// Usage: -// cat file.toml | tomljson > file.json -// tomljson file1.toml > file.json -package main - -import ( - "encoding/json" - "flag" - "fmt" - "io" - "os" - - "github.com/pelletier/go-toml" -) - -func main() { - flag.Usage = func() { - fmt.Fprintln(os.Stderr, "tomljson can be used in two ways:") - fmt.Fprintln(os.Stderr, "Writing to STDIN and reading from STDOUT:") - fmt.Fprintln(os.Stderr, " cat file.toml | tomljson > file.json") - fmt.Fprintln(os.Stderr, "") - fmt.Fprintln(os.Stderr, "Reading from a file name:") - fmt.Fprintln(os.Stderr, " tomljson file.toml") - } - flag.Parse() - os.Exit(processMain(flag.Args(), os.Stdin, os.Stdout, os.Stderr)) -} - -func processMain(files []string, defaultInput io.Reader, output io.Writer, errorOutput io.Writer) int { - // read from stdin and print to stdout - inputReader := defaultInput - - if len(files) > 0 { - var err error - inputReader, err = os.Open(files[0]) - if err != nil { - printError(err, errorOutput) - return -1 - } - } - s, err := reader(inputReader) - if err != nil { - printError(err, errorOutput) - return -1 - } - io.WriteString(output, s+"\n") - return 0 -} - -func printError(err error, output io.Writer) { - io.WriteString(output, err.Error()+"\n") -} - -func reader(r io.Reader) (string, error) { - tree, err := toml.LoadReader(r) - if err != nil { - return "", err - } - return mapToJSON(tree) -} - -func mapToJSON(tree *toml.Tree) (string, error) { - treeMap := tree.ToMap() - bytes, err := json.MarshalIndent(treeMap, "", " ") - if err != nil { - return "", err - } - return string(bytes[:]), nil -} diff --git a/cmd/tomljson/main_test.go b/cmd/tomljson/main_test.go deleted file mode 100644 index d515ee02..00000000 --- a/cmd/tomljson/main_test.go +++ /dev/null @@ -1,90 +0,0 @@ -package main - -import ( - "bytes" - "io/ioutil" - "os" - "runtime" - "strings" - "testing" -) - -func expectBufferEquality(t *testing.T, name string, buffer *bytes.Buffer, expected string) { - output := buffer.String() - if output != expected { - t.Errorf("incorrect %s:\n%s\n\nexpected %s:\n%s", name, output, name, expected) - t.Log([]rune(output)) - t.Log([]rune(expected)) - } -} - -func expectProcessMainResults(t *testing.T, input string, args []string, exitCode int, expectedOutput string, expectedError string) { - inputReader := strings.NewReader(input) - outputBuffer := new(bytes.Buffer) - errorBuffer := new(bytes.Buffer) - - returnCode := processMain(args, inputReader, outputBuffer, errorBuffer) - - expectBufferEquality(t, "output", outputBuffer, expectedOutput) - expectBufferEquality(t, "error", errorBuffer, expectedError) - - if returnCode != exitCode { - t.Error("incorrect return code:", returnCode, "expected", exitCode) - } -} - -func TestProcessMainReadFromStdin(t *testing.T) { - input := ` - [mytoml] - a = 42` - expectedOutput := `{ - "mytoml": { - "a": 42 - } -} -` - expectedError := `` - expectedExitCode := 0 - - expectProcessMainResults(t, input, []string{}, expectedExitCode, expectedOutput, expectedError) -} - -func TestProcessMainReadFromFile(t *testing.T) { - input := ` - [mytoml] - a = 42` - - tmpfile, err := ioutil.TempFile("", "example.toml") - if err != nil { - t.Fatal(err) - } - if _, err := tmpfile.Write([]byte(input)); err != nil { - t.Fatal(err) - } - - defer os.Remove(tmpfile.Name()) - - expectedOutput := `{ - "mytoml": { - "a": 42 - } -} -` - expectedError := `` - expectedExitCode := 0 - - expectProcessMainResults(t, ``, []string{tmpfile.Name()}, expectedExitCode, expectedOutput, expectedError) -} - -func TestProcessMainReadFromMissingFile(t *testing.T) { - var expectedError string - if runtime.GOOS == "windows" { - expectedError = `open /this/file/does/not/exist: The system cannot find the path specified. -` - } else { - expectedError = `open /this/file/does/not/exist: no such file or directory -` - } - - expectProcessMainResults(t, ``, []string{"/this/file/does/not/exist"}, -1, ``, expectedError) -} diff --git a/cmd/tomll/main.go b/cmd/tomll/main.go deleted file mode 100644 index 93ab0c96..00000000 --- a/cmd/tomll/main.go +++ /dev/null @@ -1,65 +0,0 @@ -// Tomll is a linter for TOML -// -// Usage: -// cat file.toml | tomll > file_linted.toml -// tomll file1.toml file2.toml # lint the two files in place -package main - -import ( - "flag" - "fmt" - "io" - "io/ioutil" - "os" - - "github.com/pelletier/go-toml" -) - -func main() { - flag.Usage = func() { - fmt.Fprintln(os.Stderr, "tomll can be used in two ways:") - fmt.Fprintln(os.Stderr, "Writing to STDIN and reading from STDOUT:") - fmt.Fprintln(os.Stderr, " cat file.toml | tomll > file.toml") - fmt.Fprintln(os.Stderr, "") - fmt.Fprintln(os.Stderr, "Reading and updating a list of files:") - fmt.Fprintln(os.Stderr, " tomll a.toml b.toml c.toml") - fmt.Fprintln(os.Stderr, "") - fmt.Fprintln(os.Stderr, "When given a list of files, tomll will modify all files in place without asking.") - } - flag.Parse() - // read from stdin and print to stdout - if flag.NArg() == 0 { - s, err := lintReader(os.Stdin) - if err != nil { - io.WriteString(os.Stderr, err.Error()) - os.Exit(-1) - } - io.WriteString(os.Stdout, s) - } else { - // otherwise modify a list of files - for _, filename := range flag.Args() { - s, err := lintFile(filename) - if err != nil { - io.WriteString(os.Stderr, err.Error()) - os.Exit(-1) - } - ioutil.WriteFile(filename, []byte(s), 0644) - } - } -} - -func lintFile(filename string) (string, error) { - tree, err := toml.LoadFile(filename) - if err != nil { - return "", err - } - return tree.String(), nil -} - -func lintReader(r io.Reader) (string, error) { - tree, err := toml.LoadReader(r) - if err != nil { - return "", err - } - return tree.String(), nil -} diff --git a/cmd/tomltestgen/main.go b/cmd/tomltestgen/main.go deleted file mode 100644 index cd9bdd77..00000000 --- a/cmd/tomltestgen/main.go +++ /dev/null @@ -1,219 +0,0 @@ -// Tomltestgen is a program that retrieves a given version of -// https://github.com/BurntSushi/toml-test and generates go code for go-toml's unit tests -// based on the test files. -// -// Usage: go run github.com/pelletier/go-toml/cmd/tomltestgen > toml_testgen_test.go -package main - -import ( - "archive/zip" - "bytes" - "flag" - "fmt" - "go/format" - "io" - "io/ioutil" - "log" - "net/http" - "os" - "regexp" - "strconv" - "strings" - "text/template" - "time" -) - -type invalid struct { - Name string - Input string -} - -type valid struct { - Name string - Input string - JsonRef string -} - -type testsCollection struct { - Ref string - Timestamp string - Invalid []invalid - Valid []valid - Count int -} - -const srcTemplate = "// Generated by tomltestgen for toml-test ref {{.Ref}} on {{.Timestamp}}\n" + - "package toml\n" + - " import (\n" + - " \"testing\"\n" + - ")\n" + - - "{{range .Invalid}}\n" + - "func TestInvalid{{.Name}}(t *testing.T) {\n" + - " input := {{.Input|gostr}}\n" + - " testgenInvalid(t, input)\n" + - "}\n" + - "{{end}}\n" + - "\n" + - "{{range .Valid}}\n" + - "func TestValid{{.Name}}(t *testing.T) {\n" + - " input := {{.Input|gostr}}\n" + - " jsonRef := {{.JsonRef|gostr}}\n" + - " testgenValid(t, input, jsonRef)\n" + - "}\n" + - "{{end}}\n" - -func downloadTmpFile(url string) string { - log.Println("starting to download file from", url) - resp, err := http.Get(url) - if err != nil { - panic(err) - } - defer resp.Body.Close() - - tmpfile, err := ioutil.TempFile("", "toml-test-*.zip") - if err != nil { - panic(err) - } - defer tmpfile.Close() - - copiedLen, err := io.Copy(tmpfile, resp.Body) - if err != nil { - panic(err) - } - if resp.ContentLength > 0 && copiedLen != resp.ContentLength { - panic(fmt.Errorf("copied %d bytes, request body had %d", copiedLen, resp.ContentLength)) - } - return tmpfile.Name() -} - -func kebabToCamel(kebab string) string { - camel := "" - nextUpper := true - for _, c := range kebab { - if nextUpper { - camel += strings.ToUpper(string(c)) - nextUpper = false - } else if c == '-' { - nextUpper = true - } else { - camel += string(c) - } - } - return camel -} - -func readFileFromZip(f *zip.File) string { - reader, err := f.Open() - if err != nil { - panic(err) - } - defer reader.Close() - bytes, err := ioutil.ReadAll(reader) - if err != nil { - panic(err) - } - return string(bytes) -} - -func templateGoStr(input string) string { - if len(input) > 0 && input[len(input)-1] == '\n' { - input = input[0 : len(input)-1] - } - if strings.Contains(input, "`") { - lines := strings.Split(input, "\n") - for idx, line := range lines { - lines[idx] = strconv.Quote(line + "\n") - } - return strings.Join(lines, " + \n") - } - return "`" + input + "`" -} - -var ( - ref = flag.String("r", "master", "git reference") -) - -func usage() { - _, _ = fmt.Fprintf(os.Stderr, "usage: tomltestgen [flags]\n") - flag.PrintDefaults() -} - -func main() { - flag.Usage = usage - flag.Parse() - - url := "https://codeload.github.com/BurntSushi/toml-test/zip/" + *ref - resultFile := downloadTmpFile(url) - defer os.Remove(resultFile) - log.Println("file written to", resultFile) - - zipReader, err := zip.OpenReader(resultFile) - if err != nil { - panic(err) - } - defer zipReader.Close() - - collection := testsCollection{ - Ref: *ref, - Timestamp: time.Now().Format(time.RFC3339), - } - - zipFilesMap := map[string]*zip.File{} - - for _, f := range zipReader.File { - zipFilesMap[f.Name] = f - } - - testFileRegexp := regexp.MustCompile(`([^/]+/tests/(valid|invalid)/(.+))\.(toml)`) - for _, f := range zipReader.File { - groups := testFileRegexp.FindStringSubmatch(f.Name) - if len(groups) > 0 { - name := kebabToCamel(groups[3]) - testType := groups[2] - - log.Printf("> [%s] %s\n", testType, name) - - tomlContent := readFileFromZip(f) - - switch testType { - case "invalid": - collection.Invalid = append(collection.Invalid, invalid{ - Name: name, - Input: tomlContent, - }) - collection.Count++ - case "valid": - baseFilePath := groups[1] - jsonFilePath := baseFilePath + ".json" - jsonContent := readFileFromZip(zipFilesMap[jsonFilePath]) - - collection.Valid = append(collection.Valid, valid{ - Name: name, - Input: tomlContent, - JsonRef: jsonContent, - }) - collection.Count++ - default: - panic(fmt.Sprintf("unknown test type: %s", testType)) - } - } - } - - log.Printf("Collected %d tests from toml-test\n", collection.Count) - - funcMap := template.FuncMap{ - "gostr": templateGoStr, - } - t := template.Must(template.New("src").Funcs(funcMap).Parse(srcTemplate)) - buf := new(bytes.Buffer) - err = t.Execute(buf, collection) - if err != nil { - panic(err) - } - outputBytes, err := format.Source(buf.Bytes()) - if err != nil { - panic(err) - } - fmt.Println(string(outputBytes)) -} diff --git a/doc.go b/doc.go deleted file mode 100644 index a1406a32..00000000 --- a/doc.go +++ /dev/null @@ -1,23 +0,0 @@ -// Package toml is a TOML parser and manipulation library. -// -// This version supports the specification as described in -// https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.5.0.md -// -// Marshaling -// -// Go-toml can marshal and unmarshal TOML documents from and to data -// structures. -// -// TOML document as a tree -// -// Go-toml can operate on a TOML document as a tree. Use one of the Load* -// functions to parse TOML data and obtain a Tree instance, then one of its -// methods to manipulate the tree. -// -// JSONPath-like queries -// -// The package github.com/pelletier/go-toml/query implements a system -// similar to JSONPath to quickly retrieve elements of a TOML document using a -// single expression. See the package documentation for more information. -// -package toml diff --git a/doc_test.go b/doc_test.go deleted file mode 100644 index 7aaddabe..00000000 --- a/doc_test.go +++ /dev/null @@ -1,170 +0,0 @@ -// code examples for godoc - -package toml_test - -import ( - "fmt" - "log" - "os" - - toml "github.com/pelletier/go-toml" -) - -func Example_tree() { - config, err := toml.LoadFile("config.toml") - - if err != nil { - fmt.Println("Error ", err.Error()) - } else { - // retrieve data directly - directUser := config.Get("postgres.user").(string) - directPassword := config.Get("postgres.password").(string) - fmt.Println("User is", directUser, " and password is", directPassword) - - // or using an intermediate object - configTree := config.Get("postgres").(*toml.Tree) - user := configTree.Get("user").(string) - password := configTree.Get("password").(string) - fmt.Println("User is", user, " and password is", password) - - // show where elements are in the file - fmt.Printf("User position: %v\n", configTree.GetPosition("user")) - fmt.Printf("Password position: %v\n", configTree.GetPosition("password")) - } -} - -func Example_unmarshal() { - type Employer struct { - Name string - Phone string - } - type Person struct { - Name string - Age int64 - Employer Employer - } - - document := []byte(` - name = "John" - age = 30 - [employer] - name = "Company Inc." - phone = "+1 234 567 89012" - `) - - person := Person{} - toml.Unmarshal(document, &person) - fmt.Println(person.Name, "is", person.Age, "and works at", person.Employer.Name) - // Output: - // John is 30 and works at Company Inc. -} - -func ExampleMarshal() { - type Postgres struct { - User string `toml:"user"` - Password string `toml:"password"` - Database string `toml:"db" commented:"true" comment:"not used anymore"` - } - type Config struct { - Postgres Postgres `toml:"postgres" comment:"Postgres configuration"` - } - - config := Config{Postgres{User: "pelletier", Password: "mypassword", Database: "old_database"}} - b, err := toml.Marshal(config) - if err != nil { - log.Fatal(err) - } - fmt.Println(string(b)) - // Output: - // # Postgres configuration - // [postgres] - // - // # not used anymore - // # db = "old_database" - // password = "mypassword" - // user = "pelletier" -} - -func ExampleUnmarshal() { - type Postgres struct { - User string - Password string - } - type Config struct { - Postgres Postgres - } - - doc := []byte(` - [postgres] - user = "pelletier" - password = "mypassword"`) - - config := Config{} - toml.Unmarshal(doc, &config) - fmt.Println("user=", config.Postgres.User) - // Output: - // user= pelletier -} - -func ExampleEncoder_anonymous() { - type Credentials struct { - User string `toml:"user"` - Password string `toml:"password"` - } - - type Protocol struct { - Name string `toml:"name"` - } - - type Config struct { - Version int `toml:"version"` - Credentials - Protocol `toml:"Protocol"` - } - config := Config{ - Version: 2, - Credentials: Credentials{ - User: "pelletier", - Password: "mypassword", - }, - Protocol: Protocol{ - Name: "tcp", - }, - } - fmt.Println("Default:") - fmt.Println("---------------") - - def := toml.NewEncoder(os.Stdout) - if err := def.Encode(config); err != nil { - log.Fatal(err) - } - - fmt.Println("---------------") - fmt.Println("With promotion:") - fmt.Println("---------------") - - prom := toml.NewEncoder(os.Stdout).PromoteAnonymous(true) - if err := prom.Encode(config); err != nil { - log.Fatal(err) - } - // Output: - // Default: - // --------------- - // password = "mypassword" - // user = "pelletier" - // version = 2 - // - // [Protocol] - // name = "tcp" - // --------------- - // With promotion: - // --------------- - // version = 2 - // - // [Credentials] - // password = "mypassword" - // user = "pelletier" - // - // [Protocol] - // name = "tcp" -} diff --git a/example-crlf.toml b/example-crlf.toml deleted file mode 100644 index 780d9c68..00000000 --- a/example-crlf.toml +++ /dev/null @@ -1,30 +0,0 @@ -# This is a TOML document. Boom. - -title = "TOML Example" - -[owner] -name = "Tom Preston-Werner" -organization = "GitHub" -bio = "GitHub Cofounder & CEO\nLikes tater tots and beer." -dob = 1979-05-27T07:32:00Z # First class dates? Why not? - -[database] -server = "192.168.1.1" -ports = [ 8001, 8001, 8002 ] -connection_max = 5000 -enabled = true - -[servers] - - # You can indent as you please. Tabs or spaces. TOML don't care. - [servers.alpha] - ip = "10.0.0.1" - dc = "eqdc10" - - [servers.beta] - ip = "10.0.0.2" - dc = "eqdc10" - -[clients] -data = [ ["gamma", "delta"], [1, 2] ] # just an update to make sure parsers support it -score = 4e-08 # to make sure leading zeroes in exponent parts of floats are supported \ No newline at end of file diff --git a/example.toml b/example.toml deleted file mode 100644 index f45bf88b..00000000 --- a/example.toml +++ /dev/null @@ -1,30 +0,0 @@ -# This is a TOML document. Boom. - -title = "TOML Example" - -[owner] -name = "Tom Preston-Werner" -organization = "GitHub" -bio = "GitHub Cofounder & CEO\nLikes tater tots and beer." -dob = 1979-05-27T07:32:00Z # First class dates? Why not? - -[database] -server = "192.168.1.1" -ports = [ 8001, 8001, 8002 ] -connection_max = 5000 -enabled = true - -[servers] - - # You can indent as you please. Tabs or spaces. TOML don't care. - [servers.alpha] - ip = "10.0.0.1" - dc = "eqdc10" - - [servers.beta] - ip = "10.0.0.2" - dc = "eqdc10" - -[clients] -data = [ ["gamma", "delta"], [1, 2] ] # just an update to make sure parsers support it -score = 4e-08 # to make sure leading zeroes in exponent parts of floats are supported \ No newline at end of file diff --git a/fuzz.go b/fuzz.go deleted file mode 100644 index 14570c8d..00000000 --- a/fuzz.go +++ /dev/null @@ -1,31 +0,0 @@ -// +build gofuzz - -package toml - -func Fuzz(data []byte) int { - tree, err := LoadBytes(data) - if err != nil { - if tree != nil { - panic("tree must be nil if there is an error") - } - return 0 - } - - str, err := tree.ToTomlString() - if err != nil { - if str != "" { - panic(`str must be "" if there is an error`) - } - panic(err) - } - - tree, err = Load(str) - if err != nil { - if tree != nil { - panic("tree must be nil if there is an error") - } - return 0 - } - - return 1 -} diff --git a/fuzz.sh b/fuzz.sh deleted file mode 100755 index 3204b4c4..00000000 --- a/fuzz.sh +++ /dev/null @@ -1,15 +0,0 @@ -#! /bin/sh -set -eu - -go get github.com/dvyukov/go-fuzz/go-fuzz -go get github.com/dvyukov/go-fuzz/go-fuzz-build - -if [ ! -e toml-fuzz.zip ]; then - go-fuzz-build github.com/pelletier/go-toml -fi - -rm -fr fuzz -mkdir -p fuzz/corpus -cp *.toml fuzz/corpus - -go-fuzz -bin=toml-fuzz.zip -workdir=fuzz diff --git a/go.mod b/go.mod index e924cb90..3825359e 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,5 @@ -module github.com/pelletier/go-toml +module github.com/pelletier/go-toml/v2 -go 1.12 +go 1.14 -require github.com/davecgh/go-spew v1.1.1 +require github.com/stretchr/testify v1.7.0 diff --git a/go.sum b/go.sum index b5e2922e..d021fdd5 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,12 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/keysparsing.go b/keysparsing.go deleted file mode 100644 index e091500b..00000000 --- a/keysparsing.go +++ /dev/null @@ -1,112 +0,0 @@ -// Parsing keys handling both bare and quoted keys. - -package toml - -import ( - "errors" - "fmt" -) - -// Convert the bare key group string to an array. -// The input supports double quotation and single quotation, -// but escape sequences are not supported. Lexers must unescape them beforehand. -func parseKey(key string) ([]string, error) { - runes := []rune(key) - var groups []string - - if len(key) == 0 { - return nil, errors.New("empty key") - } - - idx := 0 - for idx < len(runes) { - for ; idx < len(runes) && isSpace(runes[idx]); idx++ { - // skip leading whitespace - } - if idx >= len(runes) { - break - } - r := runes[idx] - if isValidBareChar(r) { - // parse bare key - startIdx := idx - endIdx := -1 - idx++ - for idx < len(runes) { - r = runes[idx] - if isValidBareChar(r) { - idx++ - } else if r == '.' { - endIdx = idx - break - } else if isSpace(r) { - endIdx = idx - for ; idx < len(runes) && isSpace(runes[idx]); idx++ { - // skip trailing whitespace - } - if idx < len(runes) && runes[idx] != '.' { - return nil, fmt.Errorf("invalid key character after whitespace: %c", runes[idx]) - } - break - } else { - return nil, fmt.Errorf("invalid bare key character: %c", r) - } - } - if endIdx == -1 { - endIdx = idx - } - groups = append(groups, string(runes[startIdx:endIdx])) - } else if r == '\'' { - // parse single quoted key - idx++ - startIdx := idx - for { - if idx >= len(runes) { - return nil, fmt.Errorf("unclosed single-quoted key") - } - r = runes[idx] - if r == '\'' { - groups = append(groups, string(runes[startIdx:idx])) - idx++ - break - } - idx++ - } - } else if r == '"' { - // parse double quoted key - idx++ - startIdx := idx - for { - if idx >= len(runes) { - return nil, fmt.Errorf("unclosed double-quoted key") - } - r = runes[idx] - if r == '"' { - groups = append(groups, string(runes[startIdx:idx])) - idx++ - break - } - idx++ - } - } else if r == '.' { - idx++ - if idx >= len(runes) { - return nil, fmt.Errorf("unexpected end of key") - } - r = runes[idx] - if !isValidBareChar(r) && r != '\'' && r != '"' && r != ' ' { - return nil, fmt.Errorf("expecting key part after dot") - } - } else { - return nil, fmt.Errorf("invalid key character: %c", r) - } - } - if len(groups) == 0 { - return nil, fmt.Errorf("empty key") - } - return groups, nil -} - -func isValidBareChar(r rune) bool { - return isAlphanumeric(r) || r == '-' || isDigit(r) -} diff --git a/keysparsing_test.go b/keysparsing_test.go deleted file mode 100644 index 3858259f..00000000 --- a/keysparsing_test.go +++ /dev/null @@ -1,79 +0,0 @@ -package toml - -import ( - "fmt" - "testing" -) - -func testResult(t *testing.T, key string, expected []string) { - parsed, err := parseKey(key) - t.Logf("key=%s expected=%s parsed=%s", key, expected, parsed) - if err != nil { - t.Fatal("Unexpected error:", err) - } - if len(expected) != len(parsed) { - t.Fatal("Expected length", len(expected), "but", len(parsed), "parsed") - } - for index, expectedKey := range expected { - if expectedKey != parsed[index] { - t.Fatal("Expected", expectedKey, "at index", index, "but found", parsed[index]) - } - } -} - -func testError(t *testing.T, key string, expectedError string) { - res, err := parseKey(key) - if err == nil { - t.Fatalf("Expected error, but successfully parsed key %s", res) - } - if fmt.Sprintf("%s", err) != expectedError { - t.Fatalf("Expected error \"%s\", but got \"%s\".", expectedError, err) - } -} - -func TestBareKeyBasic(t *testing.T) { - testResult(t, "test", []string{"test"}) -} - -func TestBareKeyDotted(t *testing.T) { - testResult(t, "this.is.a.key", []string{"this", "is", "a", "key"}) -} - -func TestDottedKeyBasic(t *testing.T) { - testResult(t, "\"a.dotted.key\"", []string{"a.dotted.key"}) -} - -func TestBaseKeyPound(t *testing.T) { - testError(t, "hello#world", "invalid bare key character: #") -} - -func TestUnclosedSingleQuotedKey(t *testing.T) { - testError(t, "'", "unclosed single-quoted key") -} - -func TestUnclosedDoubleQuotedKey(t *testing.T) { - testError(t, "\"", "unclosed double-quoted key") -} - -func TestInvalidStartKeyCharacter(t *testing.T) { - testError(t, "/", "invalid key character: /") -} - -func TestInvalidSpaceInKey(t *testing.T) { - testError(t, "invalid key", "invalid key character after whitespace: k") -} - -func TestQuotedKeys(t *testing.T) { - testResult(t, `hello."foo".bar`, []string{"hello", "foo", "bar"}) - testResult(t, `"hello!"`, []string{"hello!"}) - testResult(t, `foo."ba.r".baz`, []string{"foo", "ba.r", "baz"}) - - // escape sequences must not be converted - testResult(t, `"hello\tworld"`, []string{`hello\tworld`}) -} - -func TestEmptyKey(t *testing.T) { - testError(t, ``, "empty key") - testError(t, ` `, "empty key") - testResult(t, `""`, []string{""}) -} diff --git a/lexer.go b/lexer.go deleted file mode 100644 index 313908e3..00000000 --- a/lexer.go +++ /dev/null @@ -1,1031 +0,0 @@ -// TOML lexer. -// -// Written using the principles developed by Rob Pike in -// http://www.youtube.com/watch?v=HxaD_trXwRE - -package toml - -import ( - "bytes" - "errors" - "fmt" - "strconv" - "strings" -) - -// Define state functions -type tomlLexStateFn func() tomlLexStateFn - -// Define lexer -type tomlLexer struct { - inputIdx int - input []rune // Textual source - currentTokenStart int - currentTokenStop int - tokens []token - brackets []rune - line int - col int - endbufferLine int - endbufferCol int -} - -// Basic read operations on input - -func (l *tomlLexer) read() rune { - r := l.peek() - if r == '\n' { - l.endbufferLine++ - l.endbufferCol = 1 - } else { - l.endbufferCol++ - } - l.inputIdx++ - return r -} - -func (l *tomlLexer) next() rune { - r := l.read() - - if r != eof { - l.currentTokenStop++ - } - return r -} - -func (l *tomlLexer) ignore() { - l.currentTokenStart = l.currentTokenStop - l.line = l.endbufferLine - l.col = l.endbufferCol -} - -func (l *tomlLexer) skip() { - l.next() - l.ignore() -} - -func (l *tomlLexer) fastForward(n int) { - for i := 0; i < n; i++ { - l.next() - } -} - -func (l *tomlLexer) emitWithValue(t tokenType, value string) { - l.tokens = append(l.tokens, token{ - Position: Position{l.line, l.col}, - typ: t, - val: value, - }) - l.ignore() -} - -func (l *tomlLexer) emit(t tokenType) { - l.emitWithValue(t, string(l.input[l.currentTokenStart:l.currentTokenStop])) -} - -func (l *tomlLexer) peek() rune { - if l.inputIdx >= len(l.input) { - return eof - } - return l.input[l.inputIdx] -} - -func (l *tomlLexer) peekString(size int) string { - maxIdx := len(l.input) - upperIdx := l.inputIdx + size // FIXME: potential overflow - if upperIdx > maxIdx { - upperIdx = maxIdx - } - return string(l.input[l.inputIdx:upperIdx]) -} - -func (l *tomlLexer) follow(next string) bool { - return next == l.peekString(len(next)) -} - -// Error management - -func (l *tomlLexer) errorf(format string, args ...interface{}) tomlLexStateFn { - l.tokens = append(l.tokens, token{ - Position: Position{l.line, l.col}, - typ: tokenError, - val: fmt.Sprintf(format, args...), - }) - return nil -} - -// State functions - -func (l *tomlLexer) lexVoid() tomlLexStateFn { - for { - next := l.peek() - switch next { - case '}': // after '{' - return l.lexRightCurlyBrace - case '[': - return l.lexTableKey - case '#': - return l.lexComment(l.lexVoid) - case '=': - return l.lexEqual - case '\r': - fallthrough - case '\n': - l.skip() - continue - } - - if isSpace(next) { - l.skip() - } - - if isKeyStartChar(next) { - return l.lexKey - } - - if next == eof { - l.next() - break - } - } - - l.emit(tokenEOF) - return nil -} - -func (l *tomlLexer) lexRvalue() tomlLexStateFn { - for { - next := l.peek() - switch next { - case '.': - return l.errorf("cannot start float with a dot") - case '=': - return l.lexEqual - case '[': - return l.lexLeftBracket - case ']': - return l.lexRightBracket - case '{': - return l.lexLeftCurlyBrace - case '}': - return l.lexRightCurlyBrace - case '#': - return l.lexComment(l.lexRvalue) - case '"': - return l.lexString - case '\'': - return l.lexLiteralString - case ',': - return l.lexComma - case '\r': - fallthrough - case '\n': - l.skip() - if len(l.brackets) > 0 && l.brackets[len(l.brackets)-1] == '[' { - return l.lexRvalue - } - return l.lexVoid - } - - if l.follow("true") { - return l.lexTrue - } - - if l.follow("false") { - return l.lexFalse - } - - if l.follow("inf") { - return l.lexInf - } - - if l.follow("nan") { - return l.lexNan - } - - if isSpace(next) { - l.skip() - continue - } - - if next == eof { - l.next() - break - } - - if next == '+' || next == '-' { - return l.lexNumber - } - - if isDigit(next) { - return l.lexDateTimeOrNumber - } - - return l.errorf("no value can start with %c", next) - } - - l.emit(tokenEOF) - return nil -} - -func (l *tomlLexer) lexDateTimeOrNumber() tomlLexStateFn { - // Could be either a date/time, or a digit. - // The options for date/times are: - // YYYY-... => date or date-time - // HH:... => time - // Anything else should be a number. - - lookAhead := l.peekString(5) - if len(lookAhead) < 3 { - return l.lexNumber() - } - - for idx, r := range lookAhead { - if !isDigit(r) { - if idx == 2 && r == ':' { - return l.lexDateTimeOrTime() - } - if idx == 4 && r == '-' { - return l.lexDateTimeOrTime() - } - return l.lexNumber() - } - } - return l.lexNumber() -} - -func (l *tomlLexer) lexLeftCurlyBrace() tomlLexStateFn { - l.next() - l.emit(tokenLeftCurlyBrace) - l.brackets = append(l.brackets, '{') - return l.lexVoid -} - -func (l *tomlLexer) lexRightCurlyBrace() tomlLexStateFn { - l.next() - l.emit(tokenRightCurlyBrace) - if len(l.brackets) == 0 || l.brackets[len(l.brackets)-1] != '{' { - return l.errorf("cannot have '}' here") - } - l.brackets = l.brackets[:len(l.brackets)-1] - return l.lexRvalue -} - -func (l *tomlLexer) lexDateTimeOrTime() tomlLexStateFn { - // Example matches: - // 1979-05-27T07:32:00Z - // 1979-05-27T00:32:00-07:00 - // 1979-05-27T00:32:00.999999-07:00 - // 1979-05-27 07:32:00Z - // 1979-05-27 00:32:00-07:00 - // 1979-05-27 00:32:00.999999-07:00 - // 1979-05-27T07:32:00 - // 1979-05-27T00:32:00.999999 - // 1979-05-27 07:32:00 - // 1979-05-27 00:32:00.999999 - // 1979-05-27 - // 07:32:00 - // 00:32:00.999999 - - // we already know those two are digits - l.next() - l.next() - - // Got 2 digits. At that point it could be either a time or a date(-time). - - r := l.next() - if r == ':' { - return l.lexTime() - } - - return l.lexDateTime() -} - -func (l *tomlLexer) lexDateTime() tomlLexStateFn { - // This state accepts an offset date-time, a local date-time, or a local date. - // - // v--- cursor - // 1979-05-27T07:32:00Z - // 1979-05-27T00:32:00-07:00 - // 1979-05-27T00:32:00.999999-07:00 - // 1979-05-27 07:32:00Z - // 1979-05-27 00:32:00-07:00 - // 1979-05-27 00:32:00.999999-07:00 - // 1979-05-27T07:32:00 - // 1979-05-27T00:32:00.999999 - // 1979-05-27 07:32:00 - // 1979-05-27 00:32:00.999999 - // 1979-05-27 - - // date - - // already checked by lexRvalue - l.next() // digit - l.next() // - - - for i := 0; i < 2; i++ { - r := l.next() - if !isDigit(r) { - return l.errorf("invalid month digit in date: %c", r) - } - } - - r := l.next() - if r != '-' { - return l.errorf("expected - to separate month of a date, not %c", r) - } - - for i := 0; i < 2; i++ { - r := l.next() - if !isDigit(r) { - return l.errorf("invalid day digit in date: %c", r) - } - } - - l.emit(tokenLocalDate) - - r = l.peek() - - if r == eof { - - return l.lexRvalue - } - - if r != ' ' && r != 'T' { - return l.errorf("incorrect date/time separation character: %c", r) - } - - if r == ' ' { - lookAhead := l.peekString(3)[1:] - if len(lookAhead) < 2 { - return l.lexRvalue - } - for _, r := range lookAhead { - if !isDigit(r) { - return l.lexRvalue - } - } - } - - l.skip() // skip the T or ' ' - - // time - - for i := 0; i < 2; i++ { - r := l.next() - if !isDigit(r) { - return l.errorf("invalid hour digit in time: %c", r) - } - } - - r = l.next() - if r != ':' { - return l.errorf("time hour/minute separator should be :, not %c", r) - } - - for i := 0; i < 2; i++ { - r := l.next() - if !isDigit(r) { - return l.errorf("invalid minute digit in time: %c", r) - } - } - - r = l.next() - if r != ':' { - return l.errorf("time minute/second separator should be :, not %c", r) - } - - for i := 0; i < 2; i++ { - r := l.next() - if !isDigit(r) { - return l.errorf("invalid second digit in time: %c", r) - } - } - - r = l.peek() - if r == '.' { - l.next() - r := l.next() - if !isDigit(r) { - return l.errorf("expected at least one digit in time's fraction, not %c", r) - } - - for { - r := l.peek() - if !isDigit(r) { - break - } - l.next() - } - } - - l.emit(tokenLocalTime) - - return l.lexTimeOffset - -} - -func (l *tomlLexer) lexTimeOffset() tomlLexStateFn { - // potential offset - - // Z - // -07:00 - // +07:00 - // nothing - - r := l.peek() - - if r == 'Z' { - l.next() - l.emit(tokenTimeOffset) - } else if r == '+' || r == '-' { - l.next() - - for i := 0; i < 2; i++ { - r := l.next() - if !isDigit(r) { - return l.errorf("invalid hour digit in time offset: %c", r) - } - } - - r = l.next() - if r != ':' { - return l.errorf("time offset hour/minute separator should be :, not %c", r) - } - - for i := 0; i < 2; i++ { - r := l.next() - if !isDigit(r) { - return l.errorf("invalid minute digit in time offset: %c", r) - } - } - - l.emit(tokenTimeOffset) - } - - return l.lexRvalue -} - -func (l *tomlLexer) lexTime() tomlLexStateFn { - // v--- cursor - // 07:32:00 - // 00:32:00.999999 - - for i := 0; i < 2; i++ { - r := l.next() - if !isDigit(r) { - return l.errorf("invalid minute digit in time: %c", r) - } - } - - r := l.next() - if r != ':' { - return l.errorf("time minute/second separator should be :, not %c", r) - } - - for i := 0; i < 2; i++ { - r := l.next() - if !isDigit(r) { - return l.errorf("invalid second digit in time: %c", r) - } - } - - r = l.peek() - if r == '.' { - l.next() - r := l.next() - if !isDigit(r) { - return l.errorf("expected at least one digit in time's fraction, not %c", r) - } - - for { - r := l.peek() - if !isDigit(r) { - break - } - l.next() - } - } - - l.emit(tokenLocalTime) - return l.lexRvalue - -} - -func (l *tomlLexer) lexTrue() tomlLexStateFn { - l.fastForward(4) - l.emit(tokenTrue) - return l.lexRvalue -} - -func (l *tomlLexer) lexFalse() tomlLexStateFn { - l.fastForward(5) - l.emit(tokenFalse) - return l.lexRvalue -} - -func (l *tomlLexer) lexInf() tomlLexStateFn { - l.fastForward(3) - l.emit(tokenInf) - return l.lexRvalue -} - -func (l *tomlLexer) lexNan() tomlLexStateFn { - l.fastForward(3) - l.emit(tokenNan) - return l.lexRvalue -} - -func (l *tomlLexer) lexEqual() tomlLexStateFn { - l.next() - l.emit(tokenEqual) - return l.lexRvalue -} - -func (l *tomlLexer) lexComma() tomlLexStateFn { - l.next() - l.emit(tokenComma) - if len(l.brackets) > 0 && l.brackets[len(l.brackets)-1] == '{' { - return l.lexVoid - } - return l.lexRvalue -} - -// Parse the key and emits its value without escape sequences. -// bare keys, basic string keys and literal string keys are supported. -func (l *tomlLexer) lexKey() tomlLexStateFn { - var sb strings.Builder - - for r := l.peek(); isKeyChar(r) || r == '\n' || r == '\r'; r = l.peek() { - if r == '"' { - l.next() - str, err := l.lexStringAsString(`"`, false, true) - if err != nil { - return l.errorf(err.Error()) - } - sb.WriteString("\"") - sb.WriteString(str) - sb.WriteString("\"") - l.next() - continue - } else if r == '\'' { - l.next() - str, err := l.lexLiteralStringAsString(`'`, false) - if err != nil { - return l.errorf(err.Error()) - } - sb.WriteString("'") - sb.WriteString(str) - sb.WriteString("'") - l.next() - continue - } else if r == '\n' { - return l.errorf("keys cannot contain new lines") - } else if isSpace(r) { - var str strings.Builder - str.WriteString(" ") - - // skip trailing whitespace - l.next() - for r = l.peek(); isSpace(r); r = l.peek() { - str.WriteRune(r) - l.next() - } - // break loop if not a dot - if r != '.' { - break - } - str.WriteString(".") - // skip trailing whitespace after dot - l.next() - for r = l.peek(); isSpace(r); r = l.peek() { - str.WriteRune(r) - l.next() - } - sb.WriteString(str.String()) - continue - } else if r == '.' { - // skip - } else if !isValidBareChar(r) { - return l.errorf("keys cannot contain %c character", r) - } - sb.WriteRune(r) - l.next() - } - l.emitWithValue(tokenKey, sb.String()) - return l.lexVoid -} - -func (l *tomlLexer) lexComment(previousState tomlLexStateFn) tomlLexStateFn { - return func() tomlLexStateFn { - for next := l.peek(); next != '\n' && next != eof; next = l.peek() { - if next == '\r' && l.follow("\r\n") { - break - } - l.next() - } - l.ignore() - return previousState - } -} - -func (l *tomlLexer) lexLeftBracket() tomlLexStateFn { - l.next() - l.emit(tokenLeftBracket) - l.brackets = append(l.brackets, '[') - return l.lexRvalue -} - -func (l *tomlLexer) lexLiteralStringAsString(terminator string, discardLeadingNewLine bool) (string, error) { - var sb strings.Builder - - if discardLeadingNewLine { - if l.follow("\r\n") { - l.skip() - l.skip() - } else if l.peek() == '\n' { - l.skip() - } - } - - // find end of string - for { - if l.follow(terminator) { - return sb.String(), nil - } - - next := l.peek() - if next == eof { - break - } - sb.WriteRune(l.next()) - } - - return "", errors.New("unclosed string") -} - -func (l *tomlLexer) lexLiteralString() tomlLexStateFn { - l.skip() - - // handle special case for triple-quote - terminator := "'" - discardLeadingNewLine := false - if l.follow("''") { - l.skip() - l.skip() - terminator = "'''" - discardLeadingNewLine = true - } - - str, err := l.lexLiteralStringAsString(terminator, discardLeadingNewLine) - if err != nil { - return l.errorf(err.Error()) - } - - l.emitWithValue(tokenString, str) - l.fastForward(len(terminator)) - l.ignore() - return l.lexRvalue -} - -// Lex a string and return the results as a string. -// Terminator is the substring indicating the end of the token. -// The resulting string does not include the terminator. -func (l *tomlLexer) lexStringAsString(terminator string, discardLeadingNewLine, acceptNewLines bool) (string, error) { - var sb strings.Builder - - if discardLeadingNewLine { - if l.follow("\r\n") { - l.skip() - l.skip() - } else if l.peek() == '\n' { - l.skip() - } - } - - for { - if l.follow(terminator) { - return sb.String(), nil - } - - if l.follow("\\") { - l.next() - switch l.peek() { - case '\r': - fallthrough - case '\n': - fallthrough - case '\t': - fallthrough - case ' ': - // skip all whitespace chars following backslash - for strings.ContainsRune("\r\n\t ", l.peek()) { - l.next() - } - case '"': - sb.WriteString("\"") - l.next() - case 'n': - sb.WriteString("\n") - l.next() - case 'b': - sb.WriteString("\b") - l.next() - case 'f': - sb.WriteString("\f") - l.next() - case '/': - sb.WriteString("/") - l.next() - case 't': - sb.WriteString("\t") - l.next() - case 'r': - sb.WriteString("\r") - l.next() - case '\\': - sb.WriteString("\\") - l.next() - case 'u': - l.next() - var code strings.Builder - for i := 0; i < 4; i++ { - c := l.peek() - if !isHexDigit(c) { - return "", errors.New("unfinished unicode escape") - } - l.next() - code.WriteRune(c) - } - intcode, err := strconv.ParseInt(code.String(), 16, 32) - if err != nil { - return "", errors.New("invalid unicode escape: \\u" + code.String()) - } - sb.WriteRune(rune(intcode)) - case 'U': - l.next() - var code strings.Builder - for i := 0; i < 8; i++ { - c := l.peek() - if !isHexDigit(c) { - return "", errors.New("unfinished unicode escape") - } - l.next() - code.WriteRune(c) - } - intcode, err := strconv.ParseInt(code.String(), 16, 64) - if err != nil { - return "", errors.New("invalid unicode escape: \\U" + code.String()) - } - sb.WriteRune(rune(intcode)) - default: - return "", errors.New("invalid escape sequence: \\" + string(l.peek())) - } - } else { - r := l.peek() - - if 0x00 <= r && r <= 0x1F && r != '\t' && !(acceptNewLines && (r == '\n' || r == '\r')) { - return "", fmt.Errorf("unescaped control character %U", r) - } - l.next() - sb.WriteRune(r) - } - - if l.peek() == eof { - break - } - } - - return "", errors.New("unclosed string") -} - -func (l *tomlLexer) lexString() tomlLexStateFn { - l.skip() - - // handle special case for triple-quote - terminator := `"` - discardLeadingNewLine := false - acceptNewLines := false - if l.follow(`""`) { - l.skip() - l.skip() - terminator = `"""` - discardLeadingNewLine = true - acceptNewLines = true - } - - str, err := l.lexStringAsString(terminator, discardLeadingNewLine, acceptNewLines) - if err != nil { - return l.errorf(err.Error()) - } - - l.emitWithValue(tokenString, str) - l.fastForward(len(terminator)) - l.ignore() - return l.lexRvalue -} - -func (l *tomlLexer) lexTableKey() tomlLexStateFn { - l.next() - - if l.peek() == '[' { - // token '[[' signifies an array of tables - l.next() - l.emit(tokenDoubleLeftBracket) - return l.lexInsideTableArrayKey - } - // vanilla table key - l.emit(tokenLeftBracket) - return l.lexInsideTableKey -} - -// Parse the key till "]]", but only bare keys are supported -func (l *tomlLexer) lexInsideTableArrayKey() tomlLexStateFn { - for r := l.peek(); r != eof; r = l.peek() { - switch r { - case ']': - if l.currentTokenStop > l.currentTokenStart { - l.emit(tokenKeyGroupArray) - } - l.next() - if l.peek() != ']' { - break - } - l.next() - l.emit(tokenDoubleRightBracket) - return l.lexVoid - case '[': - return l.errorf("table array key cannot contain ']'") - default: - l.next() - } - } - return l.errorf("unclosed table array key") -} - -// Parse the key till "]" but only bare keys are supported -func (l *tomlLexer) lexInsideTableKey() tomlLexStateFn { - for r := l.peek(); r != eof; r = l.peek() { - switch r { - case ']': - if l.currentTokenStop > l.currentTokenStart { - l.emit(tokenKeyGroup) - } - l.next() - l.emit(tokenRightBracket) - return l.lexVoid - case '[': - return l.errorf("table key cannot contain ']'") - default: - l.next() - } - } - return l.errorf("unclosed table key") -} - -func (l *tomlLexer) lexRightBracket() tomlLexStateFn { - l.next() - l.emit(tokenRightBracket) - if len(l.brackets) == 0 || l.brackets[len(l.brackets)-1] != '[' { - return l.errorf("cannot have ']' here") - } - l.brackets = l.brackets[:len(l.brackets)-1] - return l.lexRvalue -} - -type validRuneFn func(r rune) bool - -func isValidHexRune(r rune) bool { - return r >= 'a' && r <= 'f' || - r >= 'A' && r <= 'F' || - r >= '0' && r <= '9' || - r == '_' -} - -func isValidOctalRune(r rune) bool { - return r >= '0' && r <= '7' || r == '_' -} - -func isValidBinaryRune(r rune) bool { - return r == '0' || r == '1' || r == '_' -} - -func (l *tomlLexer) lexNumber() tomlLexStateFn { - r := l.peek() - - if r == '0' { - follow := l.peekString(2) - if len(follow) == 2 { - var isValidRune validRuneFn - switch follow[1] { - case 'x': - isValidRune = isValidHexRune - case 'o': - isValidRune = isValidOctalRune - case 'b': - isValidRune = isValidBinaryRune - default: - if follow[1] >= 'a' && follow[1] <= 'z' || follow[1] >= 'A' && follow[1] <= 'Z' { - return l.errorf("unknown number base: %s. possible options are x (hex) o (octal) b (binary)", string(follow[1])) - } - } - - if isValidRune != nil { - l.next() - l.next() - digitSeen := false - for { - next := l.peek() - if !isValidRune(next) { - break - } - digitSeen = true - l.next() - } - - if !digitSeen { - return l.errorf("number needs at least one digit") - } - - l.emit(tokenInteger) - - return l.lexRvalue - } - } - } - - if r == '+' || r == '-' { - l.next() - if l.follow("inf") { - return l.lexInf - } - if l.follow("nan") { - return l.lexNan - } - } - - pointSeen := false - expSeen := false - digitSeen := false - for { - next := l.peek() - if next == '.' { - if pointSeen { - return l.errorf("cannot have two dots in one float") - } - l.next() - if !isDigit(l.peek()) { - return l.errorf("float cannot end with a dot") - } - pointSeen = true - } else if next == 'e' || next == 'E' { - expSeen = true - l.next() - r := l.peek() - if r == '+' || r == '-' { - l.next() - } - } else if isDigit(next) { - digitSeen = true - l.next() - } else if next == '_' { - l.next() - } else { - break - } - if pointSeen && !digitSeen { - return l.errorf("cannot start float with a dot") - } - } - - if !digitSeen { - return l.errorf("no digit in that number") - } - if pointSeen || expSeen { - l.emit(tokenFloat) - } else { - l.emit(tokenInteger) - } - return l.lexRvalue -} - -func (l *tomlLexer) run() { - for state := l.lexVoid; state != nil; { - state = state() - } -} - -// Entry point -func lexToml(inputBytes []byte) []token { - runes := bytes.Runes(inputBytes) - l := &tomlLexer{ - input: runes, - tokens: make([]token, 0, 256), - line: 1, - col: 1, - endbufferLine: 1, - endbufferCol: 1, - } - l.run() - return l.tokens -} diff --git a/lexer_test.go b/lexer_test.go deleted file mode 100644 index 016e1225..00000000 --- a/lexer_test.go +++ /dev/null @@ -1,1247 +0,0 @@ -package toml - -import ( - "bytes" - "fmt" - "reflect" - "testing" - "text/tabwriter" -) - -func testFlow(t *testing.T, input string, expectedFlow []token) { - tokens := lexToml([]byte(input)) - - if !reflect.DeepEqual(tokens, expectedFlow) { - diffFlowsColumnsFatal(t, expectedFlow, tokens) - } -} - -func diffFlowsColumnsFatal(t *testing.T, expectedFlow []token, actualFlow []token) { - max := len(expectedFlow) - if len(actualFlow) > max { - max = len(actualFlow) - } - - b := &bytes.Buffer{} - w := tabwriter.NewWriter(b, 0, 0, 1, ' ', tabwriter.Debug) - - fmt.Fprintln(w, "expected\tT\tP\tactual\tT\tP\tdiff") - - for i := 0; i < max; i++ { - expected := "" - expectedType := "" - expectedPos := "" - if i < len(expectedFlow) { - expected = fmt.Sprintf("%s", expectedFlow[i]) - expectedType = fmt.Sprintf("%s", expectedFlow[i].typ) - expectedPos = expectedFlow[i].Position.String() - } - actual := "" - actualType := "" - actualPos := "" - if i < len(actualFlow) { - actual = fmt.Sprintf("%s", actualFlow[i]) - actualType = fmt.Sprintf("%s", actualFlow[i].typ) - actualPos = actualFlow[i].Position.String() - } - different := "" - if i >= len(expectedFlow) { - different = "+" - } else if i >= len(actualFlow) { - different = "-" - } else if !reflect.DeepEqual(expectedFlow[i], actualFlow[i]) { - different = "x" - } - fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n", expected, expectedType, expectedPos, actual, actualType, actualPos, different) - } - w.Flush() - t.Errorf("Different flows:\n%s", b.String()) -} - -func TestValidKeyGroup(t *testing.T) { - testFlow(t, "[hello world]", []token{ - {Position{1, 1}, tokenLeftBracket, "["}, - {Position{1, 2}, tokenKeyGroup, "hello world"}, - {Position{1, 13}, tokenRightBracket, "]"}, - {Position{1, 14}, tokenEOF, ""}, - }) -} - -func TestNestedQuotedUnicodeKeyGroup(t *testing.T) { - testFlow(t, `[ j . "ʞ" . l . 'ɯ' ]`, []token{ - {Position{1, 1}, tokenLeftBracket, "["}, - {Position{1, 2}, tokenKeyGroup, ` j . "ʞ" . l . 'ɯ' `}, - {Position{1, 21}, tokenRightBracket, "]"}, - {Position{1, 22}, tokenEOF, ""}, - }) -} - -func TestNestedQuotedUnicodeKeyAssign(t *testing.T) { - testFlow(t, ` j . "ʞ" . l . 'ɯ' = 3`, []token{ - {Position{1, 2}, tokenKey, `j . "ʞ" . l . 'ɯ'`}, - {Position{1, 20}, tokenEqual, "="}, - {Position{1, 22}, tokenInteger, "3"}, - {Position{1, 23}, tokenEOF, ""}, - }) -} - -func TestUnclosedKeyGroup(t *testing.T) { - testFlow(t, "[hello world", []token{ - {Position{1, 1}, tokenLeftBracket, "["}, - {Position{1, 2}, tokenError, "unclosed table key"}, - }) -} - -func TestComment(t *testing.T) { - testFlow(t, "# blahblah", []token{ - {Position{1, 11}, tokenEOF, ""}, - }) -} - -func TestKeyGroupComment(t *testing.T) { - testFlow(t, "[hello world] # blahblah", []token{ - {Position{1, 1}, tokenLeftBracket, "["}, - {Position{1, 2}, tokenKeyGroup, "hello world"}, - {Position{1, 13}, tokenRightBracket, "]"}, - {Position{1, 25}, tokenEOF, ""}, - }) -} - -func TestMultipleKeyGroupsComment(t *testing.T) { - testFlow(t, "[hello world] # blahblah\n[test]", []token{ - {Position{1, 1}, tokenLeftBracket, "["}, - {Position{1, 2}, tokenKeyGroup, "hello world"}, - {Position{1, 13}, tokenRightBracket, "]"}, - {Position{2, 1}, tokenLeftBracket, "["}, - {Position{2, 2}, tokenKeyGroup, "test"}, - {Position{2, 6}, tokenRightBracket, "]"}, - {Position{2, 7}, tokenEOF, ""}, - }) -} - -func TestSimpleWindowsCRLF(t *testing.T) { - testFlow(t, "a=4\r\nb=2", []token{ - {Position{1, 1}, tokenKey, "a"}, - {Position{1, 2}, tokenEqual, "="}, - {Position{1, 3}, tokenInteger, "4"}, - {Position{2, 1}, tokenKey, "b"}, - {Position{2, 2}, tokenEqual, "="}, - {Position{2, 3}, tokenInteger, "2"}, - {Position{2, 4}, tokenEOF, ""}, - }) -} - -func TestBasicKey(t *testing.T) { - testFlow(t, "hello", []token{ - {Position{1, 1}, tokenKey, "hello"}, - {Position{1, 6}, tokenEOF, ""}, - }) -} - -func TestBasicKeyWithUnderscore(t *testing.T) { - testFlow(t, "hello_hello", []token{ - {Position{1, 1}, tokenKey, "hello_hello"}, - {Position{1, 12}, tokenEOF, ""}, - }) -} - -func TestBasicKeyWithDash(t *testing.T) { - testFlow(t, "hello-world", []token{ - {Position{1, 1}, tokenKey, "hello-world"}, - {Position{1, 12}, tokenEOF, ""}, - }) -} - -func TestBasicKeyWithUppercaseMix(t *testing.T) { - testFlow(t, "helloHELLOHello", []token{ - {Position{1, 1}, tokenKey, "helloHELLOHello"}, - {Position{1, 16}, tokenEOF, ""}, - }) -} - -func TestBasicKeyWithInternationalCharacters(t *testing.T) { - testFlow(t, "'héllÖ'", []token{ - {Position{1, 1}, tokenKey, "'héllÖ'"}, - {Position{1, 8}, tokenEOF, ""}, - }) -} - -func TestBasicKeyAndEqual(t *testing.T) { - testFlow(t, "hello =", []token{ - {Position{1, 1}, tokenKey, "hello"}, - {Position{1, 7}, tokenEqual, "="}, - {Position{1, 8}, tokenEOF, ""}, - }) -} - -func TestKeyWithSharpAndEqual(t *testing.T) { - testFlow(t, "key#name = 5", []token{ - {Position{1, 1}, tokenError, "keys cannot contain # character"}, - }) -} - -func TestKeyWithSymbolsAndEqual(t *testing.T) { - testFlow(t, "~!@$^&*()_+-`1234567890[]\\|/?><.,;:' = 5", []token{ - {Position{1, 1}, tokenError, "keys cannot contain ~ character"}, - }) -} - -func TestKeyEqualStringEscape(t *testing.T) { - testFlow(t, `foo = "hello\""`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 8}, tokenString, "hello\""}, - {Position{1, 16}, tokenEOF, ""}, - }) -} - -func TestKeyEqualStringUnfinished(t *testing.T) { - testFlow(t, `foo = "bar`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 8}, tokenError, "unclosed string"}, - }) -} - -func TestKeyEqualString(t *testing.T) { - testFlow(t, `foo = "bar"`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 8}, tokenString, "bar"}, - {Position{1, 12}, tokenEOF, ""}, - }) -} - -func TestKeyEqualTrue(t *testing.T) { - testFlow(t, "foo = true", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenTrue, "true"}, - {Position{1, 11}, tokenEOF, ""}, - }) -} - -func TestKeyEqualFalse(t *testing.T) { - testFlow(t, "foo = false", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenFalse, "false"}, - {Position{1, 12}, tokenEOF, ""}, - }) -} - -func TestArrayNestedString(t *testing.T) { - testFlow(t, `a = [ ["hello", "world"] ]`, []token{ - {Position{1, 1}, tokenKey, "a"}, - {Position{1, 3}, tokenEqual, "="}, - {Position{1, 5}, tokenLeftBracket, "["}, - {Position{1, 7}, tokenLeftBracket, "["}, - {Position{1, 9}, tokenString, "hello"}, - {Position{1, 15}, tokenComma, ","}, - {Position{1, 18}, tokenString, "world"}, - {Position{1, 24}, tokenRightBracket, "]"}, - {Position{1, 26}, tokenRightBracket, "]"}, - {Position{1, 27}, tokenEOF, ""}, - }) -} - -func TestArrayNestedInts(t *testing.T) { - testFlow(t, "a = [ [42, 21], [10] ]", []token{ - {Position{1, 1}, tokenKey, "a"}, - {Position{1, 3}, tokenEqual, "="}, - {Position{1, 5}, tokenLeftBracket, "["}, - {Position{1, 7}, tokenLeftBracket, "["}, - {Position{1, 8}, tokenInteger, "42"}, - {Position{1, 10}, tokenComma, ","}, - {Position{1, 12}, tokenInteger, "21"}, - {Position{1, 14}, tokenRightBracket, "]"}, - {Position{1, 15}, tokenComma, ","}, - {Position{1, 17}, tokenLeftBracket, "["}, - {Position{1, 18}, tokenInteger, "10"}, - {Position{1, 20}, tokenRightBracket, "]"}, - {Position{1, 22}, tokenRightBracket, "]"}, - {Position{1, 23}, tokenEOF, ""}, - }) -} - -func TestArrayInts(t *testing.T) { - testFlow(t, "a = [ 42, 21, 10, ]", []token{ - {Position{1, 1}, tokenKey, "a"}, - {Position{1, 3}, tokenEqual, "="}, - {Position{1, 5}, tokenLeftBracket, "["}, - {Position{1, 7}, tokenInteger, "42"}, - {Position{1, 9}, tokenComma, ","}, - {Position{1, 11}, tokenInteger, "21"}, - {Position{1, 13}, tokenComma, ","}, - {Position{1, 15}, tokenInteger, "10"}, - {Position{1, 17}, tokenComma, ","}, - {Position{1, 19}, tokenRightBracket, "]"}, - {Position{1, 20}, tokenEOF, ""}, - }) -} - -func TestMultilineArrayComments(t *testing.T) { - testFlow(t, "a = [1, # wow\n2, # such items\n3, # so array\n]", []token{ - {Position{1, 1}, tokenKey, "a"}, - {Position{1, 3}, tokenEqual, "="}, - {Position{1, 5}, tokenLeftBracket, "["}, - {Position{1, 6}, tokenInteger, "1"}, - {Position{1, 7}, tokenComma, ","}, - {Position{2, 1}, tokenInteger, "2"}, - {Position{2, 2}, tokenComma, ","}, - {Position{3, 1}, tokenInteger, "3"}, - {Position{3, 2}, tokenComma, ","}, - {Position{4, 1}, tokenRightBracket, "]"}, - {Position{4, 2}, tokenEOF, ""}, - }) -} - -func TestNestedArraysComment(t *testing.T) { - toml := ` -someArray = [ -# does not work -["entry1"] -]` - testFlow(t, toml, []token{ - {Position{2, 1}, tokenKey, "someArray"}, - {Position{2, 11}, tokenEqual, "="}, - {Position{2, 13}, tokenLeftBracket, "["}, - {Position{4, 1}, tokenLeftBracket, "["}, - {Position{4, 3}, tokenString, "entry1"}, - {Position{4, 10}, tokenRightBracket, "]"}, - {Position{5, 1}, tokenRightBracket, "]"}, - {Position{5, 2}, tokenEOF, ""}, - }) -} - -func TestKeyEqualArrayBools(t *testing.T) { - testFlow(t, "foo = [true, false, true]", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLeftBracket, "["}, - {Position{1, 8}, tokenTrue, "true"}, - {Position{1, 12}, tokenComma, ","}, - {Position{1, 14}, tokenFalse, "false"}, - {Position{1, 19}, tokenComma, ","}, - {Position{1, 21}, tokenTrue, "true"}, - {Position{1, 25}, tokenRightBracket, "]"}, - {Position{1, 26}, tokenEOF, ""}, - }) -} - -func TestKeyEqualArrayBoolsWithComments(t *testing.T) { - testFlow(t, "foo = [true, false, true] # YEAH", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLeftBracket, "["}, - {Position{1, 8}, tokenTrue, "true"}, - {Position{1, 12}, tokenComma, ","}, - {Position{1, 14}, tokenFalse, "false"}, - {Position{1, 19}, tokenComma, ","}, - {Position{1, 21}, tokenTrue, "true"}, - {Position{1, 25}, tokenRightBracket, "]"}, - {Position{1, 33}, tokenEOF, ""}, - }) -} - -func TestKeyEqualDate(t *testing.T) { - t.Run("local date time", func(t *testing.T) { - testFlow(t, "foo = 1979-05-27T07:32:00", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLocalDate, "1979-05-27"}, - {Position{1, 18}, tokenLocalTime, "07:32:00"}, - {Position{1, 26}, tokenEOF, ""}, - }) - }) - - t.Run("local date time space", func(t *testing.T) { - testFlow(t, "foo = 1979-05-27 07:32:00", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLocalDate, "1979-05-27"}, - {Position{1, 18}, tokenLocalTime, "07:32:00"}, - {Position{1, 26}, tokenEOF, ""}, - }) - }) - - t.Run("local date time fraction", func(t *testing.T) { - testFlow(t, "foo = 1979-05-27T00:32:00.999999", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLocalDate, "1979-05-27"}, - {Position{1, 18}, tokenLocalTime, "00:32:00.999999"}, - {Position{1, 33}, tokenEOF, ""}, - }) - }) - - t.Run("local date time fraction space", func(t *testing.T) { - testFlow(t, "foo = 1979-05-27 00:32:00.999999", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLocalDate, "1979-05-27"}, - {Position{1, 18}, tokenLocalTime, "00:32:00.999999"}, - {Position{1, 33}, tokenEOF, ""}, - }) - }) - - t.Run("offset date-time utc", func(t *testing.T) { - testFlow(t, "foo = 1979-05-27T07:32:00Z", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLocalDate, "1979-05-27"}, - {Position{1, 18}, tokenLocalTime, "07:32:00"}, - {Position{1, 26}, tokenTimeOffset, "Z"}, - {Position{1, 27}, tokenEOF, ""}, - }) - }) - - t.Run("offset date-time -07:00", func(t *testing.T) { - testFlow(t, "foo = 1979-05-27T00:32:00-07:00", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLocalDate, "1979-05-27"}, - {Position{1, 18}, tokenLocalTime, "00:32:00"}, - {Position{1, 26}, tokenTimeOffset, "-07:00"}, - {Position{1, 32}, tokenEOF, ""}, - }) - }) - - t.Run("offset date-time fractions -07:00", func(t *testing.T) { - testFlow(t, "foo = 1979-05-27T00:32:00.999999-07:00", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLocalDate, "1979-05-27"}, - {Position{1, 18}, tokenLocalTime, "00:32:00.999999"}, - {Position{1, 33}, tokenTimeOffset, "-07:00"}, - {Position{1, 39}, tokenEOF, ""}, - }) - }) - - t.Run("offset date-time space separated utc", func(t *testing.T) { - testFlow(t, "foo = 1979-05-27 07:32:00Z", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLocalDate, "1979-05-27"}, - {Position{1, 18}, tokenLocalTime, "07:32:00"}, - {Position{1, 26}, tokenTimeOffset, "Z"}, - {Position{1, 27}, tokenEOF, ""}, - }) - }) - - t.Run("offset date-time space separated offset", func(t *testing.T) { - testFlow(t, "foo = 1979-05-27 00:32:00-07:00", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLocalDate, "1979-05-27"}, - {Position{1, 18}, tokenLocalTime, "00:32:00"}, - {Position{1, 26}, tokenTimeOffset, "-07:00"}, - {Position{1, 32}, tokenEOF, ""}, - }) - }) - - t.Run("offset date-time space separated fraction offset", func(t *testing.T) { - testFlow(t, "foo = 1979-05-27 00:32:00.999999-07:00", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLocalDate, "1979-05-27"}, - {Position{1, 18}, tokenLocalTime, "00:32:00.999999"}, - {Position{1, 33}, tokenTimeOffset, "-07:00"}, - {Position{1, 39}, tokenEOF, ""}, - }) - }) - - t.Run("local date", func(t *testing.T) { - testFlow(t, "foo = 1979-05-27", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLocalDate, "1979-05-27"}, - {Position{1, 17}, tokenEOF, ""}, - }) - }) - - t.Run("local time", func(t *testing.T) { - testFlow(t, "foo = 07:32:00", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLocalTime, "07:32:00"}, - {Position{1, 15}, tokenEOF, ""}, - }) - }) - - t.Run("local time fraction", func(t *testing.T) { - testFlow(t, "foo = 00:32:00.999999", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLocalTime, "00:32:00.999999"}, - {Position{1, 22}, tokenEOF, ""}, - }) - }) - - t.Run("local time invalid minute digit", func(t *testing.T) { - testFlow(t, "foo = 00:3x:00.999999", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenError, "invalid minute digit in time: x"}, - }) - }) - - t.Run("local time invalid minute/second digit", func(t *testing.T) { - testFlow(t, "foo = 00:30x00.999999", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenError, "time minute/second separator should be :, not x"}, - }) - }) - - t.Run("local time invalid second digit", func(t *testing.T) { - testFlow(t, "foo = 00:30:x0.999999", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenError, "invalid second digit in time: x"}, - }) - }) - - t.Run("local time invalid second digit", func(t *testing.T) { - testFlow(t, "foo = 00:30:00.F", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenError, "expected at least one digit in time's fraction, not F"}, - }) - }) - - t.Run("local date-time invalid minute digit", func(t *testing.T) { - testFlow(t, "foo = 1979-05-27 00:3x:00.999999", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLocalDate, "1979-05-27"}, - {Position{1, 18}, tokenError, "invalid minute digit in time: x"}, - }) - }) - - t.Run("local date-time invalid hour digit", func(t *testing.T) { - testFlow(t, "foo = 1979-05-27T0x:30:00.999999", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLocalDate, "1979-05-27"}, - {Position{1, 18}, tokenError, "invalid hour digit in time: x"}, - }) - }) - - t.Run("local date-time invalid hour digit", func(t *testing.T) { - testFlow(t, "foo = 1979-05-27T00x30:00.999999", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLocalDate, "1979-05-27"}, - {Position{1, 18}, tokenError, "time hour/minute separator should be :, not x"}, - }) - }) - - t.Run("local date-time invalid minute/second digit", func(t *testing.T) { - testFlow(t, "foo = 1979-05-27 00:30x00.999999", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLocalDate, "1979-05-27"}, - {Position{1, 18}, tokenError, "time minute/second separator should be :, not x"}, - }) - }) - - t.Run("local date-time invalid second digit", func(t *testing.T) { - testFlow(t, "foo = 1979-05-27 00:30:x0.999999", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLocalDate, "1979-05-27"}, - {Position{1, 18}, tokenError, "invalid second digit in time: x"}, - }) - }) - - t.Run("local date-time invalid fraction", func(t *testing.T) { - testFlow(t, "foo = 1979-05-27 00:30:00.F", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLocalDate, "1979-05-27"}, - {Position{1, 18}, tokenError, "expected at least one digit in time's fraction, not F"}, - }) - }) - - t.Run("local date-time invalid month-date separator", func(t *testing.T) { - testFlow(t, "foo = 1979-05X27 00:30:00.F", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenError, "expected - to separate month of a date, not X"}, - }) - }) - - t.Run("local date-time extra whitespace", func(t *testing.T) { - testFlow(t, "foo = 1979-05-27 ", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLocalDate, "1979-05-27"}, - {Position{1, 19}, tokenEOF, ""}, - }) - }) - - t.Run("local date-time extra whitespace", func(t *testing.T) { - testFlow(t, "foo = 1979-05-27 ", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLocalDate, "1979-05-27"}, - {Position{1, 22}, tokenEOF, ""}, - }) - }) - - t.Run("offset date-time space separated offset", func(t *testing.T) { - testFlow(t, "foo = 1979-05-27 00:32:00-0x:00", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLocalDate, "1979-05-27"}, - {Position{1, 18}, tokenLocalTime, "00:32:00"}, - {Position{1, 26}, tokenError, "invalid hour digit in time offset: x"}, - }) - }) - - t.Run("offset date-time space separated offset", func(t *testing.T) { - testFlow(t, "foo = 1979-05-27 00:32:00-07x00", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLocalDate, "1979-05-27"}, - {Position{1, 18}, tokenLocalTime, "00:32:00"}, - {Position{1, 26}, tokenError, "time offset hour/minute separator should be :, not x"}, - }) - }) - - t.Run("offset date-time space separated offset", func(t *testing.T) { - testFlow(t, "foo = 1979-05-27 00:32:00-07:x0", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLocalDate, "1979-05-27"}, - {Position{1, 18}, tokenLocalTime, "00:32:00"}, - {Position{1, 26}, tokenError, "invalid minute digit in time offset: x"}, - }) - }) -} - -func TestFloatEndingWithDot(t *testing.T) { - testFlow(t, "foo = 42.", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenError, "float cannot end with a dot"}, - }) -} - -func TestFloatWithTwoDots(t *testing.T) { - testFlow(t, "foo = 4.2.", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenError, "cannot have two dots in one float"}, - }) -} - -func TestFloatWithExponent1(t *testing.T) { - testFlow(t, "a = 5e+22", []token{ - {Position{1, 1}, tokenKey, "a"}, - {Position{1, 3}, tokenEqual, "="}, - {Position{1, 5}, tokenFloat, "5e+22"}, - {Position{1, 10}, tokenEOF, ""}, - }) -} - -func TestFloatWithExponent2(t *testing.T) { - testFlow(t, "a = 5E+22", []token{ - {Position{1, 1}, tokenKey, "a"}, - {Position{1, 3}, tokenEqual, "="}, - {Position{1, 5}, tokenFloat, "5E+22"}, - {Position{1, 10}, tokenEOF, ""}, - }) -} - -func TestFloatWithExponent3(t *testing.T) { - testFlow(t, "a = -5e+22", []token{ - {Position{1, 1}, tokenKey, "a"}, - {Position{1, 3}, tokenEqual, "="}, - {Position{1, 5}, tokenFloat, "-5e+22"}, - {Position{1, 11}, tokenEOF, ""}, - }) -} - -func TestFloatWithExponent4(t *testing.T) { - testFlow(t, "a = -5e-22", []token{ - {Position{1, 1}, tokenKey, "a"}, - {Position{1, 3}, tokenEqual, "="}, - {Position{1, 5}, tokenFloat, "-5e-22"}, - {Position{1, 11}, tokenEOF, ""}, - }) -} - -func TestFloatWithExponent5(t *testing.T) { - testFlow(t, "a = 6.626e-34", []token{ - {Position{1, 1}, tokenKey, "a"}, - {Position{1, 3}, tokenEqual, "="}, - {Position{1, 5}, tokenFloat, "6.626e-34"}, - {Position{1, 14}, tokenEOF, ""}, - }) -} - -func TestInvalidEsquapeSequence(t *testing.T) { - testFlow(t, `foo = "\x"`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 8}, tokenError, "invalid escape sequence: \\x"}, - }) -} - -func TestNestedArrays(t *testing.T) { - testFlow(t, "foo = [[[]]]", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLeftBracket, "["}, - {Position{1, 8}, tokenLeftBracket, "["}, - {Position{1, 9}, tokenLeftBracket, "["}, - {Position{1, 10}, tokenRightBracket, "]"}, - {Position{1, 11}, tokenRightBracket, "]"}, - {Position{1, 12}, tokenRightBracket, "]"}, - {Position{1, 13}, tokenEOF, ""}, - }) -} - -func TestKeyEqualNumber(t *testing.T) { - testFlow(t, "foo = 42", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenInteger, "42"}, - {Position{1, 9}, tokenEOF, ""}, - }) - - testFlow(t, "foo = +42", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenInteger, "+42"}, - {Position{1, 10}, tokenEOF, ""}, - }) - - testFlow(t, "foo = -42", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenInteger, "-42"}, - {Position{1, 10}, tokenEOF, ""}, - }) - - testFlow(t, "foo = 4.2", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenFloat, "4.2"}, - {Position{1, 10}, tokenEOF, ""}, - }) - - testFlow(t, "foo = +4.2", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenFloat, "+4.2"}, - {Position{1, 11}, tokenEOF, ""}, - }) - - testFlow(t, "foo = -4.2", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenFloat, "-4.2"}, - {Position{1, 11}, tokenEOF, ""}, - }) - - testFlow(t, "foo = 1_000", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenInteger, "1_000"}, - {Position{1, 12}, tokenEOF, ""}, - }) - - testFlow(t, "foo = 5_349_221", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenInteger, "5_349_221"}, - {Position{1, 16}, tokenEOF, ""}, - }) - - testFlow(t, "foo = 1_2_3_4_5", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenInteger, "1_2_3_4_5"}, - {Position{1, 16}, tokenEOF, ""}, - }) - - testFlow(t, "flt8 = 9_224_617.445_991_228_313", []token{ - {Position{1, 1}, tokenKey, "flt8"}, - {Position{1, 6}, tokenEqual, "="}, - {Position{1, 8}, tokenFloat, "9_224_617.445_991_228_313"}, - {Position{1, 33}, tokenEOF, ""}, - }) - - testFlow(t, "foo = +", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenError, "no digit in that number"}, - }) -} - -func TestMultiline(t *testing.T) { - testFlow(t, "foo = 42\nbar=21", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenInteger, "42"}, - {Position{2, 1}, tokenKey, "bar"}, - {Position{2, 4}, tokenEqual, "="}, - {Position{2, 5}, tokenInteger, "21"}, - {Position{2, 7}, tokenEOF, ""}, - }) -} - -func TestKeyEqualStringUnicodeEscape(t *testing.T) { - testFlow(t, `foo = "hello \u2665"`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 8}, tokenString, "hello ♥"}, - {Position{1, 21}, tokenEOF, ""}, - }) - testFlow(t, `foo = "hello \U000003B4"`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 8}, tokenString, "hello δ"}, - {Position{1, 25}, tokenEOF, ""}, - }) - testFlow(t, `foo = "\uabcd"`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 8}, tokenString, "\uabcd"}, - {Position{1, 15}, tokenEOF, ""}, - }) - testFlow(t, `foo = "\uABCD"`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 8}, tokenString, "\uABCD"}, - {Position{1, 15}, tokenEOF, ""}, - }) - testFlow(t, `foo = "\U000bcdef"`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 8}, tokenString, "\U000bcdef"}, - {Position{1, 19}, tokenEOF, ""}, - }) - testFlow(t, `foo = "\U000BCDEF"`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 8}, tokenString, "\U000BCDEF"}, - {Position{1, 19}, tokenEOF, ""}, - }) - testFlow(t, `foo = "\u2"`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 8}, tokenError, "unfinished unicode escape"}, - }) - testFlow(t, `foo = "\U2"`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 8}, tokenError, "unfinished unicode escape"}, - }) -} - -func TestKeyEqualStringNoEscape(t *testing.T) { - testFlow(t, "foo = \"hello \u0002\"", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 8}, tokenError, "unescaped control character U+0002"}, - }) - testFlow(t, "foo = \"hello \u001F\"", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 8}, tokenError, "unescaped control character U+001F"}, - }) -} - -func TestLiteralString(t *testing.T) { - testFlow(t, `foo = 'C:\Users\nodejs\templates'`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 8}, tokenString, `C:\Users\nodejs\templates`}, - {Position{1, 34}, tokenEOF, ""}, - }) - testFlow(t, `foo = '\\ServerX\admin$\system32\'`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 8}, tokenString, `\\ServerX\admin$\system32\`}, - {Position{1, 35}, tokenEOF, ""}, - }) - testFlow(t, `foo = 'Tom "Dubs" Preston-Werner'`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 8}, tokenString, `Tom "Dubs" Preston-Werner`}, - {Position{1, 34}, tokenEOF, ""}, - }) - testFlow(t, `foo = '<\i\c*\s*>'`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 8}, tokenString, `<\i\c*\s*>`}, - {Position{1, 19}, tokenEOF, ""}, - }) - testFlow(t, `foo = 'C:\Users\nodejs\unfinis`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 8}, tokenError, "unclosed string"}, - }) -} - -func TestMultilineLiteralString(t *testing.T) { - testFlow(t, `foo = '''hello 'literal' world'''`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 10}, tokenString, `hello 'literal' world`}, - {Position{1, 34}, tokenEOF, ""}, - }) - - testFlow(t, "foo = '''\nhello\n'literal'\nworld'''", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{2, 1}, tokenString, "hello\n'literal'\nworld"}, - {Position{4, 9}, tokenEOF, ""}, - }) - testFlow(t, "foo = '''\r\nhello\r\n'literal'\r\nworld'''", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{2, 1}, tokenString, "hello\r\n'literal'\r\nworld"}, - {Position{4, 9}, tokenEOF, ""}, - }) -} - -func TestMultilineString(t *testing.T) { - testFlow(t, `foo = """hello "literal" world"""`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 10}, tokenString, `hello "literal" world`}, - {Position{1, 34}, tokenEOF, ""}, - }) - - testFlow(t, "foo = \"\"\"\r\nhello\\\r\n\"literal\"\\\nworld\"\"\"", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{2, 1}, tokenString, "hello\"literal\"world"}, - {Position{4, 9}, tokenEOF, ""}, - }) - - testFlow(t, "foo = \"\"\"\\\n \\\n \\\n hello\\\nmultiline\\\nworld\"\"\"", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 10}, tokenString, "hellomultilineworld"}, - {Position{6, 9}, tokenEOF, ""}, - }) - - testFlow(t, `foo = """hello world"""`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 10}, tokenString, "hello\tworld"}, - {Position{1, 24}, tokenEOF, ""}, - }) - - testFlow(t, "key2 = \"\"\"\nThe quick brown \\\n\n\n fox jumps over \\\n the lazy dog.\"\"\"", []token{ - {Position{1, 1}, tokenKey, "key2"}, - {Position{1, 6}, tokenEqual, "="}, - {Position{2, 1}, tokenString, "The quick brown fox jumps over the lazy dog."}, - {Position{6, 21}, tokenEOF, ""}, - }) - - testFlow(t, "key2 = \"\"\"\\\n The quick brown \\\n fox jumps over \\\n the lazy dog.\\\n \"\"\"", []token{ - {Position{1, 1}, tokenKey, "key2"}, - {Position{1, 6}, tokenEqual, "="}, - {Position{1, 11}, tokenString, "The quick brown fox jumps over the lazy dog."}, - {Position{5, 11}, tokenEOF, ""}, - }) - - testFlow(t, `key2 = "Roses are red\nViolets are blue"`, []token{ - {Position{1, 1}, tokenKey, "key2"}, - {Position{1, 6}, tokenEqual, "="}, - {Position{1, 9}, tokenString, "Roses are red\nViolets are blue"}, - {Position{1, 41}, tokenEOF, ""}, - }) - - testFlow(t, "key2 = \"\"\"\nRoses are red\nViolets are blue\"\"\"", []token{ - {Position{1, 1}, tokenKey, "key2"}, - {Position{1, 6}, tokenEqual, "="}, - {Position{2, 1}, tokenString, "Roses are red\nViolets are blue"}, - {Position{3, 20}, tokenEOF, ""}, - }) -} - -func TestUnicodeString(t *testing.T) { - testFlow(t, `foo = "hello ♥ world"`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 8}, tokenString, "hello ♥ world"}, - {Position{1, 22}, tokenEOF, ""}, - }) -} - -func TestEscapeInString(t *testing.T) { - testFlow(t, `foo = "\b\f\/"`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 8}, tokenString, "\b\f/"}, - {Position{1, 15}, tokenEOF, ""}, - }) -} - -func TestTabInString(t *testing.T) { - testFlow(t, `foo = "hello world"`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 8}, tokenString, "hello\tworld"}, - {Position{1, 20}, tokenEOF, ""}, - }) -} - -func TestKeyGroupArray(t *testing.T) { - testFlow(t, "[[foo]]", []token{ - {Position{1, 1}, tokenDoubleLeftBracket, "[["}, - {Position{1, 3}, tokenKeyGroupArray, "foo"}, - {Position{1, 6}, tokenDoubleRightBracket, "]]"}, - {Position{1, 8}, tokenEOF, ""}, - }) -} - -func TestQuotedKey(t *testing.T) { - testFlow(t, "\"a b\" = 42", []token{ - {Position{1, 1}, tokenKey, "\"a b\""}, - {Position{1, 7}, tokenEqual, "="}, - {Position{1, 9}, tokenInteger, "42"}, - {Position{1, 11}, tokenEOF, ""}, - }) -} - -func TestQuotedKeyTab(t *testing.T) { - testFlow(t, "\"num\tber\" = 123", []token{ - {Position{1, 1}, tokenKey, "\"num\tber\""}, - {Position{1, 11}, tokenEqual, "="}, - {Position{1, 13}, tokenInteger, "123"}, - {Position{1, 16}, tokenEOF, ""}, - }) -} - -func TestKeyNewline(t *testing.T) { - testFlow(t, "a\n= 4", []token{ - {Position{1, 1}, tokenError, "keys cannot contain new lines"}, - }) -} - -func TestInvalidFloat(t *testing.T) { - testFlow(t, "a=7e1_", []token{ - {Position{1, 1}, tokenKey, "a"}, - {Position{1, 2}, tokenEqual, "="}, - {Position{1, 3}, tokenFloat, "7e1_"}, - {Position{1, 7}, tokenEOF, ""}, - }) -} - -func TestLexUnknownRvalue(t *testing.T) { - testFlow(t, `a = !b`, []token{ - {Position{1, 1}, tokenKey, "a"}, - {Position{1, 3}, tokenEqual, "="}, - {Position{1, 5}, tokenError, "no value can start with !"}, - }) - - testFlow(t, `a = \b`, []token{ - {Position{1, 1}, tokenKey, "a"}, - {Position{1, 3}, tokenEqual, "="}, - {Position{1, 5}, tokenError, `no value can start with \`}, - }) -} - -func TestLexInlineTableEmpty(t *testing.T) { - testFlow(t, `foo = {}`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLeftCurlyBrace, "{"}, - {Position{1, 8}, tokenRightCurlyBrace, "}"}, - {Position{1, 9}, tokenEOF, ""}, - }) -} - -func TestLexInlineTableBareKey(t *testing.T) { - testFlow(t, `foo = { bar = "baz" }`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLeftCurlyBrace, "{"}, - {Position{1, 9}, tokenKey, "bar"}, - {Position{1, 13}, tokenEqual, "="}, - {Position{1, 16}, tokenString, "baz"}, - {Position{1, 21}, tokenRightCurlyBrace, "}"}, - {Position{1, 22}, tokenEOF, ""}, - }) -} - -func TestLexInlineTableBareKeyDash(t *testing.T) { - testFlow(t, `foo = { -bar = "baz" }`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLeftCurlyBrace, "{"}, - {Position{1, 9}, tokenKey, "-bar"}, - {Position{1, 14}, tokenEqual, "="}, - {Position{1, 17}, tokenString, "baz"}, - {Position{1, 22}, tokenRightCurlyBrace, "}"}, - {Position{1, 23}, tokenEOF, ""}, - }) -} - -func TestLexInlineTableBareKeyInArray(t *testing.T) { - testFlow(t, `foo = [{ -bar_ = "baz" }]`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLeftBracket, "["}, - {Position{1, 8}, tokenLeftCurlyBrace, "{"}, - {Position{1, 10}, tokenKey, "-bar_"}, - {Position{1, 16}, tokenEqual, "="}, - {Position{1, 19}, tokenString, "baz"}, - {Position{1, 24}, tokenRightCurlyBrace, "}"}, - {Position{1, 25}, tokenRightBracket, "]"}, - {Position{1, 26}, tokenEOF, ""}, - }) -} - -func TestLexInlineTableError1(t *testing.T) { - testFlow(t, `foo = { 123 = 0 ]`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLeftCurlyBrace, "{"}, - {Position{1, 9}, tokenKey, "123"}, - {Position{1, 13}, tokenEqual, "="}, - {Position{1, 15}, tokenInteger, "0"}, - {Position{1, 17}, tokenRightBracket, "]"}, - {Position{1, 18}, tokenError, "cannot have ']' here"}, - }) -} - -func TestLexInlineTableError2(t *testing.T) { - testFlow(t, `foo = { 123 = 0 }}`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLeftCurlyBrace, "{"}, - {Position{1, 9}, tokenKey, "123"}, - {Position{1, 13}, tokenEqual, "="}, - {Position{1, 15}, tokenInteger, "0"}, - {Position{1, 17}, tokenRightCurlyBrace, "}"}, - {Position{1, 18}, tokenRightCurlyBrace, "}"}, - {Position{1, 19}, tokenError, "cannot have '}' here"}, - }) -} - -func TestLexInlineTableDottedKey1(t *testing.T) { - testFlow(t, `foo = { a = 0, 123.45abc = 0 }`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLeftCurlyBrace, "{"}, - {Position{1, 9}, tokenKey, "a"}, - {Position{1, 11}, tokenEqual, "="}, - {Position{1, 13}, tokenInteger, "0"}, - {Position{1, 14}, tokenComma, ","}, - {Position{1, 16}, tokenKey, "123.45abc"}, - {Position{1, 26}, tokenEqual, "="}, - {Position{1, 28}, tokenInteger, "0"}, - {Position{1, 30}, tokenRightCurlyBrace, "}"}, - {Position{1, 31}, tokenEOF, ""}, - }) -} - -func TestLexInlineTableDottedKey2(t *testing.T) { - testFlow(t, `foo = { a = 0, '123'.'45abc' = 0 }`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLeftCurlyBrace, "{"}, - {Position{1, 9}, tokenKey, "a"}, - {Position{1, 11}, tokenEqual, "="}, - {Position{1, 13}, tokenInteger, "0"}, - {Position{1, 14}, tokenComma, ","}, - {Position{1, 16}, tokenKey, "'123'.'45abc'"}, - {Position{1, 30}, tokenEqual, "="}, - {Position{1, 32}, tokenInteger, "0"}, - {Position{1, 34}, tokenRightCurlyBrace, "}"}, - {Position{1, 35}, tokenEOF, ""}, - }) -} - -func TestLexInlineTableDottedKey3(t *testing.T) { - testFlow(t, `foo = { a = 0, "123"."45ʎǝʞ" = 0 }`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLeftCurlyBrace, "{"}, - {Position{1, 9}, tokenKey, "a"}, - {Position{1, 11}, tokenEqual, "="}, - {Position{1, 13}, tokenInteger, "0"}, - {Position{1, 14}, tokenComma, ","}, - {Position{1, 16}, tokenKey, `"123"."45ʎǝʞ"`}, - {Position{1, 30}, tokenEqual, "="}, - {Position{1, 32}, tokenInteger, "0"}, - {Position{1, 34}, tokenRightCurlyBrace, "}"}, - {Position{1, 35}, tokenEOF, ""}, - }) -} - -func TestLexInlineTableBareKeyWithComma(t *testing.T) { - testFlow(t, `foo = { -bar1 = "baz", -bar_ = "baz" }`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLeftCurlyBrace, "{"}, - {Position{1, 9}, tokenKey, "-bar1"}, - {Position{1, 15}, tokenEqual, "="}, - {Position{1, 18}, tokenString, "baz"}, - {Position{1, 22}, tokenComma, ","}, - {Position{1, 24}, tokenKey, "-bar_"}, - {Position{1, 30}, tokenEqual, "="}, - {Position{1, 33}, tokenString, "baz"}, - {Position{1, 38}, tokenRightCurlyBrace, "}"}, - {Position{1, 39}, tokenEOF, ""}, - }) -} - -func TestLexInlineTableBareKeyUnderscore(t *testing.T) { - testFlow(t, `foo = { _bar = "baz" }`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLeftCurlyBrace, "{"}, - {Position{1, 9}, tokenKey, "_bar"}, - {Position{1, 14}, tokenEqual, "="}, - {Position{1, 17}, tokenString, "baz"}, - {Position{1, 22}, tokenRightCurlyBrace, "}"}, - {Position{1, 23}, tokenEOF, ""}, - }) -} - -func TestLexInlineTableQuotedKey(t *testing.T) { - testFlow(t, `foo = { "bar" = "baz" }`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLeftCurlyBrace, "{"}, - {Position{1, 9}, tokenKey, "\"bar\""}, - {Position{1, 15}, tokenEqual, "="}, - {Position{1, 18}, tokenString, "baz"}, - {Position{1, 23}, tokenRightCurlyBrace, "}"}, - {Position{1, 24}, tokenEOF, ""}, - }) -} - -func BenchmarkLexer(b *testing.B) { - sample := `title = "Hugo: A Fast and Flexible Website Generator" -baseurl = "http://gohugo.io/" -MetaDataFormat = "yaml" -pluralizeListTitles = false - -[params] - description = "Documentation of Hugo, a fast and flexible static site generator built with love by spf13, bep and friends in Go" - author = "Steve Francia (spf13) and friends" - release = "0.22-DEV" - -[[menu.main]] - name = "Download Hugo" - pre = "" - url = "https://github.com/spf13/hugo/releases" - weight = -200 -` - b.ResetTimer() - for i := 0; i < b.N; i++ { - lexToml([]byte(sample)) - } -} diff --git a/localtime.go b/localtime.go deleted file mode 100644 index a2149e96..00000000 --- a/localtime.go +++ /dev/null @@ -1,281 +0,0 @@ -// Implementation of TOML's local date/time. -// Copied over from https://github.com/googleapis/google-cloud-go/blob/master/civil/civil.go -// to avoid pulling all the Google dependencies. -// -// Copyright 2016 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package civil implements types for civil time, a time-zone-independent -// representation of time that follows the rules of the proleptic -// Gregorian calendar with exactly 24-hour days, 60-minute hours, and 60-second -// minutes. -// -// Because they lack location information, these types do not represent unique -// moments or intervals of time. Use time.Time for that purpose. -package toml - -import ( - "fmt" - "time" -) - -// A LocalDate represents a date (year, month, day). -// -// This type does not include location information, and therefore does not -// describe a unique 24-hour timespan. -type LocalDate struct { - Year int // Year (e.g., 2014). - Month time.Month // Month of the year (January = 1, ...). - Day int // Day of the month, starting at 1. -} - -// LocalDateOf returns the LocalDate in which a time occurs in that time's location. -func LocalDateOf(t time.Time) LocalDate { - var d LocalDate - d.Year, d.Month, d.Day = t.Date() - return d -} - -// ParseLocalDate parses a string in RFC3339 full-date format and returns the date value it represents. -func ParseLocalDate(s string) (LocalDate, error) { - t, err := time.Parse("2006-01-02", s) - if err != nil { - return LocalDate{}, err - } - return LocalDateOf(t), nil -} - -// String returns the date in RFC3339 full-date format. -func (d LocalDate) String() string { - return fmt.Sprintf("%04d-%02d-%02d", d.Year, d.Month, d.Day) -} - -// IsValid reports whether the date is valid. -func (d LocalDate) IsValid() bool { - return LocalDateOf(d.In(time.UTC)) == d -} - -// In returns the time corresponding to time 00:00:00 of the date in the location. -// -// In is always consistent with time.LocalDate, even when time.LocalDate returns a time -// on a different day. For example, if loc is America/Indiana/Vincennes, then both -// time.LocalDate(1955, time.May, 1, 0, 0, 0, 0, loc) -// and -// civil.LocalDate{Year: 1955, Month: time.May, Day: 1}.In(loc) -// return 23:00:00 on April 30, 1955. -// -// In panics if loc is nil. -func (d LocalDate) In(loc *time.Location) time.Time { - return time.Date(d.Year, d.Month, d.Day, 0, 0, 0, 0, loc) -} - -// AddDays returns the date that is n days in the future. -// n can also be negative to go into the past. -func (d LocalDate) AddDays(n int) LocalDate { - return LocalDateOf(d.In(time.UTC).AddDate(0, 0, n)) -} - -// DaysSince returns the signed number of days between the date and s, not including the end day. -// This is the inverse operation to AddDays. -func (d LocalDate) DaysSince(s LocalDate) (days int) { - // We convert to Unix time so we do not have to worry about leap seconds: - // Unix time increases by exactly 86400 seconds per day. - deltaUnix := d.In(time.UTC).Unix() - s.In(time.UTC).Unix() - return int(deltaUnix / 86400) -} - -// Before reports whether d1 occurs before d2. -func (d1 LocalDate) Before(d2 LocalDate) bool { - if d1.Year != d2.Year { - return d1.Year < d2.Year - } - if d1.Month != d2.Month { - return d1.Month < d2.Month - } - return d1.Day < d2.Day -} - -// After reports whether d1 occurs after d2. -func (d1 LocalDate) After(d2 LocalDate) bool { - return d2.Before(d1) -} - -// MarshalText implements the encoding.TextMarshaler interface. -// The output is the result of d.String(). -func (d LocalDate) MarshalText() ([]byte, error) { - return []byte(d.String()), nil -} - -// UnmarshalText implements the encoding.TextUnmarshaler interface. -// The date is expected to be a string in a format accepted by ParseLocalDate. -func (d *LocalDate) UnmarshalText(data []byte) error { - var err error - *d, err = ParseLocalDate(string(data)) - return err -} - -// A LocalTime represents a time with nanosecond precision. -// -// This type does not include location information, and therefore does not -// describe a unique moment in time. -// -// This type exists to represent the TIME type in storage-based APIs like BigQuery. -// Most operations on Times are unlikely to be meaningful. Prefer the LocalDateTime type. -type LocalTime struct { - Hour int // The hour of the day in 24-hour format; range [0-23] - Minute int // The minute of the hour; range [0-59] - Second int // The second of the minute; range [0-59] - Nanosecond int // The nanosecond of the second; range [0-999999999] -} - -// LocalTimeOf returns the LocalTime representing the time of day in which a time occurs -// in that time's location. It ignores the date. -func LocalTimeOf(t time.Time) LocalTime { - var tm LocalTime - tm.Hour, tm.Minute, tm.Second = t.Clock() - tm.Nanosecond = t.Nanosecond() - return tm -} - -// ParseLocalTime parses a string and returns the time value it represents. -// ParseLocalTime accepts an extended form of the RFC3339 partial-time format. After -// the HH:MM:SS part of the string, an optional fractional part may appear, -// consisting of a decimal point followed by one to nine decimal digits. -// (RFC3339 admits only one digit after the decimal point). -func ParseLocalTime(s string) (LocalTime, error) { - t, err := time.Parse("15:04:05.999999999", s) - if err != nil { - return LocalTime{}, err - } - return LocalTimeOf(t), nil -} - -// String returns the date in the format described in ParseLocalTime. If Nanoseconds -// is zero, no fractional part will be generated. Otherwise, the result will -// end with a fractional part consisting of a decimal point and nine digits. -func (t LocalTime) String() string { - s := fmt.Sprintf("%02d:%02d:%02d", t.Hour, t.Minute, t.Second) - if t.Nanosecond == 0 { - return s - } - return s + fmt.Sprintf(".%09d", t.Nanosecond) -} - -// IsValid reports whether the time is valid. -func (t LocalTime) IsValid() bool { - // Construct a non-zero time. - tm := time.Date(2, 2, 2, t.Hour, t.Minute, t.Second, t.Nanosecond, time.UTC) - return LocalTimeOf(tm) == t -} - -// MarshalText implements the encoding.TextMarshaler interface. -// The output is the result of t.String(). -func (t LocalTime) MarshalText() ([]byte, error) { - return []byte(t.String()), nil -} - -// UnmarshalText implements the encoding.TextUnmarshaler interface. -// The time is expected to be a string in a format accepted by ParseLocalTime. -func (t *LocalTime) UnmarshalText(data []byte) error { - var err error - *t, err = ParseLocalTime(string(data)) - return err -} - -// A LocalDateTime represents a date and time. -// -// This type does not include location information, and therefore does not -// describe a unique moment in time. -type LocalDateTime struct { - Date LocalDate - Time LocalTime -} - -// Note: We deliberately do not embed LocalDate into LocalDateTime, to avoid promoting AddDays and Sub. - -// LocalDateTimeOf returns the LocalDateTime in which a time occurs in that time's location. -func LocalDateTimeOf(t time.Time) LocalDateTime { - return LocalDateTime{ - Date: LocalDateOf(t), - Time: LocalTimeOf(t), - } -} - -// ParseLocalDateTime parses a string and returns the LocalDateTime it represents. -// ParseLocalDateTime accepts a variant of the RFC3339 date-time format that omits -// the time offset but includes an optional fractional time, as described in -// ParseLocalTime. Informally, the accepted format is -// YYYY-MM-DDTHH:MM:SS[.FFFFFFFFF] -// where the 'T' may be a lower-case 't'. -func ParseLocalDateTime(s string) (LocalDateTime, error) { - t, err := time.Parse("2006-01-02T15:04:05.999999999", s) - if err != nil { - t, err = time.Parse("2006-01-02t15:04:05.999999999", s) - if err != nil { - return LocalDateTime{}, err - } - } - return LocalDateTimeOf(t), nil -} - -// String returns the date in the format described in ParseLocalDate. -func (dt LocalDateTime) String() string { - return dt.Date.String() + "T" + dt.Time.String() -} - -// IsValid reports whether the datetime is valid. -func (dt LocalDateTime) IsValid() bool { - return dt.Date.IsValid() && dt.Time.IsValid() -} - -// In returns the time corresponding to the LocalDateTime in the given location. -// -// If the time is missing or ambigous at the location, In returns the same -// result as time.LocalDate. For example, if loc is America/Indiana/Vincennes, then -// both -// time.LocalDate(1955, time.May, 1, 0, 30, 0, 0, loc) -// and -// civil.LocalDateTime{ -// civil.LocalDate{Year: 1955, Month: time.May, Day: 1}}, -// civil.LocalTime{Minute: 30}}.In(loc) -// return 23:30:00 on April 30, 1955. -// -// In panics if loc is nil. -func (dt LocalDateTime) In(loc *time.Location) time.Time { - return time.Date(dt.Date.Year, dt.Date.Month, dt.Date.Day, dt.Time.Hour, dt.Time.Minute, dt.Time.Second, dt.Time.Nanosecond, loc) -} - -// Before reports whether dt1 occurs before dt2. -func (dt1 LocalDateTime) Before(dt2 LocalDateTime) bool { - return dt1.In(time.UTC).Before(dt2.In(time.UTC)) -} - -// After reports whether dt1 occurs after dt2. -func (dt1 LocalDateTime) After(dt2 LocalDateTime) bool { - return dt2.Before(dt1) -} - -// MarshalText implements the encoding.TextMarshaler interface. -// The output is the result of dt.String(). -func (dt LocalDateTime) MarshalText() ([]byte, error) { - return []byte(dt.String()), nil -} - -// UnmarshalText implements the encoding.TextUnmarshaler interface. -// The datetime is expected to be a string in a format accepted by ParseLocalDateTime -func (dt *LocalDateTime) UnmarshalText(data []byte) error { - var err error - *dt, err = ParseLocalDateTime(string(data)) - return err -} diff --git a/localtime_test.go b/localtime_test.go deleted file mode 100644 index 4bbb5b0e..00000000 --- a/localtime_test.go +++ /dev/null @@ -1,446 +0,0 @@ -// Copyright 2016 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package toml - -import ( - "encoding/json" - "reflect" - "testing" - "time" -) - -func cmpEqual(x, y interface{}) bool { - return reflect.DeepEqual(x, y) -} - -func TestDates(t *testing.T) { - for _, test := range []struct { - date LocalDate - loc *time.Location - wantStr string - wantTime time.Time - }{ - { - date: LocalDate{2014, 7, 29}, - loc: time.Local, - wantStr: "2014-07-29", - wantTime: time.Date(2014, time.July, 29, 0, 0, 0, 0, time.Local), - }, - { - date: LocalDateOf(time.Date(2014, 8, 20, 15, 8, 43, 1, time.Local)), - loc: time.UTC, - wantStr: "2014-08-20", - wantTime: time.Date(2014, 8, 20, 0, 0, 0, 0, time.UTC), - }, - { - date: LocalDateOf(time.Date(999, time.January, 26, 0, 0, 0, 0, time.Local)), - loc: time.UTC, - wantStr: "0999-01-26", - wantTime: time.Date(999, 1, 26, 0, 0, 0, 0, time.UTC), - }, - } { - if got := test.date.String(); got != test.wantStr { - t.Errorf("%#v.String() = %q, want %q", test.date, got, test.wantStr) - } - if got := test.date.In(test.loc); !got.Equal(test.wantTime) { - t.Errorf("%#v.In(%v) = %v, want %v", test.date, test.loc, got, test.wantTime) - } - } -} - -func TestDateIsValid(t *testing.T) { - for _, test := range []struct { - date LocalDate - want bool - }{ - {LocalDate{2014, 7, 29}, true}, - {LocalDate{2000, 2, 29}, true}, - {LocalDate{10000, 12, 31}, true}, - {LocalDate{1, 1, 1}, true}, - {LocalDate{0, 1, 1}, true}, // year zero is OK - {LocalDate{-1, 1, 1}, true}, // negative year is OK - {LocalDate{1, 0, 1}, false}, - {LocalDate{1, 1, 0}, false}, - {LocalDate{2016, 1, 32}, false}, - {LocalDate{2016, 13, 1}, false}, - {LocalDate{1, -1, 1}, false}, - {LocalDate{1, 1, -1}, false}, - } { - got := test.date.IsValid() - if got != test.want { - t.Errorf("%#v: got %t, want %t", test.date, got, test.want) - } - } -} - -func TestParseDate(t *testing.T) { - for _, test := range []struct { - str string - want LocalDate // if empty, expect an error - }{ - {"2016-01-02", LocalDate{2016, 1, 2}}, - {"2016-12-31", LocalDate{2016, 12, 31}}, - {"0003-02-04", LocalDate{3, 2, 4}}, - {"999-01-26", LocalDate{}}, - {"", LocalDate{}}, - {"2016-01-02x", LocalDate{}}, - } { - got, err := ParseLocalDate(test.str) - if got != test.want { - t.Errorf("ParseLocalDate(%q) = %+v, want %+v", test.str, got, test.want) - } - if err != nil && test.want != (LocalDate{}) { - t.Errorf("Unexpected error %v from ParseLocalDate(%q)", err, test.str) - } - } -} - -func TestDateArithmetic(t *testing.T) { - for _, test := range []struct { - desc string - start LocalDate - end LocalDate - days int - }{ - { - desc: "zero days noop", - start: LocalDate{2014, 5, 9}, - end: LocalDate{2014, 5, 9}, - days: 0, - }, - { - desc: "crossing a year boundary", - start: LocalDate{2014, 12, 31}, - end: LocalDate{2015, 1, 1}, - days: 1, - }, - { - desc: "negative number of days", - start: LocalDate{2015, 1, 1}, - end: LocalDate{2014, 12, 31}, - days: -1, - }, - { - desc: "full leap year", - start: LocalDate{2004, 1, 1}, - end: LocalDate{2005, 1, 1}, - days: 366, - }, - { - desc: "full non-leap year", - start: LocalDate{2001, 1, 1}, - end: LocalDate{2002, 1, 1}, - days: 365, - }, - { - desc: "crossing a leap second", - start: LocalDate{1972, 6, 30}, - end: LocalDate{1972, 7, 1}, - days: 1, - }, - { - desc: "dates before the unix epoch", - start: LocalDate{101, 1, 1}, - end: LocalDate{102, 1, 1}, - days: 365, - }, - } { - if got := test.start.AddDays(test.days); got != test.end { - t.Errorf("[%s] %#v.AddDays(%v) = %#v, want %#v", test.desc, test.start, test.days, got, test.end) - } - if got := test.end.DaysSince(test.start); got != test.days { - t.Errorf("[%s] %#v.Sub(%#v) = %v, want %v", test.desc, test.end, test.start, got, test.days) - } - } -} - -func TestDateBefore(t *testing.T) { - for _, test := range []struct { - d1, d2 LocalDate - want bool - }{ - {LocalDate{2016, 12, 31}, LocalDate{2017, 1, 1}, true}, - {LocalDate{2016, 1, 1}, LocalDate{2016, 1, 1}, false}, - {LocalDate{2016, 12, 30}, LocalDate{2016, 12, 31}, true}, - {LocalDate{2016, 1, 30}, LocalDate{2016, 12, 31}, true}, - } { - if got := test.d1.Before(test.d2); got != test.want { - t.Errorf("%v.Before(%v): got %t, want %t", test.d1, test.d2, got, test.want) - } - } -} - -func TestDateAfter(t *testing.T) { - for _, test := range []struct { - d1, d2 LocalDate - want bool - }{ - {LocalDate{2016, 12, 31}, LocalDate{2017, 1, 1}, false}, - {LocalDate{2016, 1, 1}, LocalDate{2016, 1, 1}, false}, - {LocalDate{2016, 12, 30}, LocalDate{2016, 12, 31}, false}, - } { - if got := test.d1.After(test.d2); got != test.want { - t.Errorf("%v.After(%v): got %t, want %t", test.d1, test.d2, got, test.want) - } - } -} - -func TestTimeToString(t *testing.T) { - for _, test := range []struct { - str string - time LocalTime - roundTrip bool // ParseLocalTime(str).String() == str? - }{ - {"13:26:33", LocalTime{13, 26, 33, 0}, true}, - {"01:02:03.000023456", LocalTime{1, 2, 3, 23456}, true}, - {"00:00:00.000000001", LocalTime{0, 0, 0, 1}, true}, - {"13:26:03.1", LocalTime{13, 26, 3, 100000000}, false}, - {"13:26:33.0000003", LocalTime{13, 26, 33, 300}, false}, - } { - gotTime, err := ParseLocalTime(test.str) - if err != nil { - t.Errorf("ParseLocalTime(%q): got error: %v", test.str, err) - continue - } - if gotTime != test.time { - t.Errorf("ParseLocalTime(%q) = %+v, want %+v", test.str, gotTime, test.time) - } - if test.roundTrip { - gotStr := test.time.String() - if gotStr != test.str { - t.Errorf("%#v.String() = %q, want %q", test.time, gotStr, test.str) - } - } - } -} - -func TestTimeOf(t *testing.T) { - for _, test := range []struct { - time time.Time - want LocalTime - }{ - {time.Date(2014, 8, 20, 15, 8, 43, 1, time.Local), LocalTime{15, 8, 43, 1}}, - {time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC), LocalTime{0, 0, 0, 0}}, - } { - if got := LocalTimeOf(test.time); got != test.want { - t.Errorf("LocalTimeOf(%v) = %+v, want %+v", test.time, got, test.want) - } - } -} - -func TestTimeIsValid(t *testing.T) { - for _, test := range []struct { - time LocalTime - want bool - }{ - {LocalTime{0, 0, 0, 0}, true}, - {LocalTime{23, 0, 0, 0}, true}, - {LocalTime{23, 59, 59, 999999999}, true}, - {LocalTime{24, 59, 59, 999999999}, false}, - {LocalTime{23, 60, 59, 999999999}, false}, - {LocalTime{23, 59, 60, 999999999}, false}, - {LocalTime{23, 59, 59, 1000000000}, false}, - {LocalTime{-1, 0, 0, 0}, false}, - {LocalTime{0, -1, 0, 0}, false}, - {LocalTime{0, 0, -1, 0}, false}, - {LocalTime{0, 0, 0, -1}, false}, - } { - got := test.time.IsValid() - if got != test.want { - t.Errorf("%#v: got %t, want %t", test.time, got, test.want) - } - } -} - -func TestDateTimeToString(t *testing.T) { - for _, test := range []struct { - str string - dateTime LocalDateTime - roundTrip bool // ParseLocalDateTime(str).String() == str? - }{ - {"2016-03-22T13:26:33", LocalDateTime{LocalDate{2016, 03, 22}, LocalTime{13, 26, 33, 0}}, true}, - {"2016-03-22T13:26:33.000000600", LocalDateTime{LocalDate{2016, 03, 22}, LocalTime{13, 26, 33, 600}}, true}, - {"2016-03-22t13:26:33", LocalDateTime{LocalDate{2016, 03, 22}, LocalTime{13, 26, 33, 0}}, false}, - } { - gotDateTime, err := ParseLocalDateTime(test.str) - if err != nil { - t.Errorf("ParseLocalDateTime(%q): got error: %v", test.str, err) - continue - } - if gotDateTime != test.dateTime { - t.Errorf("ParseLocalDateTime(%q) = %+v, want %+v", test.str, gotDateTime, test.dateTime) - } - if test.roundTrip { - gotStr := test.dateTime.String() - if gotStr != test.str { - t.Errorf("%#v.String() = %q, want %q", test.dateTime, gotStr, test.str) - } - } - } -} - -func TestParseDateTimeErrors(t *testing.T) { - for _, str := range []string{ - "", - "2016-03-22", // just a date - "13:26:33", // just a time - "2016-03-22 13:26:33", // wrong separating character - "2016-03-22T13:26:33x", // extra at end - } { - if _, err := ParseLocalDateTime(str); err == nil { - t.Errorf("ParseLocalDateTime(%q) succeeded, want error", str) - } - } -} - -func TestDateTimeOf(t *testing.T) { - for _, test := range []struct { - time time.Time - want LocalDateTime - }{ - {time.Date(2014, 8, 20, 15, 8, 43, 1, time.Local), - LocalDateTime{LocalDate{2014, 8, 20}, LocalTime{15, 8, 43, 1}}}, - {time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC), - LocalDateTime{LocalDate{1, 1, 1}, LocalTime{0, 0, 0, 0}}}, - } { - if got := LocalDateTimeOf(test.time); got != test.want { - t.Errorf("LocalDateTimeOf(%v) = %+v, want %+v", test.time, got, test.want) - } - } -} - -func TestDateTimeIsValid(t *testing.T) { - // No need to be exhaustive here; it's just LocalDate.IsValid && LocalTime.IsValid. - for _, test := range []struct { - dt LocalDateTime - want bool - }{ - {LocalDateTime{LocalDate{2016, 3, 20}, LocalTime{0, 0, 0, 0}}, true}, - {LocalDateTime{LocalDate{2016, -3, 20}, LocalTime{0, 0, 0, 0}}, false}, - {LocalDateTime{LocalDate{2016, 3, 20}, LocalTime{24, 0, 0, 0}}, false}, - } { - got := test.dt.IsValid() - if got != test.want { - t.Errorf("%#v: got %t, want %t", test.dt, got, test.want) - } - } -} - -func TestDateTimeIn(t *testing.T) { - dt := LocalDateTime{LocalDate{2016, 1, 2}, LocalTime{3, 4, 5, 6}} - got := dt.In(time.UTC) - want := time.Date(2016, 1, 2, 3, 4, 5, 6, time.UTC) - if !got.Equal(want) { - t.Errorf("got %v, want %v", got, want) - } -} - -func TestDateTimeBefore(t *testing.T) { - d1 := LocalDate{2016, 12, 31} - d2 := LocalDate{2017, 1, 1} - t1 := LocalTime{5, 6, 7, 8} - t2 := LocalTime{5, 6, 7, 9} - for _, test := range []struct { - dt1, dt2 LocalDateTime - want bool - }{ - {LocalDateTime{d1, t1}, LocalDateTime{d2, t1}, true}, - {LocalDateTime{d1, t1}, LocalDateTime{d1, t2}, true}, - {LocalDateTime{d2, t1}, LocalDateTime{d1, t1}, false}, - {LocalDateTime{d2, t1}, LocalDateTime{d2, t1}, false}, - } { - if got := test.dt1.Before(test.dt2); got != test.want { - t.Errorf("%v.Before(%v): got %t, want %t", test.dt1, test.dt2, got, test.want) - } - } -} - -func TestDateTimeAfter(t *testing.T) { - d1 := LocalDate{2016, 12, 31} - d2 := LocalDate{2017, 1, 1} - t1 := LocalTime{5, 6, 7, 8} - t2 := LocalTime{5, 6, 7, 9} - for _, test := range []struct { - dt1, dt2 LocalDateTime - want bool - }{ - {LocalDateTime{d1, t1}, LocalDateTime{d2, t1}, false}, - {LocalDateTime{d1, t1}, LocalDateTime{d1, t2}, false}, - {LocalDateTime{d2, t1}, LocalDateTime{d1, t1}, true}, - {LocalDateTime{d2, t1}, LocalDateTime{d2, t1}, false}, - } { - if got := test.dt1.After(test.dt2); got != test.want { - t.Errorf("%v.After(%v): got %t, want %t", test.dt1, test.dt2, got, test.want) - } - } -} - -func TestMarshalJSON(t *testing.T) { - for _, test := range []struct { - value interface{} - want string - }{ - {LocalDate{1987, 4, 15}, `"1987-04-15"`}, - {LocalTime{18, 54, 2, 0}, `"18:54:02"`}, - {LocalDateTime{LocalDate{1987, 4, 15}, LocalTime{18, 54, 2, 0}}, `"1987-04-15T18:54:02"`}, - } { - bgot, err := json.Marshal(test.value) - if err != nil { - t.Fatal(err) - } - if got := string(bgot); got != test.want { - t.Errorf("%#v: got %s, want %s", test.value, got, test.want) - } - } -} - -func TestUnmarshalJSON(t *testing.T) { - var d LocalDate - var tm LocalTime - var dt LocalDateTime - for _, test := range []struct { - data string - ptr interface{} - want interface{} - }{ - {`"1987-04-15"`, &d, &LocalDate{1987, 4, 15}}, - {`"1987-04-\u0031\u0035"`, &d, &LocalDate{1987, 4, 15}}, - {`"18:54:02"`, &tm, &LocalTime{18, 54, 2, 0}}, - {`"1987-04-15T18:54:02"`, &dt, &LocalDateTime{LocalDate{1987, 4, 15}, LocalTime{18, 54, 2, 0}}}, - } { - if err := json.Unmarshal([]byte(test.data), test.ptr); err != nil { - t.Fatalf("%s: %v", test.data, err) - } - if !cmpEqual(test.ptr, test.want) { - t.Errorf("%s: got %#v, want %#v", test.data, test.ptr, test.want) - } - } - - for _, bad := range []string{"", `""`, `"bad"`, `"1987-04-15x"`, - `19870415`, // a JSON number - `11987-04-15x`, // not a JSON string - - } { - if json.Unmarshal([]byte(bad), &d) == nil { - t.Errorf("%q, LocalDate: got nil, want error", bad) - } - if json.Unmarshal([]byte(bad), &tm) == nil { - t.Errorf("%q, LocalTime: got nil, want error", bad) - } - if json.Unmarshal([]byte(bad), &dt) == nil { - t.Errorf("%q, LocalDateTime: got nil, want error", bad) - } - } -} diff --git a/marshal.go b/marshal.go deleted file mode 100644 index 365543a1..00000000 --- a/marshal.go +++ /dev/null @@ -1,1293 +0,0 @@ -package toml - -import ( - "bytes" - "encoding" - "errors" - "fmt" - "io" - "reflect" - "sort" - "strconv" - "strings" - "time" -) - -const ( - tagFieldName = "toml" - tagFieldComment = "comment" - tagCommented = "commented" - tagMultiline = "multiline" - tagDefault = "default" -) - -type tomlOpts struct { - name string - nameFromTag bool - comment string - commented bool - multiline bool - include bool - omitempty bool - defaultValue string -} - -type encOpts struct { - quoteMapKeys bool - arraysOneElementPerLine bool -} - -var encOptsDefaults = encOpts{ - quoteMapKeys: false, -} - -type annotation struct { - tag string - comment string - commented string - multiline string - defaultValue string -} - -var annotationDefault = annotation{ - tag: tagFieldName, - comment: tagFieldComment, - commented: tagCommented, - multiline: tagMultiline, - defaultValue: tagDefault, -} - -type marshalOrder int - -// Orders the Encoder can write the fields to the output stream. -const ( - // Sort fields alphabetically. - OrderAlphabetical marshalOrder = iota + 1 - // Preserve the order the fields are encountered. For example, the order of fields in - // a struct. - OrderPreserve -) - -var timeType = reflect.TypeOf(time.Time{}) -var marshalerType = reflect.TypeOf(new(Marshaler)).Elem() -var unmarshalerType = reflect.TypeOf(new(Unmarshaler)).Elem() -var textMarshalerType = reflect.TypeOf(new(encoding.TextMarshaler)).Elem() -var textUnmarshalerType = reflect.TypeOf(new(encoding.TextUnmarshaler)).Elem() -var localDateType = reflect.TypeOf(LocalDate{}) -var localTimeType = reflect.TypeOf(LocalTime{}) -var localDateTimeType = reflect.TypeOf(LocalDateTime{}) -var mapStringInterfaceType = reflect.TypeOf(map[string]interface{}{}) - -// Check if the given marshal type maps to a Tree primitive -func isPrimitive(mtype reflect.Type) bool { - switch mtype.Kind() { - case reflect.Ptr: - return isPrimitive(mtype.Elem()) - case reflect.Bool: - return true - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return true - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return true - case reflect.Float32, reflect.Float64: - return true - case reflect.String: - return true - case reflect.Struct: - return isTimeType(mtype) - default: - return false - } -} - -func isTimeType(mtype reflect.Type) bool { - return mtype == timeType || mtype == localDateType || mtype == localDateTimeType || mtype == localTimeType -} - -// Check if the given marshal type maps to a Tree slice or array -func isTreeSequence(mtype reflect.Type) bool { - switch mtype.Kind() { - case reflect.Ptr: - return isTreeSequence(mtype.Elem()) - case reflect.Slice, reflect.Array: - return isTree(mtype.Elem()) - default: - return false - } -} - -// Check if the given marshal type maps to a slice or array of a custom marshaler type -func isCustomMarshalerSequence(mtype reflect.Type) bool { - switch mtype.Kind() { - case reflect.Ptr: - return isCustomMarshalerSequence(mtype.Elem()) - case reflect.Slice, reflect.Array: - return isCustomMarshaler(mtype.Elem()) || isCustomMarshaler(reflect.New(mtype.Elem()).Type()) - default: - return false - } -} - -// Check if the given marshal type maps to a slice or array of a text marshaler type -func isTextMarshalerSequence(mtype reflect.Type) bool { - switch mtype.Kind() { - case reflect.Ptr: - return isTextMarshalerSequence(mtype.Elem()) - case reflect.Slice, reflect.Array: - return isTextMarshaler(mtype.Elem()) || isTextMarshaler(reflect.New(mtype.Elem()).Type()) - default: - return false - } -} - -// Check if the given marshal type maps to a non-Tree slice or array -func isOtherSequence(mtype reflect.Type) bool { - switch mtype.Kind() { - case reflect.Ptr: - return isOtherSequence(mtype.Elem()) - case reflect.Slice, reflect.Array: - return !isTreeSequence(mtype) - default: - return false - } -} - -// Check if the given marshal type maps to a Tree -func isTree(mtype reflect.Type) bool { - switch mtype.Kind() { - case reflect.Ptr: - return isTree(mtype.Elem()) - case reflect.Map: - return true - case reflect.Struct: - return !isPrimitive(mtype) - default: - return false - } -} - -func isCustomMarshaler(mtype reflect.Type) bool { - return mtype.Implements(marshalerType) -} - -func callCustomMarshaler(mval reflect.Value) ([]byte, error) { - return mval.Interface().(Marshaler).MarshalTOML() -} - -func isTextMarshaler(mtype reflect.Type) bool { - return mtype.Implements(textMarshalerType) && !isTimeType(mtype) -} - -func callTextMarshaler(mval reflect.Value) ([]byte, error) { - return mval.Interface().(encoding.TextMarshaler).MarshalText() -} - -func isCustomUnmarshaler(mtype reflect.Type) bool { - return mtype.Implements(unmarshalerType) -} - -func callCustomUnmarshaler(mval reflect.Value, tval interface{}) error { - return mval.Interface().(Unmarshaler).UnmarshalTOML(tval) -} - -func isTextUnmarshaler(mtype reflect.Type) bool { - return mtype.Implements(textUnmarshalerType) -} - -func callTextUnmarshaler(mval reflect.Value, text []byte) error { - return mval.Interface().(encoding.TextUnmarshaler).UnmarshalText(text) -} - -// Marshaler is the interface implemented by types that -// can marshal themselves into valid TOML. -type Marshaler interface { - MarshalTOML() ([]byte, error) -} - -// Unmarshaler is the interface implemented by types that -// can unmarshal a TOML description of themselves. -type Unmarshaler interface { - UnmarshalTOML(interface{}) error -} - -/* -Marshal returns the TOML encoding of v. Behavior is similar to the Go json -encoder, except that there is no concept of a Marshaler interface or MarshalTOML -function for sub-structs, and currently only definite types can be marshaled -(i.e. no `interface{}`). - -The following struct annotations are supported: - - toml:"Field" Overrides the field's name to output. - omitempty When set, empty values and groups are not emitted. - comment:"comment" Emits a # comment on the same line. This supports new lines. - commented:"true" Emits the value as commented. - -Note that pointers are automatically assigned the "omitempty" option, as TOML -explicitly does not handle null values (saying instead the label should be -dropped). - -Tree structural types and corresponding marshal types: - - *Tree (*)struct, (*)map[string]interface{} - []*Tree (*)[](*)struct, (*)[](*)map[string]interface{} - []interface{} (as interface{}) (*)[]primitive, (*)[]([]interface{}) - interface{} (*)primitive - -Tree primitive types and corresponding marshal types: - - uint64 uint, uint8-uint64, pointers to same - int64 int, int8-uint64, pointers to same - float64 float32, float64, pointers to same - string string, pointers to same - bool bool, pointers to same - time.LocalTime time.LocalTime{}, pointers to same - -For additional flexibility, use the Encoder API. -*/ -func Marshal(v interface{}) ([]byte, error) { - return NewEncoder(nil).marshal(v) -} - -// Encoder writes TOML values to an output stream. -type Encoder struct { - w io.Writer - encOpts - annotation - line int - col int - order marshalOrder - promoteAnon bool - indentation string -} - -// NewEncoder returns a new encoder that writes to w. -func NewEncoder(w io.Writer) *Encoder { - return &Encoder{ - w: w, - encOpts: encOptsDefaults, - annotation: annotationDefault, - line: 0, - col: 1, - order: OrderAlphabetical, - indentation: " ", - } -} - -// Encode writes the TOML encoding of v to the stream. -// -// See the documentation for Marshal for details. -func (e *Encoder) Encode(v interface{}) error { - b, err := e.marshal(v) - if err != nil { - return err - } - if _, err := e.w.Write(b); err != nil { - return err - } - return nil -} - -// QuoteMapKeys sets up the encoder to encode -// maps with string type keys with quoted TOML keys. -// -// This relieves the character limitations on map keys. -func (e *Encoder) QuoteMapKeys(v bool) *Encoder { - e.quoteMapKeys = v - return e -} - -// ArraysWithOneElementPerLine sets up the encoder to encode arrays -// with more than one element on multiple lines instead of one. -// -// For example: -// -// A = [1,2,3] -// -// Becomes -// -// A = [ -// 1, -// 2, -// 3, -// ] -func (e *Encoder) ArraysWithOneElementPerLine(v bool) *Encoder { - e.arraysOneElementPerLine = v - return e -} - -// Order allows to change in which order fields will be written to the output stream. -func (e *Encoder) Order(ord marshalOrder) *Encoder { - e.order = ord - return e -} - -// Indentation allows to change indentation when marshalling. -func (e *Encoder) Indentation(indent string) *Encoder { - e.indentation = indent - return e -} - -// SetTagName allows changing default tag "toml" -func (e *Encoder) SetTagName(v string) *Encoder { - e.tag = v - return e -} - -// SetTagComment allows changing default tag "comment" -func (e *Encoder) SetTagComment(v string) *Encoder { - e.comment = v - return e -} - -// SetTagCommented allows changing default tag "commented" -func (e *Encoder) SetTagCommented(v string) *Encoder { - e.commented = v - return e -} - -// SetTagMultiline allows changing default tag "multiline" -func (e *Encoder) SetTagMultiline(v string) *Encoder { - e.multiline = v - return e -} - -// PromoteAnonymous allows to change how anonymous struct fields are marshaled. -// Usually, they are marshaled as if the inner exported fields were fields in -// the outer struct. However, if an anonymous struct field is given a name in -// its TOML tag, it is treated like a regular struct field with that name. -// rather than being anonymous. -// -// In case anonymous promotion is enabled, all anonymous structs are promoted -// and treated like regular struct fields. -func (e *Encoder) PromoteAnonymous(promote bool) *Encoder { - e.promoteAnon = promote - return e -} - -func (e *Encoder) marshal(v interface{}) ([]byte, error) { - // Check if indentation is valid - for _, char := range e.indentation { - if !isSpace(char) { - return []byte{}, fmt.Errorf("invalid indentation: must only contains space or tab characters") - } - } - - mtype := reflect.TypeOf(v) - if mtype == nil { - return []byte{}, errors.New("nil cannot be marshaled to TOML") - } - - switch mtype.Kind() { - case reflect.Struct, reflect.Map: - case reflect.Ptr: - if mtype.Elem().Kind() != reflect.Struct { - return []byte{}, errors.New("Only pointer to struct can be marshaled to TOML") - } - if reflect.ValueOf(v).IsNil() { - return []byte{}, errors.New("nil pointer cannot be marshaled to TOML") - } - default: - return []byte{}, errors.New("Only a struct or map can be marshaled to TOML") - } - - sval := reflect.ValueOf(v) - if isCustomMarshaler(mtype) { - return callCustomMarshaler(sval) - } - if isTextMarshaler(mtype) { - return callTextMarshaler(sval) - } - t, err := e.valueToTree(mtype, sval) - if err != nil { - return []byte{}, err - } - - var buf bytes.Buffer - _, err = t.writeToOrdered(&buf, "", "", 0, e.arraysOneElementPerLine, e.order, e.indentation, false) - - return buf.Bytes(), err -} - -// Create next tree with a position based on Encoder.line -func (e *Encoder) nextTree() *Tree { - return newTreeWithPosition(Position{Line: e.line, Col: 1}) -} - -// Convert given marshal struct or map value to toml tree -func (e *Encoder) valueToTree(mtype reflect.Type, mval reflect.Value) (*Tree, error) { - if mtype.Kind() == reflect.Ptr { - return e.valueToTree(mtype.Elem(), mval.Elem()) - } - tval := e.nextTree() - switch mtype.Kind() { - case reflect.Struct: - switch mval.Interface().(type) { - case Tree: - reflect.ValueOf(tval).Elem().Set(mval) - default: - for i := 0; i < mtype.NumField(); i++ { - mtypef, mvalf := mtype.Field(i), mval.Field(i) - opts := tomlOptions(mtypef, e.annotation) - if opts.include && ((mtypef.Type.Kind() != reflect.Interface && !opts.omitempty) || !isZero(mvalf)) { - val, err := e.valueToToml(mtypef.Type, mvalf) - if err != nil { - return nil, err - } - if tree, ok := val.(*Tree); ok && mtypef.Anonymous && !opts.nameFromTag && !e.promoteAnon { - e.appendTree(tval, tree) - } else { - val = e.wrapTomlValue(val, tval) - tval.SetPathWithOptions([]string{opts.name}, SetOptions{ - Comment: opts.comment, - Commented: opts.commented, - Multiline: opts.multiline, - }, val) - } - } - } - } - case reflect.Map: - keys := mval.MapKeys() - if e.order == OrderPreserve && len(keys) > 0 { - // Sorting []reflect.Value is not straight forward. - // - // OrderPreserve will support deterministic results when string is used - // as the key to maps. - typ := keys[0].Type() - kind := keys[0].Kind() - if kind == reflect.String { - ikeys := make([]string, len(keys)) - for i := range keys { - ikeys[i] = keys[i].Interface().(string) - } - sort.Strings(ikeys) - for i := range ikeys { - keys[i] = reflect.ValueOf(ikeys[i]).Convert(typ) - } - } - } - for _, key := range keys { - mvalf := mval.MapIndex(key) - if (mtype.Elem().Kind() == reflect.Ptr || mtype.Elem().Kind() == reflect.Interface) && mvalf.IsNil() { - continue - } - val, err := e.valueToToml(mtype.Elem(), mvalf) - if err != nil { - return nil, err - } - val = e.wrapTomlValue(val, tval) - if e.quoteMapKeys { - keyStr, err := tomlValueStringRepresentation(key.String(), "", "", e.order, e.arraysOneElementPerLine) - if err != nil { - return nil, err - } - tval.SetPath([]string{keyStr}, val) - } else { - tval.SetPath([]string{key.String()}, val) - } - } - } - return tval, nil -} - -// Convert given marshal slice to slice of Toml trees -func (e *Encoder) valueToTreeSlice(mtype reflect.Type, mval reflect.Value) ([]*Tree, error) { - tval := make([]*Tree, mval.Len(), mval.Len()) - for i := 0; i < mval.Len(); i++ { - val, err := e.valueToTree(mtype.Elem(), mval.Index(i)) - if err != nil { - return nil, err - } - tval[i] = val - } - return tval, nil -} - -// Convert given marshal slice to slice of toml values -func (e *Encoder) valueToOtherSlice(mtype reflect.Type, mval reflect.Value) (interface{}, error) { - tval := make([]interface{}, mval.Len(), mval.Len()) - for i := 0; i < mval.Len(); i++ { - val, err := e.valueToToml(mtype.Elem(), mval.Index(i)) - if err != nil { - return nil, err - } - tval[i] = val - } - return tval, nil -} - -// Convert given marshal value to toml value -func (e *Encoder) valueToToml(mtype reflect.Type, mval reflect.Value) (interface{}, error) { - if mtype.Kind() == reflect.Ptr { - switch { - case isCustomMarshaler(mtype): - return callCustomMarshaler(mval) - case isTextMarshaler(mtype): - b, err := callTextMarshaler(mval) - return string(b), err - default: - return e.valueToToml(mtype.Elem(), mval.Elem()) - } - } - if mtype.Kind() == reflect.Interface { - return e.valueToToml(mval.Elem().Type(), mval.Elem()) - } - switch { - case isCustomMarshaler(mtype): - return callCustomMarshaler(mval) - case isTextMarshaler(mtype): - b, err := callTextMarshaler(mval) - return string(b), err - case isTree(mtype): - return e.valueToTree(mtype, mval) - case isOtherSequence(mtype), isCustomMarshalerSequence(mtype), isTextMarshalerSequence(mtype): - return e.valueToOtherSlice(mtype, mval) - case isTreeSequence(mtype): - return e.valueToTreeSlice(mtype, mval) - default: - switch mtype.Kind() { - case reflect.Bool: - return mval.Bool(), nil - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - if mtype.Kind() == reflect.Int64 && mtype == reflect.TypeOf(time.Duration(1)) { - return fmt.Sprint(mval), nil - } - return mval.Int(), nil - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return mval.Uint(), nil - case reflect.Float32, reflect.Float64: - return mval.Float(), nil - case reflect.String: - return mval.String(), nil - case reflect.Struct: - return mval.Interface(), nil - default: - return nil, fmt.Errorf("Marshal can't handle %v(%v)", mtype, mtype.Kind()) - } - } -} - -func (e *Encoder) appendTree(t, o *Tree) error { - for key, value := range o.values { - if _, ok := t.values[key]; ok { - continue - } - if tomlValue, ok := value.(*tomlValue); ok { - tomlValue.position.Col = t.position.Col - } - t.values[key] = value - } - return nil -} - -// Create a toml value with the current line number as the position line -func (e *Encoder) wrapTomlValue(val interface{}, parent *Tree) interface{} { - _, isTree := val.(*Tree) - _, isTreeS := val.([]*Tree) - if isTree || isTreeS { - return val - } - - ret := &tomlValue{ - value: val, - position: Position{ - e.line, - parent.position.Col, - }, - } - e.line++ - return ret -} - -// Unmarshal attempts to unmarshal the Tree into a Go struct pointed by v. -// Neither Unmarshaler interfaces nor UnmarshalTOML functions are supported for -// sub-structs, and only definite types can be unmarshaled. -func (t *Tree) Unmarshal(v interface{}) error { - d := Decoder{tval: t, tagName: tagFieldName} - return d.unmarshal(v) -} - -// Marshal returns the TOML encoding of Tree. -// See Marshal() documentation for types mapping table. -func (t *Tree) Marshal() ([]byte, error) { - var buf bytes.Buffer - _, err := t.WriteTo(&buf) - if err != nil { - return nil, err - } - return buf.Bytes(), nil -} - -// Unmarshal parses the TOML-encoded data and stores the result in the value -// pointed to by v. Behavior is similar to the Go json encoder, except that there -// is no concept of an Unmarshaler interface or UnmarshalTOML function for -// sub-structs, and currently only definite types can be unmarshaled to (i.e. no -// `interface{}`). -// -// The following struct annotations are supported: -// -// toml:"Field" Overrides the field's name to map to. -// default:"foo" Provides a default value. -// -// For default values, only fields of the following types are supported: -// * string -// * bool -// * int -// * int64 -// * float64 -// -// See Marshal() documentation for types mapping table. -func Unmarshal(data []byte, v interface{}) error { - t, err := LoadReader(bytes.NewReader(data)) - if err != nil { - return err - } - return t.Unmarshal(v) -} - -// Decoder reads and decodes TOML values from an input stream. -type Decoder struct { - r io.Reader - tval *Tree - encOpts - tagName string - strict bool - visitor visitorState -} - -// NewDecoder returns a new decoder that reads from r. -func NewDecoder(r io.Reader) *Decoder { - return &Decoder{ - r: r, - encOpts: encOptsDefaults, - tagName: tagFieldName, - } -} - -// Decode reads a TOML-encoded value from it's input -// and unmarshals it in the value pointed at by v. -// -// See the documentation for Marshal for details. -func (d *Decoder) Decode(v interface{}) error { - var err error - d.tval, err = LoadReader(d.r) - if err != nil { - return err - } - return d.unmarshal(v) -} - -// SetTagName allows changing default tag "toml" -func (d *Decoder) SetTagName(v string) *Decoder { - d.tagName = v - return d -} - -// Strict allows changing to strict decoding. Any fields that are found in the -// input data and do not have a corresponding struct member cause an error. -func (d *Decoder) Strict(strict bool) *Decoder { - d.strict = strict - return d -} - -func (d *Decoder) unmarshal(v interface{}) error { - mtype := reflect.TypeOf(v) - if mtype == nil { - return errors.New("nil cannot be unmarshaled from TOML") - } - if mtype.Kind() != reflect.Ptr { - return errors.New("only a pointer to struct or map can be unmarshaled from TOML") - } - - elem := mtype.Elem() - - switch elem.Kind() { - case reflect.Struct, reflect.Map: - case reflect.Interface: - elem = mapStringInterfaceType - default: - return errors.New("only a pointer to struct or map can be unmarshaled from TOML") - } - - if reflect.ValueOf(v).IsNil() { - return errors.New("nil pointer cannot be unmarshaled from TOML") - } - - vv := reflect.ValueOf(v).Elem() - - if d.strict { - d.visitor = newVisitorState(d.tval) - } - - sval, err := d.valueFromTree(elem, d.tval, &vv) - if err != nil { - return err - } - if err := d.visitor.validate(); err != nil { - return err - } - reflect.ValueOf(v).Elem().Set(sval) - return nil -} - -// Convert toml tree to marshal struct or map, using marshal type. When mval1 -// is non-nil, merge fields into the given value instead of allocating a new one. -func (d *Decoder) valueFromTree(mtype reflect.Type, tval *Tree, mval1 *reflect.Value) (reflect.Value, error) { - if mtype.Kind() == reflect.Ptr { - return d.unwrapPointer(mtype, tval, mval1) - } - - // Check if pointer to value implements the Unmarshaler interface. - if mvalPtr := reflect.New(mtype); isCustomUnmarshaler(mvalPtr.Type()) { - d.visitor.visitAll() - - if tval == nil { - return mvalPtr.Elem(), nil - } - - if err := callCustomUnmarshaler(mvalPtr, tval.ToMap()); err != nil { - return reflect.ValueOf(nil), fmt.Errorf("unmarshal toml: %v", err) - } - return mvalPtr.Elem(), nil - } - - var mval reflect.Value - switch mtype.Kind() { - case reflect.Struct: - if mval1 != nil { - mval = *mval1 - } else { - mval = reflect.New(mtype).Elem() - } - - switch mval.Interface().(type) { - case Tree: - mval.Set(reflect.ValueOf(tval).Elem()) - default: - for i := 0; i < mtype.NumField(); i++ { - mtypef := mtype.Field(i) - an := annotation{tag: d.tagName} - opts := tomlOptions(mtypef, an) - if !opts.include { - continue - } - baseKey := opts.name - keysToTry := []string{ - baseKey, - strings.ToLower(baseKey), - strings.ToTitle(baseKey), - strings.ToLower(string(baseKey[0])) + baseKey[1:], - } - - found := false - if tval != nil { - for _, key := range keysToTry { - exists := tval.HasPath([]string{key}) - if !exists { - continue - } - - d.visitor.push(key) - val := tval.GetPath([]string{key}) - fval := mval.Field(i) - mvalf, err := d.valueFromToml(mtypef.Type, val, &fval) - if err != nil { - return mval, formatError(err, tval.GetPositionPath([]string{key})) - } - mval.Field(i).Set(mvalf) - found = true - d.visitor.pop() - break - } - } - - if !found && opts.defaultValue != "" { - mvalf := mval.Field(i) - var val interface{} - var err error - switch mvalf.Kind() { - case reflect.String: - val = opts.defaultValue - case reflect.Bool: - val, err = strconv.ParseBool(opts.defaultValue) - case reflect.Uint: - val, err = strconv.ParseUint(opts.defaultValue, 10, 0) - case reflect.Uint8: - val, err = strconv.ParseUint(opts.defaultValue, 10, 8) - case reflect.Uint16: - val, err = strconv.ParseUint(opts.defaultValue, 10, 16) - case reflect.Uint32: - val, err = strconv.ParseUint(opts.defaultValue, 10, 32) - case reflect.Uint64: - val, err = strconv.ParseUint(opts.defaultValue, 10, 64) - case reflect.Int: - val, err = strconv.ParseInt(opts.defaultValue, 10, 0) - case reflect.Int8: - val, err = strconv.ParseInt(opts.defaultValue, 10, 8) - case reflect.Int16: - val, err = strconv.ParseInt(opts.defaultValue, 10, 16) - case reflect.Int32: - val, err = strconv.ParseInt(opts.defaultValue, 10, 32) - case reflect.Int64: - // Check if the provided number has a non-numeric extension. - var hasExtension bool - if len(opts.defaultValue) > 0 { - lastChar := opts.defaultValue[len(opts.defaultValue)-1] - if lastChar < '0' || lastChar > '9' { - hasExtension = true - } - } - // If the value is a time.Duration with extension, parse as duration. - // If the value is an int64 or a time.Duration without extension, parse as number. - if hasExtension && mvalf.Type().String() == "time.Duration" { - val, err = time.ParseDuration(opts.defaultValue) - } else { - val, err = strconv.ParseInt(opts.defaultValue, 10, 64) - } - case reflect.Float32: - val, err = strconv.ParseFloat(opts.defaultValue, 32) - case reflect.Float64: - val, err = strconv.ParseFloat(opts.defaultValue, 64) - default: - return mvalf, fmt.Errorf("unsupported field type for default option") - } - - if err != nil { - return mvalf, err - } - mvalf.Set(reflect.ValueOf(val).Convert(mvalf.Type())) - } - - // save the old behavior above and try to check structs - if !found && opts.defaultValue == "" && mtypef.Type.Kind() == reflect.Struct { - tmpTval := tval - if !mtypef.Anonymous { - tmpTval = nil - } - fval := mval.Field(i) - v, err := d.valueFromTree(mtypef.Type, tmpTval, &fval) - if err != nil { - return v, err - } - mval.Field(i).Set(v) - } - } - } - case reflect.Map: - mval = reflect.MakeMap(mtype) - for _, key := range tval.Keys() { - d.visitor.push(key) - // TODO: path splits key - val := tval.GetPath([]string{key}) - mvalf, err := d.valueFromToml(mtype.Elem(), val, nil) - if err != nil { - return mval, formatError(err, tval.GetPositionPath([]string{key})) - } - mval.SetMapIndex(reflect.ValueOf(key).Convert(mtype.Key()), mvalf) - d.visitor.pop() - } - } - return mval, nil -} - -// Convert toml value to marshal struct/map slice, using marshal type -func (d *Decoder) valueFromTreeSlice(mtype reflect.Type, tval []*Tree) (reflect.Value, error) { - mval, err := makeSliceOrArray(mtype, len(tval)) - if err != nil { - return mval, err - } - - for i := 0; i < len(tval); i++ { - d.visitor.push(strconv.Itoa(i)) - val, err := d.valueFromTree(mtype.Elem(), tval[i], nil) - if err != nil { - return mval, err - } - mval.Index(i).Set(val) - d.visitor.pop() - } - return mval, nil -} - -// Convert toml value to marshal primitive slice, using marshal type -func (d *Decoder) valueFromOtherSlice(mtype reflect.Type, tval []interface{}) (reflect.Value, error) { - mval, err := makeSliceOrArray(mtype, len(tval)) - if err != nil { - return mval, err - } - - for i := 0; i < len(tval); i++ { - val, err := d.valueFromToml(mtype.Elem(), tval[i], nil) - if err != nil { - return mval, err - } - mval.Index(i).Set(val) - } - return mval, nil -} - -// Convert toml value to marshal primitive slice, using marshal type -func (d *Decoder) valueFromOtherSliceI(mtype reflect.Type, tval interface{}) (reflect.Value, error) { - val := reflect.ValueOf(tval) - length := val.Len() - - mval, err := makeSliceOrArray(mtype, length) - if err != nil { - return mval, err - } - - for i := 0; i < length; i++ { - val, err := d.valueFromToml(mtype.Elem(), val.Index(i).Interface(), nil) - if err != nil { - return mval, err - } - mval.Index(i).Set(val) - } - return mval, nil -} - -// Create a new slice or a new array with specified length -func makeSliceOrArray(mtype reflect.Type, tLength int) (reflect.Value, error) { - var mval reflect.Value - switch mtype.Kind() { - case reflect.Slice: - mval = reflect.MakeSlice(mtype, tLength, tLength) - case reflect.Array: - mval = reflect.New(reflect.ArrayOf(mtype.Len(), mtype.Elem())).Elem() - if tLength > mtype.Len() { - return mval, fmt.Errorf("unmarshal: TOML array length (%v) exceeds destination array length (%v)", tLength, mtype.Len()) - } - } - return mval, nil -} - -// Convert toml value to marshal value, using marshal type. When mval1 is non-nil -// and the given type is a struct value, merge fields into it. -func (d *Decoder) valueFromToml(mtype reflect.Type, tval interface{}, mval1 *reflect.Value) (reflect.Value, error) { - if mtype.Kind() == reflect.Ptr { - return d.unwrapPointer(mtype, tval, mval1) - } - - switch t := tval.(type) { - case *Tree: - var mval11 *reflect.Value - if mtype.Kind() == reflect.Struct { - mval11 = mval1 - } - - if isTree(mtype) { - return d.valueFromTree(mtype, t, mval11) - } - - if mtype.Kind() == reflect.Interface { - if mval1 == nil || mval1.IsNil() { - return d.valueFromTree(reflect.TypeOf(map[string]interface{}{}), t, nil) - } else { - return d.valueFromToml(mval1.Elem().Type(), t, nil) - } - } - - return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to a tree", tval, tval) - case []*Tree: - if isTreeSequence(mtype) { - return d.valueFromTreeSlice(mtype, t) - } - if mtype.Kind() == reflect.Interface { - if mval1 == nil || mval1.IsNil() { - return d.valueFromTreeSlice(reflect.TypeOf([]map[string]interface{}{}), t) - } else { - ival := mval1.Elem() - return d.valueFromToml(mval1.Elem().Type(), t, &ival) - } - } - return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to trees", tval, tval) - case []interface{}: - d.visitor.visit() - if isOtherSequence(mtype) { - return d.valueFromOtherSlice(mtype, t) - } - if mtype.Kind() == reflect.Interface { - if mval1 == nil || mval1.IsNil() { - return d.valueFromOtherSlice(reflect.TypeOf([]interface{}{}), t) - } else { - ival := mval1.Elem() - return d.valueFromToml(mval1.Elem().Type(), t, &ival) - } - } - return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to a slice", tval, tval) - default: - d.visitor.visit() - mvalPtr := reflect.New(mtype) - - // Check if pointer to value implements the Unmarshaler interface. - if isCustomUnmarshaler(mvalPtr.Type()) { - if err := callCustomUnmarshaler(mvalPtr, tval); err != nil { - return reflect.ValueOf(nil), fmt.Errorf("unmarshal toml: %v", err) - } - return mvalPtr.Elem(), nil - } - - // Check if pointer to value implements the encoding.TextUnmarshaler. - if isTextUnmarshaler(mvalPtr.Type()) && !isTimeType(mtype) { - if err := d.unmarshalText(tval, mvalPtr); err != nil { - return reflect.ValueOf(nil), fmt.Errorf("unmarshal text: %v", err) - } - return mvalPtr.Elem(), nil - } - - switch mtype.Kind() { - case reflect.Bool, reflect.Struct: - val := reflect.ValueOf(tval) - - switch val.Type() { - case localDateType: - localDate := val.Interface().(LocalDate) - switch mtype { - case timeType: - return reflect.ValueOf(time.Date(localDate.Year, localDate.Month, localDate.Day, 0, 0, 0, 0, time.Local)), nil - } - case localDateTimeType: - localDateTime := val.Interface().(LocalDateTime) - switch mtype { - case timeType: - return reflect.ValueOf(time.Date( - localDateTime.Date.Year, - localDateTime.Date.Month, - localDateTime.Date.Day, - localDateTime.Time.Hour, - localDateTime.Time.Minute, - localDateTime.Time.Second, - localDateTime.Time.Nanosecond, - time.Local)), nil - } - } - - // if this passes for when mtype is reflect.Struct, tval is a time.LocalTime - if !val.Type().ConvertibleTo(mtype) { - return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to %v", tval, tval, mtype.String()) - } - - return val.Convert(mtype), nil - case reflect.String: - val := reflect.ValueOf(tval) - // stupidly, int64 is convertible to string. So special case this. - if !val.Type().ConvertibleTo(mtype) || val.Kind() == reflect.Int64 { - return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to %v", tval, tval, mtype.String()) - } - - return val.Convert(mtype), nil - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - val := reflect.ValueOf(tval) - if mtype.Kind() == reflect.Int64 && mtype == reflect.TypeOf(time.Duration(1)) && val.Kind() == reflect.String { - d, err := time.ParseDuration(val.String()) - if err != nil { - return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to %v. %s", tval, tval, mtype.String(), err) - } - return reflect.ValueOf(d), nil - } - if !val.Type().ConvertibleTo(mtype) || val.Kind() == reflect.Float64 { - return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to %v", tval, tval, mtype.String()) - } - if reflect.Indirect(reflect.New(mtype)).OverflowInt(val.Convert(reflect.TypeOf(int64(0))).Int()) { - return reflect.ValueOf(nil), fmt.Errorf("%v(%T) would overflow %v", tval, tval, mtype.String()) - } - - return val.Convert(mtype), nil - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - val := reflect.ValueOf(tval) - if !val.Type().ConvertibleTo(mtype) || val.Kind() == reflect.Float64 { - return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to %v", tval, tval, mtype.String()) - } - - if val.Convert(reflect.TypeOf(int(1))).Int() < 0 { - return reflect.ValueOf(nil), fmt.Errorf("%v(%T) is negative so does not fit in %v", tval, tval, mtype.String()) - } - if reflect.Indirect(reflect.New(mtype)).OverflowUint(val.Convert(reflect.TypeOf(uint64(0))).Uint()) { - return reflect.ValueOf(nil), fmt.Errorf("%v(%T) would overflow %v", tval, tval, mtype.String()) - } - - return val.Convert(mtype), nil - case reflect.Float32, reflect.Float64: - val := reflect.ValueOf(tval) - if !val.Type().ConvertibleTo(mtype) || val.Kind() == reflect.Int64 { - return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to %v", tval, tval, mtype.String()) - } - if reflect.Indirect(reflect.New(mtype)).OverflowFloat(val.Convert(reflect.TypeOf(float64(0))).Float()) { - return reflect.ValueOf(nil), fmt.Errorf("%v(%T) would overflow %v", tval, tval, mtype.String()) - } - - return val.Convert(mtype), nil - case reflect.Interface: - if mval1 == nil || mval1.IsNil() { - return reflect.ValueOf(tval), nil - } else { - ival := mval1.Elem() - return d.valueFromToml(mval1.Elem().Type(), t, &ival) - } - case reflect.Slice, reflect.Array: - if isOtherSequence(mtype) && isOtherSequence(reflect.TypeOf(t)) { - return d.valueFromOtherSliceI(mtype, t) - } - return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to %v(%v)", tval, tval, mtype, mtype.Kind()) - default: - return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to %v(%v)", tval, tval, mtype, mtype.Kind()) - } - } -} - -func (d *Decoder) unwrapPointer(mtype reflect.Type, tval interface{}, mval1 *reflect.Value) (reflect.Value, error) { - var melem *reflect.Value - - if mval1 != nil && !mval1.IsNil() && (mtype.Elem().Kind() == reflect.Struct || mtype.Elem().Kind() == reflect.Interface) { - elem := mval1.Elem() - melem = &elem - } - - val, err := d.valueFromToml(mtype.Elem(), tval, melem) - if err != nil { - return reflect.ValueOf(nil), err - } - mval := reflect.New(mtype.Elem()) - mval.Elem().Set(val) - return mval, nil -} - -func (d *Decoder) unmarshalText(tval interface{}, mval reflect.Value) error { - var buf bytes.Buffer - fmt.Fprint(&buf, tval) - return callTextUnmarshaler(mval, buf.Bytes()) -} - -func tomlOptions(vf reflect.StructField, an annotation) tomlOpts { - tag := vf.Tag.Get(an.tag) - parse := strings.Split(tag, ",") - var comment string - if c := vf.Tag.Get(an.comment); c != "" { - comment = c - } - commented, _ := strconv.ParseBool(vf.Tag.Get(an.commented)) - multiline, _ := strconv.ParseBool(vf.Tag.Get(an.multiline)) - defaultValue := vf.Tag.Get(tagDefault) - result := tomlOpts{ - name: vf.Name, - nameFromTag: false, - comment: comment, - commented: commented, - multiline: multiline, - include: true, - omitempty: false, - defaultValue: defaultValue, - } - if parse[0] != "" { - if parse[0] == "-" && len(parse) == 1 { - result.include = false - } else { - result.name = strings.Trim(parse[0], " ") - result.nameFromTag = true - } - } - if vf.PkgPath != "" { - result.include = false - } - if len(parse) > 1 && strings.Trim(parse[1], " ") == "omitempty" { - result.omitempty = true - } - if vf.Type.Kind() == reflect.Ptr { - result.omitempty = true - } - return result -} - -func isZero(val reflect.Value) bool { - switch val.Type().Kind() { - case reflect.Slice, reflect.Array, reflect.Map: - return val.Len() == 0 - default: - return reflect.DeepEqual(val.Interface(), reflect.Zero(val.Type()).Interface()) - } -} - -func formatError(err error, pos Position) error { - if err.Error()[0] == '(' { // Error already contains position information - return err - } - return fmt.Errorf("%s: %s", pos, err) -} - -// visitorState keeps track of which keys were unmarshaled. -type visitorState struct { - tree *Tree - path []string - keys map[string]struct{} - active bool -} - -func newVisitorState(tree *Tree) visitorState { - path, result := []string{}, map[string]struct{}{} - insertKeys(path, result, tree) - return visitorState{ - tree: tree, - path: path[:0], - keys: result, - active: true, - } -} - -func (s *visitorState) push(key string) { - if s.active { - s.path = append(s.path, key) - } -} - -func (s *visitorState) pop() { - if s.active { - s.path = s.path[:len(s.path)-1] - } -} - -func (s *visitorState) visit() { - if s.active { - delete(s.keys, strings.Join(s.path, ".")) - } -} - -func (s *visitorState) visitAll() { - if s.active { - for k := range s.keys { - if strings.HasPrefix(k, strings.Join(s.path, ".")) { - delete(s.keys, k) - } - } - } -} - -func (s *visitorState) validate() error { - if !s.active { - return nil - } - undecoded := make([]string, 0, len(s.keys)) - for key := range s.keys { - undecoded = append(undecoded, key) - } - sort.Strings(undecoded) - if len(undecoded) > 0 { - return fmt.Errorf("undecoded keys: %q", undecoded) - } - return nil -} - -func insertKeys(path []string, m map[string]struct{}, tree *Tree) { - for k, v := range tree.values { - switch node := v.(type) { - case []*Tree: - for i, item := range node { - insertKeys(append(path, k, strconv.Itoa(i)), m, item) - } - case *Tree: - insertKeys(append(path, k), m, node) - case *tomlValue: - m[strings.Join(append(path, k), ".")] = struct{}{} - } - } -} diff --git a/marshal_OrderPreserve_test.toml b/marshal_OrderPreserve_test.toml deleted file mode 100644 index 792b72ed..00000000 --- a/marshal_OrderPreserve_test.toml +++ /dev/null @@ -1,39 +0,0 @@ -title = "TOML Marshal Testing" - -[basic_lists] - floats = [12.3,45.6,78.9] - bools = [true,false,true] - dates = [1979-05-27T07:32:00Z,1980-05-27T07:32:00Z] - ints = [8001,8001,8002] - uints = [5002,5003] - strings = ["One","Two","Three"] - -[[subdocptrs]] - name = "Second" - -[basic_map] - one = "one" - two = "two" - -[subdoc] - - [subdoc.second] - name = "Second" - - [subdoc.first] - name = "First" - -[basic] - uint = 5001 - bool = true - float = 123.4 - float64 = 123.456782132399 - int = 5000 - string = "Bite me" - date = 1979-05-27T07:32:00Z - -[[subdoclist]] - name = "List.First" - -[[subdoclist]] - name = "List.Second" diff --git a/marshal_test.go b/marshal_test.go deleted file mode 100644 index f948de4b..00000000 --- a/marshal_test.go +++ /dev/null @@ -1,4054 +0,0 @@ -package toml - -import ( - "bytes" - "encoding/json" - "errors" - "fmt" - "io/ioutil" - "os" - "reflect" - "strconv" - "strings" - "testing" - "time" -) - -type basicMarshalTestStruct struct { - String string `toml:"Zstring"` - StringList []string `toml:"Ystrlist"` - BasicMarshalTestSubAnonymousStruct - Sub basicMarshalTestSubStruct `toml:"Xsubdoc"` - SubList []basicMarshalTestSubStruct `toml:"Wsublist"` -} - -type basicMarshalTestSubStruct struct { - String2 string -} - -type BasicMarshalTestSubAnonymousStruct struct { - String3 string -} - -var basicTestData = basicMarshalTestStruct{ - String: "Hello", - StringList: []string{"Howdy", "Hey There"}, - BasicMarshalTestSubAnonymousStruct: BasicMarshalTestSubAnonymousStruct{"One"}, - Sub: basicMarshalTestSubStruct{"Two"}, - SubList: []basicMarshalTestSubStruct{{"Three"}, {"Four"}}, -} - -var basicTestToml = []byte(`String3 = "One" -Ystrlist = ["Howdy", "Hey There"] -Zstring = "Hello" - -[[Wsublist]] - String2 = "Three" - -[[Wsublist]] - String2 = "Four" - -[Xsubdoc] - String2 = "Two" -`) - -var basicTestTomlCustomIndentation = []byte(`String3 = "One" -Ystrlist = ["Howdy", "Hey There"] -Zstring = "Hello" - -[[Wsublist]] - String2 = "Three" - -[[Wsublist]] - String2 = "Four" - -[Xsubdoc] - String2 = "Two" -`) - -var basicTestTomlOrdered = []byte(`Zstring = "Hello" -Ystrlist = ["Howdy", "Hey There"] -String3 = "One" - -[Xsubdoc] - String2 = "Two" - -[[Wsublist]] - String2 = "Three" - -[[Wsublist]] - String2 = "Four" -`) - -var marshalTestToml = []byte(`title = "TOML Marshal Testing" - -[basic] - bool = true - date = 1979-05-27T07:32:00Z - float = 123.4 - float64 = 123.456782132399 - int = 5000 - string = "Bite me" - uint = 5001 - -[basic_lists] - bools = [true, false, true] - dates = [1979-05-27T07:32:00Z, 1980-05-27T07:32:00Z] - floats = [12.3, 45.6, 78.9] - ints = [8001, 8001, 8002] - strings = ["One", "Two", "Three"] - uints = [5002, 5003] - -[basic_map] - one = "one" - two = "two" - -[subdoc] - - [subdoc.first] - name = "First" - - [subdoc.second] - name = "Second" - -[[subdoclist]] - name = "List.First" - -[[subdoclist]] - name = "List.Second" - -[[subdocptrs]] - name = "Second" -`) - -var marshalOrderPreserveToml = []byte(`title = "TOML Marshal Testing" - -[basic_lists] - floats = [12.3, 45.6, 78.9] - bools = [true, false, true] - dates = [1979-05-27T07:32:00Z, 1980-05-27T07:32:00Z] - ints = [8001, 8001, 8002] - uints = [5002, 5003] - strings = ["One", "Two", "Three"] - -[[subdocptrs]] - name = "Second" - -[basic_map] - one = "one" - two = "two" - -[subdoc] - - [subdoc.second] - name = "Second" - - [subdoc.first] - name = "First" - -[basic] - uint = 5001 - bool = true - float = 123.4 - float64 = 123.456782132399 - int = 5000 - string = "Bite me" - date = 1979-05-27T07:32:00Z - -[[subdoclist]] - name = "List.First" - -[[subdoclist]] - name = "List.Second" -`) - -var mashalOrderPreserveMapToml = []byte(`title = "TOML Marshal Testing" - -[basic_map] - one = "one" - two = "two" - -[long_map] - a7 = "1" - b3 = "2" - c8 = "3" - d4 = "4" - e6 = "5" - f5 = "6" - g10 = "7" - h1 = "8" - i2 = "9" - j9 = "10" -`) - -type Conf struct { - Name string - Age int - Inter interface{} -} - -type NestedStruct struct { - FirstName string - LastName string - Age int -} - -var doc = []byte(`Name = "rui" -Age = 18 - -[Inter] - FirstName = "wang" - LastName = "jl" - Age = 100`) - -func TestInterface(t *testing.T) { - var config Conf - config.Inter = &NestedStruct{} - err := Unmarshal(doc, &config) - expected := Conf{ - Name: "rui", - Age: 18, - Inter: &NestedStruct{ - FirstName: "wang", - LastName: "jl", - Age: 100, - }, - } - if err != nil || !reflect.DeepEqual(config, expected) { - t.Errorf("Bad unmarshal: expected %v, got %v", expected, config) - } -} - -func TestBasicMarshal(t *testing.T) { - result, err := Marshal(basicTestData) - if err != nil { - t.Fatal(err) - } - expected := basicTestToml - if !bytes.Equal(result, expected) { - t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) - } -} - -func TestBasicMarshalCustomIndentation(t *testing.T) { - var result bytes.Buffer - err := NewEncoder(&result).Indentation("\t").Encode(basicTestData) - if err != nil { - t.Fatal(err) - } - expected := basicTestTomlCustomIndentation - if !bytes.Equal(result.Bytes(), expected) { - t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result.Bytes()) - } -} - -func TestBasicMarshalWrongIndentation(t *testing.T) { - var result bytes.Buffer - err := NewEncoder(&result).Indentation(" \n").Encode(basicTestData) - if err.Error() != "invalid indentation: must only contains space or tab characters" { - t.Error("expect err:invalid indentation: must only contains space or tab characters but got:", err) - } -} - -func TestBasicMarshalOrdered(t *testing.T) { - var result bytes.Buffer - err := NewEncoder(&result).Order(OrderPreserve).Encode(basicTestData) - if err != nil { - t.Fatal(err) - } - expected := basicTestTomlOrdered - if !bytes.Equal(result.Bytes(), expected) { - t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result.Bytes()) - } -} - -func TestBasicMarshalWithPointer(t *testing.T) { - result, err := Marshal(&basicTestData) - if err != nil { - t.Fatal(err) - } - expected := basicTestToml - if !bytes.Equal(result, expected) { - t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) - } -} - -func TestBasicMarshalOrderedWithPointer(t *testing.T) { - var result bytes.Buffer - err := NewEncoder(&result).Order(OrderPreserve).Encode(&basicTestData) - if err != nil { - t.Fatal(err) - } - expected := basicTestTomlOrdered - if !bytes.Equal(result.Bytes(), expected) { - t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result.Bytes()) - } -} - -func TestBasicUnmarshal(t *testing.T) { - result := basicMarshalTestStruct{} - err := Unmarshal(basicTestToml, &result) - expected := basicTestData - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(result, expected) { - t.Errorf("Bad unmarshal: expected %v, got %v", expected, result) - } -} - -type quotedKeyMarshalTestStruct struct { - String string `toml:"Z.string-àéù"` - Float float64 `toml:"Yfloat-𝟘"` - Sub basicMarshalTestSubStruct `toml:"Xsubdoc-àéù"` - SubList []basicMarshalTestSubStruct `toml:"W.sublist-𝟘"` -} - -var quotedKeyMarshalTestData = quotedKeyMarshalTestStruct{ - String: "Hello", - Float: 3.5, - Sub: basicMarshalTestSubStruct{"One"}, - SubList: []basicMarshalTestSubStruct{{"Two"}, {"Three"}}, -} - -var quotedKeyMarshalTestToml = []byte(`"Yfloat-𝟘" = 3.5 -"Z.string-àéù" = "Hello" - -[["W.sublist-𝟘"]] - String2 = "Two" - -[["W.sublist-𝟘"]] - String2 = "Three" - -["Xsubdoc-àéù"] - String2 = "One" -`) - -func TestBasicMarshalQuotedKey(t *testing.T) { - result, err := Marshal(quotedKeyMarshalTestData) - if err != nil { - t.Fatal(err) - } - expected := quotedKeyMarshalTestToml - if !bytes.Equal(result, expected) { - t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) - } -} - -func TestBasicUnmarshalQuotedKey(t *testing.T) { - tree, err := LoadBytes(quotedKeyMarshalTestToml) - if err != nil { - t.Fatal(err) - } - - var q quotedKeyMarshalTestStruct - tree.Unmarshal(&q) - fmt.Println(q) - - if !reflect.DeepEqual(quotedKeyMarshalTestData, q) { - t.Errorf("Bad unmarshal: expected\n-----\n%v\n-----\ngot\n-----\n%v\n-----\n", quotedKeyMarshalTestData, q) - } -} - -type testDoc struct { - Title string `toml:"title"` - BasicLists testDocBasicLists `toml:"basic_lists"` - SubDocPtrs []*testSubDoc `toml:"subdocptrs"` - BasicMap map[string]string `toml:"basic_map"` - Subdocs testDocSubs `toml:"subdoc"` - Basics testDocBasics `toml:"basic"` - SubDocList []testSubDoc `toml:"subdoclist"` - err int `toml:"shouldntBeHere"` - unexported int `toml:"shouldntBeHere"` - Unexported2 int `toml:"-"` -} - -type testMapDoc struct { - Title string `toml:"title"` - BasicMap map[string]string `toml:"basic_map"` - LongMap map[string]string `toml:"long_map"` -} - -type testDocBasics struct { - Uint uint `toml:"uint"` - Bool bool `toml:"bool"` - Float32 float32 `toml:"float"` - Float64 float64 `toml:"float64"` - Int int `toml:"int"` - String *string `toml:"string"` - Date time.Time `toml:"date"` - unexported int `toml:"shouldntBeHere"` -} - -type testDocBasicLists struct { - Floats []*float32 `toml:"floats"` - Bools []bool `toml:"bools"` - Dates []time.Time `toml:"dates"` - Ints []int `toml:"ints"` - UInts []uint `toml:"uints"` - Strings []string `toml:"strings"` -} - -type testDocSubs struct { - Second *testSubDoc `toml:"second"` - First testSubDoc `toml:"first"` -} - -type testSubDoc struct { - Name string `toml:"name"` - unexported int `toml:"shouldntBeHere"` -} - -var biteMe = "Bite me" -var float1 float32 = 12.3 -var float2 float32 = 45.6 -var float3 float32 = 78.9 -var subdoc = testSubDoc{"Second", 0} - -var docData = testDoc{ - Title: "TOML Marshal Testing", - unexported: 0, - Unexported2: 0, - Basics: testDocBasics{ - Bool: true, - Date: time.Date(1979, 5, 27, 7, 32, 0, 0, time.UTC), - Float32: 123.4, - Float64: 123.456782132399, - Int: 5000, - Uint: 5001, - String: &biteMe, - unexported: 0, - }, - BasicLists: testDocBasicLists{ - Bools: []bool{true, false, true}, - Dates: []time.Time{ - time.Date(1979, 5, 27, 7, 32, 0, 0, time.UTC), - time.Date(1980, 5, 27, 7, 32, 0, 0, time.UTC), - }, - Floats: []*float32{&float1, &float2, &float3}, - Ints: []int{8001, 8001, 8002}, - Strings: []string{"One", "Two", "Three"}, - UInts: []uint{5002, 5003}, - }, - BasicMap: map[string]string{ - "one": "one", - "two": "two", - }, - Subdocs: testDocSubs{ - First: testSubDoc{"First", 0}, - Second: &subdoc, - }, - SubDocList: []testSubDoc{ - {"List.First", 0}, - {"List.Second", 0}, - }, - SubDocPtrs: []*testSubDoc{&subdoc}, -} - -var mapTestDoc = testMapDoc{ - Title: "TOML Marshal Testing", - BasicMap: map[string]string{ - "one": "one", - "two": "two", - }, - LongMap: map[string]string{ - "h1": "8", - "i2": "9", - "b3": "2", - "d4": "4", - "f5": "6", - "e6": "5", - "a7": "1", - "c8": "3", - "j9": "10", - "g10": "7", - }, -} - -func TestDocMarshal(t *testing.T) { - result, err := Marshal(docData) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(result, marshalTestToml) { - t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", marshalTestToml, result) - } -} - -func TestDocMarshalOrdered(t *testing.T) { - var result bytes.Buffer - err := NewEncoder(&result).Order(OrderPreserve).Encode(docData) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(result.Bytes(), marshalOrderPreserveToml) { - t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", marshalOrderPreserveToml, result.Bytes()) - } -} - -func TestDocMarshalMaps(t *testing.T) { - result, err := Marshal(mapTestDoc) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(result, mashalOrderPreserveMapToml) { - t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", mashalOrderPreserveMapToml, result) - } -} - -func TestDocMarshalOrderedMaps(t *testing.T) { - var result bytes.Buffer - err := NewEncoder(&result).Order(OrderPreserve).Encode(mapTestDoc) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(result.Bytes(), mashalOrderPreserveMapToml) { - t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", mashalOrderPreserveMapToml, result.Bytes()) - } -} - -func TestDocMarshalPointer(t *testing.T) { - result, err := Marshal(&docData) - if err != nil { - t.Fatal(err) - } - - if !bytes.Equal(result, marshalTestToml) { - t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", marshalTestToml, result) - } -} - -func TestDocUnmarshal(t *testing.T) { - result := testDoc{} - err := Unmarshal(marshalTestToml, &result) - expected := docData - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(result, expected) { - resStr, _ := json.MarshalIndent(result, "", " ") - expStr, _ := json.MarshalIndent(expected, "", " ") - t.Errorf("Bad unmarshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expStr, resStr) - } -} - -func TestDocPartialUnmarshal(t *testing.T) { - file, err := ioutil.TempFile("", "test-*.toml") - if err != nil { - t.Fatal(err) - } - defer os.Remove(file.Name()) - - err = ioutil.WriteFile(file.Name(), marshalTestToml, 0) - if err != nil { - t.Fatal(err) - } - - tree, _ := LoadFile(file.Name()) - subTree := tree.Get("subdoc").(*Tree) - - result := testDocSubs{} - err = subTree.Unmarshal(&result) - expected := docData.Subdocs - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(result, expected) { - resStr, _ := json.MarshalIndent(result, "", " ") - expStr, _ := json.MarshalIndent(expected, "", " ") - t.Errorf("Bad partial unmartial: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expStr, resStr) - } -} - -type tomlTypeCheckTest struct { - name string - item interface{} - typ int //0=primitive, 1=otherslice, 2=treeslice, 3=tree -} - -func TestTypeChecks(t *testing.T) { - tests := []tomlTypeCheckTest{ - {"bool", true, 0}, - {"bool", false, 0}, - {"int", int(2), 0}, - {"int8", int8(2), 0}, - {"int16", int16(2), 0}, - {"int32", int32(2), 0}, - {"int64", int64(2), 0}, - {"uint", uint(2), 0}, - {"uint8", uint8(2), 0}, - {"uint16", uint16(2), 0}, - {"uint32", uint32(2), 0}, - {"uint64", uint64(2), 0}, - {"float32", float32(3.14), 0}, - {"float64", float64(3.14), 0}, - {"string", "lorem ipsum", 0}, - {"time", time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC), 0}, - {"stringlist", []string{"hello", "hi"}, 1}, - {"stringlistptr", &[]string{"hello", "hi"}, 1}, - {"stringarray", [2]string{"hello", "hi"}, 1}, - {"stringarrayptr", &[2]string{"hello", "hi"}, 1}, - {"timelist", []time.Time{time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC)}, 1}, - {"timelistptr", &[]time.Time{time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC)}, 1}, - {"timearray", [1]time.Time{time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC)}, 1}, - {"timearrayptr", &[1]time.Time{time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC)}, 1}, - {"objectlist", []tomlTypeCheckTest{}, 2}, - {"objectlistptr", &[]tomlTypeCheckTest{}, 2}, - {"objectarray", [2]tomlTypeCheckTest{{}, {}}, 2}, - {"objectlistptr", &[2]tomlTypeCheckTest{{}, {}}, 2}, - {"object", tomlTypeCheckTest{}, 3}, - {"objectptr", &tomlTypeCheckTest{}, 3}, - } - - for _, test := range tests { - expected := []bool{false, false, false, false} - expected[test.typ] = true - result := []bool{ - isPrimitive(reflect.TypeOf(test.item)), - isOtherSequence(reflect.TypeOf(test.item)), - isTreeSequence(reflect.TypeOf(test.item)), - isTree(reflect.TypeOf(test.item)), - } - if !reflect.DeepEqual(expected, result) { - t.Errorf("Bad type check on %q: expected %v, got %v", test.name, expected, result) - } - } -} - -type unexportedMarshalTestStruct struct { - String string `toml:"string"` - StringList []string `toml:"strlist"` - Sub basicMarshalTestSubStruct `toml:"subdoc"` - SubList []basicMarshalTestSubStruct `toml:"sublist"` - unexported int `toml:"shouldntBeHere"` - Unexported2 int `toml:"-"` -} - -var unexportedTestData = unexportedMarshalTestStruct{ - String: "Hello", - StringList: []string{"Howdy", "Hey There"}, - Sub: basicMarshalTestSubStruct{"One"}, - SubList: []basicMarshalTestSubStruct{{"Two"}, {"Three"}}, - unexported: 0, - Unexported2: 0, -} - -var unexportedTestToml = []byte(`string = "Hello" -strlist = ["Howdy","Hey There"] -unexported = 1 -shouldntBeHere = 2 - -[subdoc] - String2 = "One" - -[[sublist]] - String2 = "Two" - -[[sublist]] - String2 = "Three" -`) - -func TestUnexportedUnmarshal(t *testing.T) { - result := unexportedMarshalTestStruct{} - err := Unmarshal(unexportedTestToml, &result) - expected := unexportedTestData - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(result, expected) { - t.Errorf("Bad unexported unmarshal: expected %v, got %v", expected, result) - } -} - -type errStruct struct { - Bool bool `toml:"bool"` - Date time.Time `toml:"date"` - Float float64 `toml:"float"` - Int int16 `toml:"int"` - String *string `toml:"string"` -} - -var errTomls = []string{ - "bool = truly\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = 5000\nstring = \"Bite me\"", - "bool = true\ndate = 1979-05-27T07:3200Z\nfloat = 123.4\nint = 5000\nstring = \"Bite me\"", - "bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123a4\nint = 5000\nstring = \"Bite me\"", - "bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = j000\nstring = \"Bite me\"", - "bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = 5000\nstring = Bite me", - "bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = 5000\nstring = Bite me", - "bool = 1\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = 5000\nstring = \"Bite me\"", - "bool = true\ndate = 1\nfloat = 123.4\nint = 5000\nstring = \"Bite me\"", - "bool = true\ndate = 1979-05-27T07:32:00Z\n\"sorry\"\nint = 5000\nstring = \"Bite me\"", - "bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = \"sorry\"\nstring = \"Bite me\"", - "bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = 5000\nstring = 1", -} - -type mapErr struct { - Vals map[string]float64 -} - -type intErr struct { - Int1 int - Int2 int8 - Int3 int16 - Int4 int32 - Int5 int64 - UInt1 uint - UInt2 uint8 - UInt3 uint16 - UInt4 uint32 - UInt5 uint64 - Flt1 float32 - Flt2 float64 -} - -var intErrTomls = []string{ - "Int1 = []\nInt2 = 2\nInt3 = 3\nInt4 = 4\nInt5 = 5\nUInt1 = 1\nUInt2 = 2\nUInt3 = 3\nUInt4 = 4\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = 2.0", - "Int1 = 1\nInt2 = []\nInt3 = 3\nInt4 = 4\nInt5 = 5\nUInt1 = 1\nUInt2 = 2\nUInt3 = 3\nUInt4 = 4\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = 2.0", - "Int1 = 1\nInt2 = 2\nInt3 = []\nInt4 = 4\nInt5 = 5\nUInt1 = 1\nUInt2 = 2\nUInt3 = 3\nUInt4 = 4\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = 2.0", - "Int1 = 1\nInt2 = 2\nInt3 = 3\nInt4 = []\nInt5 = 5\nUInt1 = 1\nUInt2 = 2\nUInt3 = 3\nUInt4 = 4\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = 2.0", - "Int1 = 1\nInt2 = 2\nInt3 = 3\nInt4 = 4\nInt5 = []\nUInt1 = 1\nUInt2 = 2\nUInt3 = 3\nUInt4 = 4\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = 2.0", - "Int1 = 1\nInt2 = 2\nInt3 = 3\nInt4 = 4\nInt5 = 5\nUInt1 = []\nUInt2 = 2\nUInt3 = 3\nUInt4 = 4\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = 2.0", - "Int1 = 1\nInt2 = 2\nInt3 = 3\nInt4 = 4\nInt5 = 5\nUInt1 = 1\nUInt2 = []\nUInt3 = 3\nUInt4 = 4\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = 2.0", - "Int1 = 1\nInt2 = 2\nInt3 = 3\nInt4 = 4\nInt5 = 5\nUInt1 = 1\nUInt2 = 2\nUInt3 = []\nUInt4 = 4\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = 2.0", - "Int1 = 1\nInt2 = 2\nInt3 = 3\nInt4 = 4\nInt5 = 5\nUInt1 = 1\nUInt2 = 2\nUInt3 = 3\nUInt4 = []\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = 2.0", - "Int1 = 1\nInt2 = 2\nInt3 = 3\nInt4 = 4\nInt5 = 5\nUInt1 = 1\nUInt2 = 2\nUInt3 = 3\nUInt4 = 4\nUInt5 = []\nFlt1 = 1.0\nFlt2 = 2.0", - "Int1 = 1\nInt2 = 2\nInt3 = 3\nInt4 = 4\nInt5 = 5\nUInt1 = 1\nUInt2 = 2\nUInt3 = 3\nUInt4 = 4\nUInt5 = 5\nFlt1 = []\nFlt2 = 2.0", - "Int1 = 1\nInt2 = 2\nInt3 = 3\nInt4 = 4\nInt5 = 5\nUInt1 = 1\nUInt2 = 2\nUInt3 = 3\nUInt4 = 4\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = []", -} - -func TestErrUnmarshal(t *testing.T) { - for ind, toml := range errTomls { - result := errStruct{} - err := Unmarshal([]byte(toml), &result) - if err == nil { - t.Errorf("Expected err from case %d\n", ind) - } - } - result2 := mapErr{} - err := Unmarshal([]byte("[Vals]\nfred=\"1.2\""), &result2) - if err == nil { - t.Errorf("Expected err from map") - } - for ind, toml := range intErrTomls { - result3 := intErr{} - err := Unmarshal([]byte(toml), &result3) - if err == nil { - t.Errorf("Expected int err from case %d\n", ind) - } - } -} - -type emptyMarshalTestStruct struct { - Title string `toml:"title"` - Bool bool `toml:"bool"` - Int int `toml:"int"` - String string `toml:"string"` - StringList []string `toml:"stringlist"` - Ptr *basicMarshalTestStruct `toml:"ptr"` - Map map[string]string `toml:"map"` -} - -var emptyTestData = emptyMarshalTestStruct{ - Title: "Placeholder", - Bool: false, - Int: 0, - String: "", - StringList: []string{}, - Ptr: nil, - Map: map[string]string{}, -} - -var emptyTestToml = []byte(`bool = false -int = 0 -string = "" -stringlist = [] -title = "Placeholder" - -[map] -`) - -type emptyMarshalTestStruct2 struct { - Title string `toml:"title"` - Bool bool `toml:"bool,omitempty"` - Int int `toml:"int, omitempty"` - String string `toml:"string,omitempty "` - StringList []string `toml:"stringlist,omitempty"` - Ptr *basicMarshalTestStruct `toml:"ptr,omitempty"` - Map map[string]string `toml:"map,omitempty"` -} - -var emptyTestData2 = emptyMarshalTestStruct2{ - Title: "Placeholder", - Bool: false, - Int: 0, - String: "", - StringList: []string{}, - Ptr: nil, - Map: map[string]string{}, -} - -var emptyTestToml2 = []byte(`title = "Placeholder" -`) - -func TestEmptyMarshal(t *testing.T) { - result, err := Marshal(emptyTestData) - if err != nil { - t.Fatal(err) - } - expected := emptyTestToml - if !bytes.Equal(result, expected) { - t.Errorf("Bad empty marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) - } -} - -func TestEmptyMarshalOmit(t *testing.T) { - result, err := Marshal(emptyTestData2) - if err != nil { - t.Fatal(err) - } - expected := emptyTestToml2 - if !bytes.Equal(result, expected) { - t.Errorf("Bad empty omit marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) - } -} - -func TestEmptyUnmarshal(t *testing.T) { - result := emptyMarshalTestStruct{} - err := Unmarshal(emptyTestToml, &result) - expected := emptyTestData - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(result, expected) { - t.Errorf("Bad empty unmarshal: expected %v, got %v", expected, result) - } -} - -func TestEmptyUnmarshalOmit(t *testing.T) { - result := emptyMarshalTestStruct2{} - err := Unmarshal(emptyTestToml, &result) - expected := emptyTestData2 - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(result, expected) { - t.Errorf("Bad empty omit unmarshal: expected %v, got %v", expected, result) - } -} - -type pointerMarshalTestStruct struct { - Str *string - List *[]string - ListPtr *[]*string - Map *map[string]string - MapPtr *map[string]*string - EmptyStr *string - EmptyList *[]string - EmptyMap *map[string]string - DblPtr *[]*[]*string -} - -var pointerStr = "Hello" -var pointerList = []string{"Hello back"} -var pointerListPtr = []*string{&pointerStr} -var pointerMap = map[string]string{"response": "Goodbye"} -var pointerMapPtr = map[string]*string{"alternate": &pointerStr} -var pointerTestData = pointerMarshalTestStruct{ - Str: &pointerStr, - List: &pointerList, - ListPtr: &pointerListPtr, - Map: &pointerMap, - MapPtr: &pointerMapPtr, - EmptyStr: nil, - EmptyList: nil, - EmptyMap: nil, -} - -var pointerTestToml = []byte(`List = ["Hello back"] -ListPtr = ["Hello"] -Str = "Hello" - -[Map] - response = "Goodbye" - -[MapPtr] - alternate = "Hello" -`) - -func TestPointerMarshal(t *testing.T) { - result, err := Marshal(pointerTestData) - if err != nil { - t.Fatal(err) - } - expected := pointerTestToml - if !bytes.Equal(result, expected) { - t.Errorf("Bad pointer marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) - } -} - -func TestPointerUnmarshal(t *testing.T) { - result := pointerMarshalTestStruct{} - err := Unmarshal(pointerTestToml, &result) - expected := pointerTestData - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(result, expected) { - t.Errorf("Bad pointer unmarshal: expected %v, got %v", expected, result) - } -} - -func TestUnmarshalTypeMismatch(t *testing.T) { - result := pointerMarshalTestStruct{} - err := Unmarshal([]byte("List = 123"), &result) - if !strings.HasPrefix(err.Error(), "(1, 1): Can't convert 123(int64) to []string(slice)") { - t.Errorf("Type mismatch must be reported: got %v", err.Error()) - } -} - -type nestedMarshalTestStruct struct { - String [][]string - //Struct [][]basicMarshalTestSubStruct - StringPtr *[]*[]*string - // StructPtr *[]*[]*basicMarshalTestSubStruct -} - -var str1 = "Three" -var str2 = "Four" -var strPtr = []*string{&str1, &str2} -var strPtr2 = []*[]*string{&strPtr} - -var nestedTestData = nestedMarshalTestStruct{ - String: [][]string{{"Five", "Six"}, {"One", "Two"}}, - StringPtr: &strPtr2, -} - -var nestedTestToml = []byte(`String = [["Five", "Six"], ["One", "Two"]] -StringPtr = [["Three", "Four"]] -`) - -func TestNestedMarshal(t *testing.T) { - result, err := Marshal(nestedTestData) - if err != nil { - t.Fatal(err) - } - expected := nestedTestToml - if !bytes.Equal(result, expected) { - t.Errorf("Bad nested marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) - } -} - -func TestNestedUnmarshal(t *testing.T) { - result := nestedMarshalTestStruct{} - err := Unmarshal(nestedTestToml, &result) - expected := nestedTestData - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(result, expected) { - t.Errorf("Bad nested unmarshal: expected %v, got %v", expected, result) - } -} - -type customMarshalerParent struct { - Self customMarshaler `toml:"me"` - Friends []customMarshaler `toml:"friends"` -} - -type customMarshaler struct { - FirstName string - LastName string -} - -func (c customMarshaler) MarshalTOML() ([]byte, error) { - fullName := fmt.Sprintf("%s %s", c.FirstName, c.LastName) - return []byte(fullName), nil -} - -var customMarshalerData = customMarshaler{FirstName: "Sally", LastName: "Fields"} -var customMarshalerToml = []byte(`Sally Fields`) -var nestedCustomMarshalerData = customMarshalerParent{ - Self: customMarshaler{FirstName: "Maiku", LastName: "Suteda"}, - Friends: []customMarshaler{customMarshalerData}, -} -var nestedCustomMarshalerToml = []byte(`friends = ["Sally Fields"] -me = "Maiku Suteda" -`) -var nestedCustomMarshalerTomlForUnmarshal = []byte(`[friends] -FirstName = "Sally" -LastName = "Fields"`) - -func TestCustomMarshaler(t *testing.T) { - result, err := Marshal(customMarshalerData) - if err != nil { - t.Fatal(err) - } - expected := customMarshalerToml - if !bytes.Equal(result, expected) { - t.Errorf("Bad custom marshaler: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) - } -} - -type IntOrString string - -func (x *IntOrString) MarshalTOML() ([]byte, error) { - s := *(*string)(x) - _, err := strconv.Atoi(s) - if err != nil { - return []byte(fmt.Sprintf(`"%s"`, s)), nil - } - return []byte(s), nil -} - -func TestNestedCustomMarshaler(t *testing.T) { - num := IntOrString("100") - str := IntOrString("hello") - var parent = struct { - IntField *IntOrString `toml:"int"` - StringField *IntOrString `toml:"string"` - }{ - &num, - &str, - } - - result, err := Marshal(parent) - if err != nil { - t.Fatal(err) - } - expected := `int = 100 -string = "hello" -` - if !bytes.Equal(result, []byte(expected)) { - t.Errorf("Bad nested text marshaler: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) - } -} - -type textMarshaler struct { - FirstName string - LastName string -} - -func (m textMarshaler) MarshalText() ([]byte, error) { - fullName := fmt.Sprintf("%s %s", m.FirstName, m.LastName) - return []byte(fullName), nil -} - -func TestTextMarshaler(t *testing.T) { - m := textMarshaler{FirstName: "Sally", LastName: "Fields"} - - result, err := Marshal(m) - if err != nil { - t.Fatal(err) - } - expected := `Sally Fields` - if !bytes.Equal(result, []byte(expected)) { - t.Errorf("Bad text marshaler: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) - } -} - -func TestUnmarshalTextMarshaler(t *testing.T) { - var nested = struct { - Friends textMarshaler `toml:"friends"` - }{} - - var expected = struct { - Friends textMarshaler `toml:"friends"` - }{ - Friends: textMarshaler{FirstName: "Sally", LastName: "Fields"}, - } - - err := Unmarshal(nestedCustomMarshalerTomlForUnmarshal, &nested) - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(nested, expected) { - t.Errorf("Bad unmarshal: expected %v, got %v", expected, nested) - } -} - -func TestNestedTextMarshaler(t *testing.T) { - var parent = struct { - Self textMarshaler `toml:"me"` - Friends []textMarshaler `toml:"friends"` - Stranger *textMarshaler `toml:"stranger"` - }{ - Self: textMarshaler{FirstName: "Maiku", LastName: "Suteda"}, - Friends: []textMarshaler{textMarshaler{FirstName: "Sally", LastName: "Fields"}}, - Stranger: &textMarshaler{FirstName: "Earl", LastName: "Henson"}, - } - - result, err := Marshal(parent) - if err != nil { - t.Fatal(err) - } - expected := `friends = ["Sally Fields"] -me = "Maiku Suteda" -stranger = "Earl Henson" -` - if !bytes.Equal(result, []byte(expected)) { - t.Errorf("Bad nested text marshaler: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) - } -} - -type precedentMarshaler struct { - FirstName string - LastName string -} - -func (m precedentMarshaler) MarshalText() ([]byte, error) { - return []byte("shadowed"), nil -} - -func (m precedentMarshaler) MarshalTOML() ([]byte, error) { - fullName := fmt.Sprintf("%s %s", m.FirstName, m.LastName) - return []byte(fullName), nil -} - -func TestPrecedentMarshaler(t *testing.T) { - m := textMarshaler{FirstName: "Sally", LastName: "Fields"} - - result, err := Marshal(m) - if err != nil { - t.Fatal(err) - } - expected := `Sally Fields` - if !bytes.Equal(result, []byte(expected)) { - t.Errorf("Bad text marshaler: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) - } -} - -type customPointerMarshaler struct { - FirstName string - LastName string -} - -func (m *customPointerMarshaler) MarshalTOML() ([]byte, error) { - return []byte(`"hidden"`), nil -} - -type textPointerMarshaler struct { - FirstName string - LastName string -} - -func (m *textPointerMarshaler) MarshalText() ([]byte, error) { - return []byte("hidden"), nil -} - -func TestPointerMarshaler(t *testing.T) { - var parent = struct { - Self customPointerMarshaler `toml:"me"` - Stranger *customPointerMarshaler `toml:"stranger"` - Friend textPointerMarshaler `toml:"friend"` - Fiend *textPointerMarshaler `toml:"fiend"` - }{ - Self: customPointerMarshaler{FirstName: "Maiku", LastName: "Suteda"}, - Stranger: &customPointerMarshaler{FirstName: "Earl", LastName: "Henson"}, - Friend: textPointerMarshaler{FirstName: "Sally", LastName: "Fields"}, - Fiend: &textPointerMarshaler{FirstName: "Casper", LastName: "Snider"}, - } - - result, err := Marshal(parent) - if err != nil { - t.Fatal(err) - } - expected := `fiend = "hidden" -stranger = "hidden" - -[friend] - FirstName = "Sally" - LastName = "Fields" - -[me] - FirstName = "Maiku" - LastName = "Suteda" -` - if !bytes.Equal(result, []byte(expected)) { - t.Errorf("Bad nested text marshaler: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) - } -} - -func TestPointerCustomMarshalerSequence(t *testing.T) { - var customPointerMarshalerSlice *[]*customPointerMarshaler - var customPointerMarshalerArray *[2]*customPointerMarshaler - - if !isCustomMarshalerSequence(reflect.TypeOf(customPointerMarshalerSlice)) { - t.Errorf("error: should be a sequence of custom marshaler interfaces") - } - if !isCustomMarshalerSequence(reflect.TypeOf(customPointerMarshalerArray)) { - t.Errorf("error: should be a sequence of custom marshaler interfaces") - } -} - -func TestPointerTextMarshalerSequence(t *testing.T) { - var textPointerMarshalerSlice *[]*textPointerMarshaler - var textPointerMarshalerArray *[2]*textPointerMarshaler - - if !isTextMarshalerSequence(reflect.TypeOf(textPointerMarshalerSlice)) { - t.Errorf("error: should be a sequence of text marshaler interfaces") - } - if !isTextMarshalerSequence(reflect.TypeOf(textPointerMarshalerArray)) { - t.Errorf("error: should be a sequence of text marshaler interfaces") - } -} - -var commentTestToml = []byte(` -# it's a comment on type -[postgres] - # isCommented = "dvalue" - noComment = "cvalue" - - # A comment on AttrB with a - # break line - password = "bvalue" - - # A comment on AttrA - user = "avalue" - - [[postgres.My]] - - # a comment on my on typeC - My = "Foo" - - [[postgres.My]] - - # a comment on my on typeC - My = "Baar" -`) - -func TestMarshalComment(t *testing.T) { - type TypeC struct { - My string `comment:"a comment on my on typeC"` - } - type TypeB struct { - AttrA string `toml:"user" comment:"A comment on AttrA"` - AttrB string `toml:"password" comment:"A comment on AttrB with a\n break line"` - AttrC string `toml:"noComment"` - AttrD string `toml:"isCommented" commented:"true"` - My []TypeC - } - type TypeA struct { - TypeB TypeB `toml:"postgres" comment:"it's a comment on type"` - } - - ta := []TypeC{{My: "Foo"}, {My: "Baar"}} - config := TypeA{TypeB{AttrA: "avalue", AttrB: "bvalue", AttrC: "cvalue", AttrD: "dvalue", My: ta}} - result, err := Marshal(config) - if err != nil { - t.Fatal(err) - } - expected := commentTestToml - if !bytes.Equal(result, expected) { - t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) - } -} - -func TestMarshalMultilineCommented(t *testing.T) { - expectedToml := []byte(`# MultilineArray = [ - # 100, - # 200, - # 300, -# ] -# MultilineNestedArray = [ - # [ - # "a", - # "b", - # "c", -# ], - # [ - # "d", - # "e", - # "f", -# ], -# ] -# MultilineString = """ -# I -# am -# Allen""" -NonCommented = "Not commented line" -`) - type StructWithMultiline struct { - NonCommented string - MultilineString string `commented:"true" multiline:"true"` - MultilineArray []int `commented:"true"` - MultilineNestedArray [][]string `commented:"true"` - } - - var buf bytes.Buffer - enc := NewEncoder(&buf) - if err := enc.ArraysWithOneElementPerLine(true).Encode(StructWithMultiline{ - NonCommented: "Not commented line", - MultilineString: "I\nam\nAllen", - MultilineArray: []int{100, 200, 300}, - MultilineNestedArray: [][]string{ - {"a", "b", "c"}, - {"d", "e", "f"}, - }, - }); err == nil { - result := buf.Bytes() - if !bytes.Equal(result, expectedToml) { - t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expectedToml, result) - } - } else { - t.Fatal(err) - } -} - -func TestMarshalNonPrimitiveTypeCommented(t *testing.T) { - expectedToml := []byte(` -# [CommentedMapField] - - # [CommentedMapField.CommentedMapField1] - # SingleLineString = "This line should be commented out" - - # [CommentedMapField.CommentedMapField2] - # SingleLineString = "This line should be commented out" - -# [CommentedStructField] - - # [CommentedStructField.CommentedStructField] - # MultilineArray = [ - # 1, - # 2, - # ] - # MultilineNestedArray = [ - # [ - # 10, - # 20, - # ], - # [ - # 100, - # 200, - # ], - # ] - # MultilineString = """ -# This line -# should be -# commented out""" - - # [CommentedStructField.NotCommentedStructField] - # MultilineArray = [ - # 1, - # 2, - # ] - # MultilineNestedArray = [ - # [ - # 10, - # 20, - # ], - # [ - # 100, - # 200, - # ], - # ] - # MultilineString = """ -# This line -# should be -# commented out""" - -[NotCommentedStructField] - - # [NotCommentedStructField.CommentedStructField] - # MultilineArray = [ - # 1, - # 2, - # ] - # MultilineNestedArray = [ - # [ - # 10, - # 20, - # ], - # [ - # 100, - # 200, - # ], - # ] - # MultilineString = """ -# This line -# should be -# commented out""" - - [NotCommentedStructField.NotCommentedStructField] - MultilineArray = [ - 3, - 4, - ] - MultilineNestedArray = [ - [ - 30, - 40, - ], - [ - 300, - 400, - ], - ] - MultilineString = """ -This line -should NOT be -commented out""" -`) - type InnerStruct struct { - MultilineString string `multiline:"true"` - MultilineArray []int - MultilineNestedArray [][]int - } - type MiddleStruct struct { - NotCommentedStructField InnerStruct - CommentedStructField InnerStruct `commented:"true"` - } - type OuterStruct struct { - CommentedStructField MiddleStruct `commented:"true"` - NotCommentedStructField MiddleStruct - CommentedMapField map[string]struct{ SingleLineString string } `commented:"true"` - } - - commentedTestStruct := OuterStruct{ - CommentedStructField: MiddleStruct{ - NotCommentedStructField: InnerStruct{ - MultilineString: "This line\nshould be\ncommented out", - MultilineArray: []int{1, 2}, - MultilineNestedArray: [][]int{{10, 20}, {100, 200}}, - }, - CommentedStructField: InnerStruct{ - MultilineString: "This line\nshould be\ncommented out", - MultilineArray: []int{1, 2}, - MultilineNestedArray: [][]int{{10, 20}, {100, 200}}, - }, - }, - NotCommentedStructField: MiddleStruct{ - NotCommentedStructField: InnerStruct{ - MultilineString: "This line\nshould NOT be\ncommented out", - MultilineArray: []int{3, 4}, - MultilineNestedArray: [][]int{{30, 40}, {300, 400}}, - }, - CommentedStructField: InnerStruct{ - MultilineString: "This line\nshould be\ncommented out", - MultilineArray: []int{1, 2}, - MultilineNestedArray: [][]int{{10, 20}, {100, 200}}, - }, - }, - CommentedMapField: map[string]struct{ SingleLineString string }{ - "CommentedMapField1": { - SingleLineString: "This line should be commented out", - }, - "CommentedMapField2": { - SingleLineString: "This line should be commented out", - }, - }, - } - - var buf bytes.Buffer - enc := NewEncoder(&buf) - if err := enc.ArraysWithOneElementPerLine(true).Encode(commentedTestStruct); err == nil { - result := buf.Bytes() - if !bytes.Equal(result, expectedToml) { - t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expectedToml, result) - } - } else { - t.Fatal(err) - } -} - -type mapsTestStruct struct { - Simple map[string]string - Paths map[string]string - Other map[string]float64 - X struct { - Y struct { - Z map[string]bool - } - } -} - -var mapsTestData = mapsTestStruct{ - Simple: map[string]string{ - "one plus one": "two", - "next": "three", - }, - Paths: map[string]string{ - "/this/is/a/path": "/this/is/also/a/path", - "/heloo.txt": "/tmp/lololo.txt", - }, - Other: map[string]float64{ - "testing": 3.9999, - }, - X: struct{ Y struct{ Z map[string]bool } }{ - Y: struct{ Z map[string]bool }{ - Z: map[string]bool{ - "is.Nested": true, - }, - }, - }, -} -var mapsTestToml = []byte(` -[Other] - "testing" = 3.9999 - -[Paths] - "/heloo.txt" = "/tmp/lololo.txt" - "/this/is/a/path" = "/this/is/also/a/path" - -[Simple] - "next" = "three" - "one plus one" = "two" - -[X] - - [X.Y] - - [X.Y.Z] - "is.Nested" = true -`) - -func TestEncodeQuotedMapKeys(t *testing.T) { - var buf bytes.Buffer - if err := NewEncoder(&buf).QuoteMapKeys(true).Encode(mapsTestData); err != nil { - t.Fatal(err) - } - result := buf.Bytes() - expected := mapsTestToml - if !bytes.Equal(result, expected) { - t.Errorf("Bad maps marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) - } -} - -func TestDecodeQuotedMapKeys(t *testing.T) { - result := mapsTestStruct{} - err := NewDecoder(bytes.NewBuffer(mapsTestToml)).Decode(&result) - expected := mapsTestData - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(result, expected) { - t.Errorf("Bad maps unmarshal: expected %v, got %v", expected, result) - } -} - -type structArrayNoTag struct { - A struct { - B []int64 - C []int64 - } -} - -func TestMarshalArray(t *testing.T) { - expected := []byte(` -[A] - B = [1, 2, 3] - C = [1] -`) - - m := structArrayNoTag{ - A: struct { - B []int64 - C []int64 - }{ - B: []int64{1, 2, 3}, - C: []int64{1}, - }, - } - - b, err := Marshal(m) - - if err != nil { - t.Fatal(err) - } - - if !bytes.Equal(b, expected) { - t.Errorf("Bad arrays marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, b) - } -} - -func TestMarshalArrayOnePerLine(t *testing.T) { - expected := []byte(` -[A] - B = [ - 1, - 2, - 3, - ] - C = [1] -`) - - m := structArrayNoTag{ - A: struct { - B []int64 - C []int64 - }{ - B: []int64{1, 2, 3}, - C: []int64{1}, - }, - } - - var buf bytes.Buffer - encoder := NewEncoder(&buf).ArraysWithOneElementPerLine(true) - err := encoder.Encode(m) - - if err != nil { - t.Fatal(err) - } - - b := buf.Bytes() - - if !bytes.Equal(b, expected) { - t.Errorf("Bad arrays marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, b) - } -} - -var customTagTestToml = []byte(` -[postgres] - password = "bvalue" - user = "avalue" - - [[postgres.My]] - My = "Foo" - - [[postgres.My]] - My = "Baar" -`) - -func TestMarshalCustomTag(t *testing.T) { - type TypeC struct { - My string - } - type TypeB struct { - AttrA string `file:"user"` - AttrB string `file:"password"` - My []TypeC - } - type TypeA struct { - TypeB TypeB `file:"postgres"` - } - - ta := []TypeC{{My: "Foo"}, {My: "Baar"}} - config := TypeA{TypeB{AttrA: "avalue", AttrB: "bvalue", My: ta}} - var buf bytes.Buffer - err := NewEncoder(&buf).SetTagName("file").Encode(config) - if err != nil { - t.Fatal(err) - } - expected := customTagTestToml - result := buf.Bytes() - if !bytes.Equal(result, expected) { - t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) - } -} - -var customCommentTagTestToml = []byte(` -# db connection -[postgres] - - # db pass - password = "bvalue" - - # db user - user = "avalue" -`) - -func TestMarshalCustomComment(t *testing.T) { - type TypeB struct { - AttrA string `toml:"user" descr:"db user"` - AttrB string `toml:"password" descr:"db pass"` - } - type TypeA struct { - TypeB TypeB `toml:"postgres" descr:"db connection"` - } - - config := TypeA{TypeB{AttrA: "avalue", AttrB: "bvalue"}} - var buf bytes.Buffer - err := NewEncoder(&buf).SetTagComment("descr").Encode(config) - if err != nil { - t.Fatal(err) - } - expected := customCommentTagTestToml - result := buf.Bytes() - if !bytes.Equal(result, expected) { - t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) - } -} - -var customCommentedTagTestToml = []byte(` -[postgres] - # password = "bvalue" - # user = "avalue" -`) - -func TestMarshalCustomCommented(t *testing.T) { - type TypeB struct { - AttrA string `toml:"user" disable:"true"` - AttrB string `toml:"password" disable:"true"` - } - type TypeA struct { - TypeB TypeB `toml:"postgres"` - } - - config := TypeA{TypeB{AttrA: "avalue", AttrB: "bvalue"}} - var buf bytes.Buffer - err := NewEncoder(&buf).SetTagCommented("disable").Encode(config) - if err != nil { - t.Fatal(err) - } - expected := customCommentedTagTestToml - result := buf.Bytes() - if !bytes.Equal(result, expected) { - t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) - } -} - -func TestMarshalDirectMultilineString(t *testing.T) { - tree := newTree() - tree.SetWithOptions("mykey", SetOptions{ - Multiline: true, - }, "my\x11multiline\nstring\ba\tb\fc\rd\"e\\!") - result, err := tree.Marshal() - if err != nil { - t.Fatal("marshal should not error:", err) - } - expected := []byte("mykey = \"\"\"\nmy\\u0011multiline\nstring\\ba\tb\\fc\rd\"e\\!\"\"\"\n") - if !bytes.Equal(result, expected) { - t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) - } -} - -func TestUnmarshalTabInStringAndQuotedKey(t *testing.T) { - type Test struct { - Field1 string `toml:"Fie ld1"` - Field2 string - } - - type TestCase struct { - desc string - input []byte - expected Test - } - - testCases := []TestCase{ - { - desc: "multiline string with tab", - input: []byte("Field2 = \"\"\"\nhello\tworld\"\"\""), - expected: Test{ - Field2: "hello\tworld", - }, - }, - { - desc: "quoted key with tab", - input: []byte("\"Fie\tld1\" = \"key with tab\""), - expected: Test{ - Field1: "key with tab", - }, - }, - { - desc: "basic string tab", - input: []byte("Field2 = \"hello\tworld\""), - expected: Test{ - Field2: "hello\tworld", - }, - }, - } - - for i := range testCases { - result := Test{} - err := Unmarshal(testCases[i].input, &result) - if err != nil { - t.Errorf("%s test error:%v", testCases[i].desc, err) - continue - } - - if !reflect.DeepEqual(result, testCases[i].expected) { - t.Errorf("%s test error: expected\n-----\n%+v\n-----\ngot\n-----\n%+v\n-----\n", - testCases[i].desc, testCases[i].expected, result) - } - } -} - -var customMultilineTagTestToml = []byte(`int_slice = [ - 1, - 2, - 3, -] -`) - -func TestMarshalCustomMultiline(t *testing.T) { - type TypeA struct { - AttrA []int `toml:"int_slice" mltln:"true"` - } - - config := TypeA{AttrA: []int{1, 2, 3}} - var buf bytes.Buffer - err := NewEncoder(&buf).ArraysWithOneElementPerLine(true).SetTagMultiline("mltln").Encode(config) - if err != nil { - t.Fatal(err) - } - expected := customMultilineTagTestToml - result := buf.Bytes() - if !bytes.Equal(result, expected) { - t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) - } -} - -func TestMultilineWithAdjacentQuotationMarks(t *testing.T) { - type testStruct struct { - Str string `multiline:"true"` - } - type testCase struct { - expected []byte - data testStruct - } - - testCases := []testCase{ - { - expected: []byte(`Str = """ -hello\"""" -`), - data: testStruct{ - Str: "hello\"", - }, - }, - { - expected: []byte(`Str = """ -""\"""\"""\"""" -`), - data: testStruct{ - Str: "\"\"\"\"\"\"\"\"\"", - }, - }, - } - for i := range testCases { - result, err := Marshal(testCases[i].data) - if err != nil { - t.Fatal(err) - } - - if !bytes.Equal(result, testCases[i].expected) { - t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", - testCases[i].expected, result) - } else { - var data testStruct - if err = Unmarshal(result, &data); err != nil { - t.Fatal(err) - } - if data.Str != testCases[i].data.Str { - t.Errorf("Round trip test fail: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", - testCases[i].data.Str, data.Str) - } - } - } -} - -func TestMarshalEmbedTree(t *testing.T) { - expected := []byte(`OuterField1 = "Out" -OuterField2 = 1024 - -[TreeField] - InnerField1 = "In" - InnerField2 = 2048 - - [TreeField.EmbedStruct] - EmbedField = "Embed" -`) - type InnerStruct struct { - InnerField1 string - InnerField2 int - EmbedStruct struct { - EmbedField string - } - } - - type OuterStruct struct { - OuterField1 string - OuterField2 int - TreeField *Tree - } - - tree, err := Load(` -InnerField1 = "In" -InnerField2 = 2048 - -[EmbedStruct] - EmbedField = "Embed" -`) - if err != nil { - t.Fatal(err) - } - - out := OuterStruct{ - "Out", - 1024, - tree, - } - actual, _ := Marshal(out) - - if !bytes.Equal(actual, expected) { - t.Errorf("Bad marshal: expected %s, got %s", expected, actual) - } -} - -var testDocBasicToml = []byte(` -[document] - bool_val = true - date_val = 1979-05-27T07:32:00Z - float_val = 123.4 - int_val = 5000 - string_val = "Bite me" - uint_val = 5001 -`) - -type testDocCustomTag struct { - Doc testDocBasicsCustomTag `file:"document"` -} -type testDocBasicsCustomTag struct { - Bool bool `file:"bool_val"` - Date time.Time `file:"date_val"` - Float float32 `file:"float_val"` - Int int `file:"int_val"` - Uint uint `file:"uint_val"` - String *string `file:"string_val"` - unexported int `file:"shouldntBeHere"` -} - -var testDocCustomTagData = testDocCustomTag{ - Doc: testDocBasicsCustomTag{ - Bool: true, - Date: time.Date(1979, 5, 27, 7, 32, 0, 0, time.UTC), - Float: 123.4, - Int: 5000, - Uint: 5001, - String: &biteMe, - unexported: 0, - }, -} - -func TestUnmarshalCustomTag(t *testing.T) { - buf := bytes.NewBuffer(testDocBasicToml) - - result := testDocCustomTag{} - err := NewDecoder(buf).SetTagName("file").Decode(&result) - if err != nil { - t.Fatal(err) - } - expected := testDocCustomTagData - if !reflect.DeepEqual(result, expected) { - resStr, _ := json.MarshalIndent(result, "", " ") - expStr, _ := json.MarshalIndent(expected, "", " ") - t.Errorf("Bad unmarshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expStr, resStr) - - } -} - -func TestUnmarshalMap(t *testing.T) { - testToml := []byte(` - a = 1 - b = 2 - c = 3 - `) - var result map[string]int - err := Unmarshal(testToml, &result) - if err != nil { - t.Errorf("Received unexpected error: %s", err) - return - } - - expected := map[string]int{ - "a": 1, - "b": 2, - "c": 3, - } - - if !reflect.DeepEqual(result, expected) { - t.Errorf("Bad unmarshal: expected %v, got %v", expected, result) - } -} - -func TestUnmarshalMapWithTypedKey(t *testing.T) { - testToml := []byte(` - a = 1 - b = 2 - c = 3 - `) - - type letter string - var result map[letter]int - err := Unmarshal(testToml, &result) - if err != nil { - t.Errorf("Received unexpected error: %s", err) - return - } - - expected := map[letter]int{ - "a": 1, - "b": 2, - "c": 3, - } - - if !reflect.DeepEqual(result, expected) { - t.Errorf("Bad unmarshal: expected %v, got %v", expected, result) - } -} - -func TestUnmarshalNonPointer(t *testing.T) { - a := 1 - err := Unmarshal([]byte{}, a) - if err == nil { - t.Fatal("unmarshal should err when given a non pointer") - } -} - -func TestUnmarshalInvalidPointerKind(t *testing.T) { - a := 1 - err := Unmarshal([]byte{}, &a) - if err == nil { - t.Fatal("unmarshal should err when given an invalid pointer type") - } -} - -func TestMarshalSlice(t *testing.T) { - m := make([]int, 1) - m[0] = 1 - - var buf bytes.Buffer - err := NewEncoder(&buf).Encode(&m) - if err == nil { - t.Error("expected error, got nil") - return - } - if err.Error() != "Only pointer to struct can be marshaled to TOML" { - t.Fail() - } -} - -func TestMarshalSlicePointer(t *testing.T) { - m := make([]int, 1) - m[0] = 1 - - var buf bytes.Buffer - err := NewEncoder(&buf).Encode(m) - if err == nil { - t.Error("expected error, got nil") - return - } - if err.Error() != "Only a struct or map can be marshaled to TOML" { - t.Fail() - } -} - -func TestMarshalNestedArrayInlineTables(t *testing.T) { - type table struct { - Value1 int `toml:"ZValue1"` - Value2 int `toml:"YValue2"` - Value3 int `toml:"XValue3"` - } - - type nestedTable struct { - Table table - } - - nestedArray := struct { - Simple [][]table - SimplePointer *[]*[]table - Nested [][]nestedTable - NestedPointer *[]*[]nestedTable - }{ - Simple: [][]table{{{Value1: 1}, {Value1: 10}}}, - SimplePointer: &[]*[]table{{{Value2: 2}}}, - Nested: [][]nestedTable{{{Table: table{Value3: 3}}}}, - NestedPointer: &[]*[]nestedTable{{{Table: table{Value3: -3}}}}, - } - - expectedPreserve := `Simple = [[{ ZValue1 = 1, YValue2 = 0, XValue3 = 0 }, { ZValue1 = 10, YValue2 = 0, XValue3 = 0 }]] -SimplePointer = [[{ ZValue1 = 0, YValue2 = 2, XValue3 = 0 }]] -Nested = [[{ Table = { ZValue1 = 0, YValue2 = 0, XValue3 = 3 } }]] -NestedPointer = [[{ Table = { ZValue1 = 0, YValue2 = 0, XValue3 = -3 } }]] -` - - expectedAlphabetical := `Nested = [[{ Table = { XValue3 = 3, YValue2 = 0, ZValue1 = 0 } }]] -NestedPointer = [[{ Table = { XValue3 = -3, YValue2 = 0, ZValue1 = 0 } }]] -Simple = [[{ XValue3 = 0, YValue2 = 0, ZValue1 = 1 }, { XValue3 = 0, YValue2 = 0, ZValue1 = 10 }]] -SimplePointer = [[{ XValue3 = 0, YValue2 = 2, ZValue1 = 0 }]] -` - - var bufPreserve bytes.Buffer - if err := NewEncoder(&bufPreserve).Order(OrderPreserve).Encode(nestedArray); err != nil { - t.Fatalf("unexpected error: %s", err.Error()) - } - if !bytes.Equal(bufPreserve.Bytes(), []byte(expectedPreserve)) { - t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expectedPreserve, bufPreserve.String()) - } - - var bufAlphabetical bytes.Buffer - if err := NewEncoder(&bufAlphabetical).Order(OrderAlphabetical).Encode(nestedArray); err != nil { - t.Fatalf("unexpected error: %s", err.Error()) - } - if !bytes.Equal(bufAlphabetical.Bytes(), []byte(expectedAlphabetical)) { - t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expectedAlphabetical, bufAlphabetical.String()) - } -} - -type testDuration struct { - Nanosec time.Duration `toml:"nanosec"` - Microsec1 time.Duration `toml:"microsec1"` - Microsec2 *time.Duration `toml:"microsec2"` - Millisec time.Duration `toml:"millisec"` - Sec time.Duration `toml:"sec"` - Min time.Duration `toml:"min"` - Hour time.Duration `toml:"hour"` - Mixed time.Duration `toml:"mixed"` - AString string `toml:"a_string"` -} - -var testDurationToml = []byte(` -nanosec = "1ns" -microsec1 = "1us" -microsec2 = "1µs" -millisec = "1ms" -sec = "1s" -min = "1m" -hour = "1h" -mixed = "1h1m1s1ms1µs1ns" -a_string = "15s" -`) - -func TestUnmarshalDuration(t *testing.T) { - buf := bytes.NewBuffer(testDurationToml) - - result := testDuration{} - err := NewDecoder(buf).Decode(&result) - if err != nil { - t.Fatal(err) - } - ms := time.Duration(1) * time.Microsecond - expected := testDuration{ - Nanosec: 1, - Microsec1: time.Microsecond, - Microsec2: &ms, - Millisec: time.Millisecond, - Sec: time.Second, - Min: time.Minute, - Hour: time.Hour, - Mixed: time.Hour + - time.Minute + - time.Second + - time.Millisecond + - time.Microsecond + - time.Nanosecond, - AString: "15s", - } - if !reflect.DeepEqual(result, expected) { - resStr, _ := json.MarshalIndent(result, "", " ") - expStr, _ := json.MarshalIndent(expected, "", " ") - t.Errorf("Bad unmarshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expStr, resStr) - - } -} - -var testDurationToml2 = []byte(`a_string = "15s" -hour = "1h0m0s" -microsec1 = "1µs" -microsec2 = "1µs" -millisec = "1ms" -min = "1m0s" -mixed = "1h1m1.001001001s" -nanosec = "1ns" -sec = "1s" -`) - -func TestMarshalDuration(t *testing.T) { - ms := time.Duration(1) * time.Microsecond - data := testDuration{ - Nanosec: 1, - Microsec1: time.Microsecond, - Microsec2: &ms, - Millisec: time.Millisecond, - Sec: time.Second, - Min: time.Minute, - Hour: time.Hour, - Mixed: time.Hour + - time.Minute + - time.Second + - time.Millisecond + - time.Microsecond + - time.Nanosecond, - AString: "15s", - } - - var buf bytes.Buffer - err := NewEncoder(&buf).Encode(data) - if err != nil { - t.Fatal(err) - } - expected := testDurationToml2 - result := buf.Bytes() - if !bytes.Equal(result, expected) { - t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) - } -} - -type testBadDuration struct { - Val time.Duration `toml:"val"` -} - -var testBadDurationToml = []byte(`val = "1z"`) - -func TestUnmarshalBadDuration(t *testing.T) { - buf := bytes.NewBuffer(testBadDurationToml) - - result := testBadDuration{} - err := NewDecoder(buf).Decode(&result) - if err == nil { - t.Fatal("expected bad duration error") - } -} - -var testCamelCaseKeyToml = []byte(`fooBar = 10`) - -func TestUnmarshalCamelCaseKey(t *testing.T) { - var x struct { - FooBar int - B int - } - - if err := Unmarshal(testCamelCaseKeyToml, &x); err != nil { - t.Fatal(err) - } - - if x.FooBar != 10 { - t.Fatal("Did not set camelCase'd key") - } -} - -func TestUnmarshalNegativeUint(t *testing.T) { - type check struct{ U uint } - - tree, _ := Load("u = -1") - err := tree.Unmarshal(&check{}) - if err.Error() != "(1, 1): -1(int64) is negative so does not fit in uint" { - t.Error("expect err:(1, 1): -1(int64) is negative so does not fit in uint but got:", err) - } -} - -func TestUnmarshalCheckConversionFloatInt(t *testing.T) { - type conversionCheck struct { - U uint - I int - F float64 - } - - treeU, _ := Load("u = 1e300") - treeI, _ := Load("i = 1e300") - treeF, _ := Load("f = 9223372036854775806") - - errU := treeU.Unmarshal(&conversionCheck{}) - errI := treeI.Unmarshal(&conversionCheck{}) - errF := treeF.Unmarshal(&conversionCheck{}) - - if errU.Error() != "(1, 1): Can't convert 1e+300(float64) to uint" { - t.Error("expect err:(1, 1): Can't convert 1e+300(float64) to uint but got:", errU) - } - if errI.Error() != "(1, 1): Can't convert 1e+300(float64) to int" { - t.Error("expect err:(1, 1): Can't convert 1e+300(float64) to int but got:", errI) - } - if errF.Error() != "(1, 1): Can't convert 9223372036854775806(int64) to float64" { - t.Error("expect err:(1, 1): Can't convert 9223372036854775806(int64) to float64 but got:", errF) - } -} - -func TestUnmarshalOverflow(t *testing.T) { - type overflow struct { - U8 uint8 - I8 int8 - F32 float32 - } - - treeU8, _ := Load("u8 = 300") - treeI8, _ := Load("i8 = 300") - treeF32, _ := Load("f32 = 1e300") - - errU8 := treeU8.Unmarshal(&overflow{}) - errI8 := treeI8.Unmarshal(&overflow{}) - errF32 := treeF32.Unmarshal(&overflow{}) - - if errU8.Error() != "(1, 1): 300(int64) would overflow uint8" { - t.Error("expect err:(1, 1): 300(int64) would overflow uint8 but got:", errU8) - } - if errI8.Error() != "(1, 1): 300(int64) would overflow int8" { - t.Error("expect err:(1, 1): 300(int64) would overflow int8 but got:", errI8) - } - if errF32.Error() != "(1, 1): 1e+300(float64) would overflow float32" { - t.Error("expect err:(1, 1): 1e+300(float64) would overflow float32 but got:", errF32) - } -} - -func TestUnmarshalDefault(t *testing.T) { - type EmbeddedStruct struct { - StringField string `default:"c"` - } - - type aliasUint uint - - var doc struct { - StringField string `default:"a"` - BoolField bool `default:"true"` - UintField uint `default:"1"` - Uint8Field uint8 `default:"8"` - Uint16Field uint16 `default:"16"` - Uint32Field uint32 `default:"32"` - Uint64Field uint64 `default:"64"` - IntField int `default:"-1"` - Int8Field int8 `default:"-8"` - Int16Field int16 `default:"-16"` - Int32Field int32 `default:"-32"` - Int64Field int64 `default:"-64"` - Float32Field float32 `default:"32.1"` - Float64Field float64 `default:"64.1"` - DurationField time.Duration `default:"120ms"` - DurationField2 time.Duration `default:"120000000"` - NonEmbeddedStruct struct { - StringField string `default:"b"` - } - EmbeddedStruct - AliasUintField aliasUint `default:"1000"` - } - - err := Unmarshal([]byte(``), &doc) - if err != nil { - t.Fatal(err) - } - if doc.BoolField != true { - t.Errorf("BoolField should be true, not %t", doc.BoolField) - } - if doc.StringField != "a" { - t.Errorf("StringField should be \"a\", not %s", doc.StringField) - } - if doc.UintField != 1 { - t.Errorf("UintField should be 1, not %d", doc.UintField) - } - if doc.Uint8Field != 8 { - t.Errorf("Uint8Field should be 8, not %d", doc.Uint8Field) - } - if doc.Uint16Field != 16 { - t.Errorf("Uint16Field should be 16, not %d", doc.Uint16Field) - } - if doc.Uint32Field != 32 { - t.Errorf("Uint32Field should be 32, not %d", doc.Uint32Field) - } - if doc.Uint64Field != 64 { - t.Errorf("Uint64Field should be 64, not %d", doc.Uint64Field) - } - if doc.IntField != -1 { - t.Errorf("IntField should be -1, not %d", doc.IntField) - } - if doc.Int8Field != -8 { - t.Errorf("Int8Field should be -8, not %d", doc.Int8Field) - } - if doc.Int16Field != -16 { - t.Errorf("Int16Field should be -16, not %d", doc.Int16Field) - } - if doc.Int32Field != -32 { - t.Errorf("Int32Field should be -32, not %d", doc.Int32Field) - } - if doc.Int64Field != -64 { - t.Errorf("Int64Field should be -64, not %d", doc.Int64Field) - } - if doc.Float32Field != 32.1 { - t.Errorf("Float32Field should be 32.1, not %f", doc.Float32Field) - } - if doc.Float64Field != 64.1 { - t.Errorf("Float64Field should be 64.1, not %f", doc.Float64Field) - } - if doc.DurationField != 120*time.Millisecond { - t.Errorf("DurationField should be 120ms, not %s", doc.DurationField.String()) - } - if doc.DurationField2 != 120*time.Millisecond { - t.Errorf("DurationField2 should be 120000000, not %d", doc.DurationField2) - } - if doc.NonEmbeddedStruct.StringField != "b" { - t.Errorf("StringField should be \"b\", not %s", doc.NonEmbeddedStruct.StringField) - } - if doc.EmbeddedStruct.StringField != "c" { - t.Errorf("StringField should be \"c\", not %s", doc.EmbeddedStruct.StringField) - } - if doc.AliasUintField != 1000 { - t.Errorf("AliasUintField should be 1000, not %d", doc.AliasUintField) - } -} - -func TestUnmarshalDefaultFailureBool(t *testing.T) { - var doc struct { - Field bool `default:"blah"` - } - - err := Unmarshal([]byte(``), &doc) - if err == nil { - t.Fatal("should error") - } -} - -func TestUnmarshalDefaultFailureInt(t *testing.T) { - var doc struct { - Field int `default:"blah"` - } - - err := Unmarshal([]byte(``), &doc) - if err == nil { - t.Fatal("should error") - } -} - -func TestUnmarshalDefaultFailureInt64(t *testing.T) { - var doc struct { - Field int64 `default:"blah"` - } - - err := Unmarshal([]byte(``), &doc) - if err == nil { - t.Fatal("should error") - } -} - -func TestUnmarshalDefaultFailureFloat64(t *testing.T) { - var doc struct { - Field float64 `default:"blah"` - } - - err := Unmarshal([]byte(``), &doc) - if err == nil { - t.Fatal("should error") - } -} - -func TestUnmarshalDefaultFailureDuration(t *testing.T) { - var doc struct { - Field time.Duration `default:"blah"` - } - - err := Unmarshal([]byte(``), &doc) - if err == nil { - t.Fatal("should error") - } -} - -func TestUnmarshalDefaultFailureUnsupported(t *testing.T) { - var doc struct { - Field struct{} `default:"blah"` - } - - err := Unmarshal([]byte(``), &doc) - if err == nil { - t.Fatal("should error") - } -} - -func TestMarshalNestedAnonymousStructs(t *testing.T) { - type Embedded struct { - Value string `toml:"value"` - Top struct { - Value string `toml:"value"` - } `toml:"top"` - } - - type Named struct { - Value string `toml:"value"` - } - - var doc struct { - Embedded - Named `toml:"named"` - Anonymous struct { - Value string `toml:"value"` - } `toml:"anonymous"` - } - - expected := `value = "" - -[anonymous] - value = "" - -[named] - value = "" - -[top] - value = "" -` - - result, err := Marshal(doc) - if err != nil { - t.Fatalf("unexpected error: %s", err.Error()) - } - if !bytes.Equal(result, []byte(expected)) { - t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, string(result)) - } -} - -func TestEncoderPromoteNestedAnonymousStructs(t *testing.T) { - type Embedded struct { - Value string `toml:"value"` - } - - var doc struct { - Embedded - } - - expected := ` -[Embedded] - value = "" -` - var buf bytes.Buffer - if err := NewEncoder(&buf).PromoteAnonymous(true).Encode(doc); err != nil { - t.Fatalf("unexpected error: %s", err.Error()) - } - if !bytes.Equal(buf.Bytes(), []byte(expected)) { - t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, buf.String()) - } -} - -func TestMarshalNestedAnonymousStructs_DuplicateField(t *testing.T) { - type Embedded struct { - Value string `toml:"value"` - Top struct { - Value string `toml:"value"` - } `toml:"top"` - } - - var doc struct { - Value string `toml:"value"` - Embedded - } - doc.Embedded.Value = "shadowed" - doc.Value = "shadows" - - expected := `value = "shadows" - -[top] - value = "" -` - - result, err := Marshal(doc) - if err != nil { - t.Fatalf("unexpected error: %s", err.Error()) - } - if !bytes.Equal(result, []byte(expected)) { - t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, string(result)) - } -} - -func TestUnmarshalNestedAnonymousStructs(t *testing.T) { - type Nested struct { - Value string `toml:"nested_field"` - } - type Deep struct { - Nested - } - type Document struct { - Deep - Value string `toml:"own_field"` - } - - var doc Document - - err := Unmarshal([]byte(`nested_field = "nested value"`+"\n"+`own_field = "own value"`), &doc) - if err != nil { - t.Fatal("should not error") - } - if doc.Value != "own value" || doc.Nested.Value != "nested value" { - t.Fatal("unexpected values") - } -} - -func TestUnmarshalNestedAnonymousStructs_Controversial(t *testing.T) { - type Nested struct { - Value string `toml:"nested"` - } - type Deep struct { - Nested - } - type Document struct { - Deep - Value string `toml:"own"` - } - - var doc Document - - err := Unmarshal([]byte(`nested = "nested value"`+"\n"+`own = "own value"`), &doc) - if err == nil { - t.Fatal("should error") - } -} - -type unexportedFieldPreservationTest struct { - Exported string `toml:"exported"` - unexported string - Nested1 unexportedFieldPreservationTestNested `toml:"nested1"` - Nested2 *unexportedFieldPreservationTestNested `toml:"nested2"` - Nested3 *unexportedFieldPreservationTestNested `toml:"nested3"` - Slice1 []unexportedFieldPreservationTestNested `toml:"slice1"` - Slice2 []*unexportedFieldPreservationTestNested `toml:"slice2"` -} - -type unexportedFieldPreservationTestNested struct { - Exported1 string `toml:"exported1"` - unexported1 string -} - -func TestUnmarshalPreservesUnexportedFields(t *testing.T) { - toml := ` - exported = "visible" - unexported = "ignored" - - [nested1] - exported1 = "visible1" - unexported1 = "ignored1" - - [nested2] - exported1 = "visible2" - unexported1 = "ignored2" - - [nested3] - exported1 = "visible3" - unexported1 = "ignored3" - - [[slice1]] - exported1 = "visible3" - - [[slice1]] - exported1 = "visible4" - - [[slice2]] - exported1 = "visible5" - ` - - t.Run("unexported field should not be set from toml", func(t *testing.T) { - var actual unexportedFieldPreservationTest - err := Unmarshal([]byte(toml), &actual) - - if err != nil { - t.Fatal("did not expect an error") - } - - expect := unexportedFieldPreservationTest{ - Exported: "visible", - unexported: "", - Nested1: unexportedFieldPreservationTestNested{"visible1", ""}, - Nested2: &unexportedFieldPreservationTestNested{"visible2", ""}, - Nested3: &unexportedFieldPreservationTestNested{"visible3", ""}, - Slice1: []unexportedFieldPreservationTestNested{ - {Exported1: "visible3"}, - {Exported1: "visible4"}, - }, - Slice2: []*unexportedFieldPreservationTestNested{ - {Exported1: "visible5"}, - }, - } - - if !reflect.DeepEqual(actual, expect) { - t.Fatalf("%+v did not equal %+v", actual, expect) - } - }) - - t.Run("unexported field should be preserved", func(t *testing.T) { - actual := unexportedFieldPreservationTest{ - Exported: "foo", - unexported: "bar", - Nested1: unexportedFieldPreservationTestNested{"baz", "bax"}, - Nested2: nil, - Nested3: &unexportedFieldPreservationTestNested{"baz", "bax"}, - } - err := Unmarshal([]byte(toml), &actual) - - if err != nil { - t.Fatal("did not expect an error") - } - - expect := unexportedFieldPreservationTest{ - Exported: "visible", - unexported: "bar", - Nested1: unexportedFieldPreservationTestNested{"visible1", "bax"}, - Nested2: &unexportedFieldPreservationTestNested{"visible2", ""}, - Nested3: &unexportedFieldPreservationTestNested{"visible3", "bax"}, - Slice1: []unexportedFieldPreservationTestNested{ - {Exported1: "visible3"}, - {Exported1: "visible4"}, - }, - Slice2: []*unexportedFieldPreservationTestNested{ - {Exported1: "visible5"}, - }, - } - - if !reflect.DeepEqual(actual, expect) { - t.Fatalf("%+v did not equal %+v", actual, expect) - } - }) -} - -func TestTreeMarshal(t *testing.T) { - cases := [][]byte{ - basicTestToml, - marshalTestToml, - emptyTestToml, - pointerTestToml, - } - for _, expected := range cases { - t.Run("", func(t *testing.T) { - tree, err := LoadBytes(expected) - if err != nil { - t.Fatal(err) - } - result, err := tree.Marshal() - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(result, expected) { - t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) - } - }) - } -} - -func TestMarshalArrays(t *testing.T) { - cases := []struct { - Data interface{} - Expected string - }{ - { - Data: struct { - XY [2]int - }{ - XY: [2]int{1, 2}, - }, - Expected: `XY = [1, 2] -`, - }, - { - Data: struct { - XY [1][2]int - }{ - XY: [1][2]int{{1, 2}}, - }, - Expected: `XY = [[1, 2]] -`, - }, - { - Data: struct { - XY [1][]int - }{ - XY: [1][]int{{1, 2}}, - }, - Expected: `XY = [[1, 2]] -`, - }, - { - Data: struct { - XY [][2]int - }{ - XY: [][2]int{{1, 2}}, - }, - Expected: `XY = [[1, 2]] -`, - }, - } - for _, tc := range cases { - t.Run("", func(t *testing.T) { - result, err := Marshal(tc.Data) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(result, []byte(tc.Expected)) { - t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", []byte(tc.Expected), result) - } - }) - } -} - -func TestUnmarshalLocalDate(t *testing.T) { - t.Run("ToLocalDate", func(t *testing.T) { - type dateStruct struct { - Date LocalDate - } - - toml := `date = 1979-05-27` - - var obj dateStruct - - err := Unmarshal([]byte(toml), &obj) - - if err != nil { - t.Fatal(err) - } - - if obj.Date.Year != 1979 { - t.Errorf("expected year 1979, got %d", obj.Date.Year) - } - if obj.Date.Month != 5 { - t.Errorf("expected month 5, got %d", obj.Date.Month) - } - if obj.Date.Day != 27 { - t.Errorf("expected day 27, got %d", obj.Date.Day) - } - }) - - t.Run("ToLocalDate", func(t *testing.T) { - type dateStruct struct { - Date time.Time - } - - toml := `date = 1979-05-27` - - var obj dateStruct - - err := Unmarshal([]byte(toml), &obj) - - if err != nil { - t.Fatal(err) - } - - if obj.Date.Year() != 1979 { - t.Errorf("expected year 1979, got %d", obj.Date.Year()) - } - if obj.Date.Month() != 5 { - t.Errorf("expected month 5, got %d", obj.Date.Month()) - } - if obj.Date.Day() != 27 { - t.Errorf("expected day 27, got %d", obj.Date.Day()) - } - }) -} - -func TestMarshalLocalDate(t *testing.T) { - type dateStruct struct { - Date LocalDate - } - - obj := dateStruct{Date: LocalDate{ - Year: 1979, - Month: 5, - Day: 27, - }} - - b, err := Marshal(obj) - - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - - got := string(b) - expected := `Date = 1979-05-27 -` - - if got != expected { - t.Errorf("expected '%s', got '%s'", expected, got) - } -} - -func TestUnmarshalLocalDateTime(t *testing.T) { - examples := []struct { - name string - in string - out LocalDateTime - }{ - { - name: "normal", - in: "1979-05-27T07:32:00", - out: LocalDateTime{ - Date: LocalDate{ - Year: 1979, - Month: 5, - Day: 27, - }, - Time: LocalTime{ - Hour: 7, - Minute: 32, - Second: 0, - Nanosecond: 0, - }, - }}, - { - name: "with nanoseconds", - in: "1979-05-27T00:32:00.999999", - out: LocalDateTime{ - Date: LocalDate{ - Year: 1979, - Month: 5, - Day: 27, - }, - Time: LocalTime{ - Hour: 0, - Minute: 32, - Second: 0, - Nanosecond: 999999000, - }, - }, - }, - } - - for i, example := range examples { - toml := fmt.Sprintf(`date = %s`, example.in) - - t.Run(fmt.Sprintf("ToLocalDateTime_%d_%s", i, example.name), func(t *testing.T) { - type dateStruct struct { - Date LocalDateTime - } - - var obj dateStruct - - err := Unmarshal([]byte(toml), &obj) - - if err != nil { - t.Fatal(err) - } - - if obj.Date != example.out { - t.Errorf("expected '%s', got '%s'", example.out, obj.Date) - } - }) - - t.Run(fmt.Sprintf("ToTime_%d_%s", i, example.name), func(t *testing.T) { - type dateStruct struct { - Date time.Time - } - - var obj dateStruct - - err := Unmarshal([]byte(toml), &obj) - - if err != nil { - t.Fatal(err) - } - - if obj.Date.Year() != example.out.Date.Year { - t.Errorf("expected year %d, got %d", example.out.Date.Year, obj.Date.Year()) - } - if obj.Date.Month() != example.out.Date.Month { - t.Errorf("expected month %d, got %d", example.out.Date.Month, obj.Date.Month()) - } - if obj.Date.Day() != example.out.Date.Day { - t.Errorf("expected day %d, got %d", example.out.Date.Day, obj.Date.Day()) - } - if obj.Date.Hour() != example.out.Time.Hour { - t.Errorf("expected hour %d, got %d", example.out.Time.Hour, obj.Date.Hour()) - } - if obj.Date.Minute() != example.out.Time.Minute { - t.Errorf("expected minute %d, got %d", example.out.Time.Minute, obj.Date.Minute()) - } - if obj.Date.Second() != example.out.Time.Second { - t.Errorf("expected second %d, got %d", example.out.Time.Second, obj.Date.Second()) - } - if obj.Date.Nanosecond() != example.out.Time.Nanosecond { - t.Errorf("expected nanoseconds %d, got %d", example.out.Time.Nanosecond, obj.Date.Nanosecond()) - } - }) - } -} - -func TestMarshalLocalDateTime(t *testing.T) { - type dateStruct struct { - DateTime LocalDateTime - } - - examples := []struct { - name string - in LocalDateTime - out string - }{ - { - name: "normal", - out: "DateTime = 1979-05-27T07:32:00\n", - in: LocalDateTime{ - Date: LocalDate{ - Year: 1979, - Month: 5, - Day: 27, - }, - Time: LocalTime{ - Hour: 7, - Minute: 32, - Second: 0, - Nanosecond: 0, - }, - }}, - { - name: "with nanoseconds", - out: "DateTime = 1979-05-27T00:32:00.999999000\n", - in: LocalDateTime{ - Date: LocalDate{ - Year: 1979, - Month: 5, - Day: 27, - }, - Time: LocalTime{ - Hour: 0, - Minute: 32, - Second: 0, - Nanosecond: 999999000, - }, - }, - }, - } - - for i, example := range examples { - t.Run(fmt.Sprintf("%d_%s", i, example.name), func(t *testing.T) { - obj := dateStruct{ - DateTime: example.in, - } - b, err := Marshal(obj) - - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - - got := string(b) - - if got != example.out { - t.Errorf("expected '%s', got '%s'", example.out, got) - } - }) - } -} - -func TestUnmarshalLocalTime(t *testing.T) { - examples := []struct { - name string - in string - out LocalTime - }{ - { - name: "normal", - in: "07:32:00", - out: LocalTime{ - Hour: 7, - Minute: 32, - Second: 0, - Nanosecond: 0, - }, - }, - { - name: "with nanoseconds", - in: "00:32:00.999999", - out: LocalTime{ - Hour: 0, - Minute: 32, - Second: 0, - Nanosecond: 999999000, - }, - }, - } - - for i, example := range examples { - toml := fmt.Sprintf(`Time = %s`, example.in) - - t.Run(fmt.Sprintf("ToLocalTime_%d_%s", i, example.name), func(t *testing.T) { - type dateStruct struct { - Time LocalTime - } - - var obj dateStruct - - err := Unmarshal([]byte(toml), &obj) - - if err != nil { - t.Fatal(err) - } - - if obj.Time != example.out { - t.Errorf("expected '%s', got '%s'", example.out, obj.Time) - } - }) - } -} - -func TestMarshalLocalTime(t *testing.T) { - type timeStruct struct { - Time LocalTime - } - - examples := []struct { - name string - in LocalTime - out string - }{ - { - name: "normal", - out: "Time = 07:32:00\n", - in: LocalTime{ - Hour: 7, - Minute: 32, - Second: 0, - Nanosecond: 0, - }}, - { - name: "with nanoseconds", - out: "Time = 00:32:00.999999000\n", - in: LocalTime{ - Hour: 0, - Minute: 32, - Second: 0, - Nanosecond: 999999000, - }, - }, - } - - for i, example := range examples { - t.Run(fmt.Sprintf("%d_%s", i, example.name), func(t *testing.T) { - obj := timeStruct{ - Time: example.in, - } - b, err := Marshal(obj) - - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - - got := string(b) - - if got != example.out { - t.Errorf("expected '%s', got '%s'", example.out, got) - } - }) - } -} - -// test case for issue #339 -func TestUnmarshalSameInnerField(t *testing.T) { - type InterStruct2 struct { - Test string - Name string - Age int - } - type Inter2 struct { - Name string - Age int - InterStruct2 InterStruct2 - } - type Server struct { - Name string `toml:"name"` - Inter2 Inter2 `toml:"inter2"` - } - - var server Server - - if err := Unmarshal([]byte(`name = "123" -[inter2] -name = "inter2" -age = 222`), &server); err == nil { - expected := Server{ - Name: "123", - Inter2: Inter2{ - Name: "inter2", - Age: 222, - }, - } - if !reflect.DeepEqual(server, expected) { - t.Errorf("Bad unmarshal: expected %v, got %v", expected, server) - } - } else { - t.Fatalf("unexpected error: %v", err) - } -} - -func TestMarshalInterface(t *testing.T) { - type InnerStruct struct { - InnerField string - } - - type OuterStruct struct { - PrimitiveField interface{} - ArrayField interface{} - StructArrayField interface{} - MapField map[string]interface{} - StructField interface{} - PointerField interface{} - NilField interface{} - InterfacePointerField *interface{} - } - - expected := []byte(`ArrayField = [1, 2, 3] -InterfacePointerField = "hello world" -PrimitiveField = "string" - -[MapField] - key1 = "value1" - key2 = false - - [MapField.key3] - InnerField = "value3" - -[PointerField] - InnerField = "yyy" - -[[StructArrayField]] - InnerField = "s1" - -[[StructArrayField]] - InnerField = "s2" - -[StructField] - InnerField = "xxx" -`) - - var h interface{} = "hello world" - if result, err := Marshal(OuterStruct{ - "string", - []int{1, 2, 3}, - []InnerStruct{{"s1"}, {"s2"}}, - map[string]interface{}{ - "key1": "value1", - "key2": false, - "key3": InnerStruct{"value3"}, - "nil value": nil, - }, - InnerStruct{ - "xxx", - }, - &InnerStruct{ - "yyy", - }, - nil, - &h, - }); err == nil { - if !bytes.Equal(result, expected) { - t.Errorf("Bad marshal: expected\n----\n%s\n----\ngot\n----\n%s\n----\n", expected, result) - } - } else { - t.Fatal(err) - } -} - -func TestUnmarshalToNilInterface(t *testing.T) { - toml := []byte(` -PrimitiveField = "Hello" -ArrayField = [1,2,3] -InterfacePointerField = "World" - -[StructField] -Field1 = 123 -Field2 = "Field2" - -[MapField] -MapField1 = [4,5,6] -MapField2 = {A = "A"} -MapField3 = false - -[[StructArrayField]] -Name = "Allen" -Age = 20 - -[[StructArrayField]] -Name = "Jack" -Age = 23 -`) - - type OuterStruct struct { - PrimitiveField interface{} - ArrayField interface{} - StructArrayField interface{} - MapField map[string]interface{} - StructField interface{} - NilField interface{} - InterfacePointerField *interface{} - } - - var s interface{} = "World" - expected := OuterStruct{ - PrimitiveField: "Hello", - ArrayField: []interface{}{int64(1), int64(2), int64(3)}, - StructField: map[string]interface{}{ - "Field1": int64(123), - "Field2": "Field2", - }, - MapField: map[string]interface{}{ - "MapField1": []interface{}{int64(4), int64(5), int64(6)}, - "MapField2": map[string]interface{}{ - "A": "A", - }, - "MapField3": false, - }, - NilField: nil, - InterfacePointerField: &s, - StructArrayField: []map[string]interface{}{ - { - "Name": "Allen", - "Age": int64(20), - }, - { - "Name": "Jack", - "Age": int64(23), - }, - }, - } - actual := OuterStruct{} - if err := Unmarshal(toml, &actual); err == nil { - if !reflect.DeepEqual(actual, expected) { - t.Errorf("Bad unmarshal: expected %v, got %v", expected, actual) - } - } else { - t.Fatal(err) - } -} - -func TestUnmarshalToNonNilInterface(t *testing.T) { - toml := []byte(` -PrimitiveField = "Allen" -ArrayField = [1,2,3] - -[StructField] -InnerField = "After1" - -[PointerField] -InnerField = "After2" - -[InterfacePointerField] -InnerField = "After" - -[MapField] -MapField1 = [4,5,6] -MapField2 = {A = "A"} -MapField3 = false - -[[StructArrayField]] -InnerField = "After3" - -[[StructArrayField]] -InnerField = "After4" -`) - type InnerStruct struct { - InnerField interface{} - } - - type OuterStruct struct { - PrimitiveField interface{} - ArrayField interface{} - StructArrayField interface{} - MapField map[string]interface{} - StructField interface{} - PointerField interface{} - NilField interface{} - InterfacePointerField *interface{} - } - - var s interface{} = InnerStruct{"After"} - expected := OuterStruct{ - PrimitiveField: "Allen", - ArrayField: []int{1, 2, 3}, - StructField: InnerStruct{InnerField: "After1"}, - MapField: map[string]interface{}{ - "MapField1": []interface{}{int64(4), int64(5), int64(6)}, - "MapField2": map[string]interface{}{ - "A": "A", - }, - "MapField3": false, - }, - PointerField: &InnerStruct{InnerField: "After2"}, - NilField: nil, - InterfacePointerField: &s, - StructArrayField: []InnerStruct{ - {InnerField: "After3"}, - {InnerField: "After4"}, - }, - } - actual := OuterStruct{ - PrimitiveField: "aaa", - ArrayField: []int{100, 200, 300, 400}, - StructField: InnerStruct{InnerField: "Before1"}, - MapField: map[string]interface{}{ - "MapField1": []int{4, 5, 6}, - "MapField2": map[string]string{ - "B": "BBB", - }, - "MapField3": true, - }, - PointerField: &InnerStruct{InnerField: "Before2"}, - NilField: nil, - InterfacePointerField: &s, - StructArrayField: []InnerStruct{ - {InnerField: "Before3"}, - {InnerField: "Before4"}, - }, - } - if err := Unmarshal(toml, &actual); err == nil { - if !reflect.DeepEqual(actual, expected) { - t.Errorf("Bad unmarshal: expected %v, got %v", expected, actual) - } - } else { - t.Fatal(err) - } -} - -func TestUnmarshalEmbedTree(t *testing.T) { - toml := []byte(` -OuterField1 = "Out" -OuterField2 = 1024 - -[TreeField] -InnerField1 = "In" -InnerField2 = 2048 - - [TreeField.EmbedStruct] - EmbedField = "Embed" - -`) - type InnerStruct struct { - InnerField1 string - InnerField2 int - EmbedStruct struct { - EmbedField string - } - } - - type OuterStruct struct { - OuterField1 string - OuterField2 int - TreeField *Tree - } - - out := OuterStruct{} - actual := InnerStruct{} - expected := InnerStruct{ - "In", - 2048, - struct { - EmbedField string - }{ - EmbedField: "Embed", - }, - } - if err := Unmarshal(toml, &out); err != nil { - t.Fatal(err) - } - if err := out.TreeField.Unmarshal(&actual); err != nil { - t.Fatal(err) - } - - if !reflect.DeepEqual(actual, expected) { - t.Errorf("Bad unmarshal: expected %v, got %v", expected, actual) - } -} - -func TestMarshalNil(t *testing.T) { - if _, err := Marshal(nil); err == nil { - t.Errorf("Expected err from nil marshal") - } - if _, err := Marshal((*struct{})(nil)); err == nil { - t.Errorf("Expected err from nil marshal") - } -} - -func TestUnmarshalNil(t *testing.T) { - if err := Unmarshal([]byte(`whatever = "whatever"`), nil); err == nil { - t.Errorf("Expected err from nil marshal") - } - if err := Unmarshal([]byte(`whatever = "whatever"`), (*struct{})(nil)); err == nil { - t.Errorf("Expected err from nil marshal") - } -} - -var sliceTomlDemo = []byte(`str_slice = ["Howdy","Hey There"] -str_slice_ptr= ["Howdy","Hey There"] -int_slice=[1,2] -int_slice_ptr=[1,2] -[[struct_slice]] -String2="1" -[[struct_slice]] -String2="2" -[[struct_slice_ptr]] -String2="1" -[[struct_slice_ptr]] -String2="2" -`) - -type sliceStruct struct { - Slice []string ` toml:"str_slice" ` - SlicePtr *[]string ` toml:"str_slice_ptr" ` - IntSlice []int ` toml:"int_slice" ` - IntSlicePtr *[]int ` toml:"int_slice_ptr" ` - StructSlice []basicMarshalTestSubStruct ` toml:"struct_slice" ` - StructSlicePtr *[]basicMarshalTestSubStruct ` toml:"struct_slice_ptr" ` -} - -type arrayStruct struct { - Slice [4]string ` toml:"str_slice" ` - SlicePtr *[4]string ` toml:"str_slice_ptr" ` - IntSlice [4]int ` toml:"int_slice" ` - IntSlicePtr *[4]int ` toml:"int_slice_ptr" ` - StructSlice [4]basicMarshalTestSubStruct ` toml:"struct_slice" ` - StructSlicePtr *[4]basicMarshalTestSubStruct ` toml:"struct_slice_ptr" ` -} - -type arrayTooSmallStruct struct { - Slice [1]string ` toml:"str_slice" ` - StructSlice [1]basicMarshalTestSubStruct ` toml:"struct_slice" ` -} - -func TestUnmarshalSlice(t *testing.T) { - tree, _ := LoadBytes(sliceTomlDemo) - tree, _ = TreeFromMap(tree.ToMap()) - - var actual sliceStruct - err := tree.Unmarshal(&actual) - if err != nil { - t.Error("shound not err", err) - } - expected := sliceStruct{ - Slice: []string{"Howdy", "Hey There"}, - SlicePtr: &[]string{"Howdy", "Hey There"}, - IntSlice: []int{1, 2}, - IntSlicePtr: &[]int{1, 2}, - StructSlice: []basicMarshalTestSubStruct{{"1"}, {"2"}}, - StructSlicePtr: &[]basicMarshalTestSubStruct{{"1"}, {"2"}}, - } - if !reflect.DeepEqual(actual, expected) { - t.Errorf("Bad unmarshal: expected %v, got %v", expected, actual) - } - -} - -func TestUnmarshalSliceFail(t *testing.T) { - tree, _ := TreeFromMap(map[string]interface{}{ - "str_slice": []int{1, 2}, - }) - - var actual sliceStruct - err := tree.Unmarshal(&actual) - if err.Error() != "(0, 0): Can't convert 1(int64) to string" { - t.Error("expect err:(0, 0): Can't convert 1(int64) to string but got ", err) - } -} - -func TestUnmarshalSliceFail2(t *testing.T) { - tree, _ := Load(`str_slice=[1,2]`) - - var actual sliceStruct - err := tree.Unmarshal(&actual) - if err.Error() != "(1, 1): Can't convert 1(int64) to string" { - t.Error("expect err:(1, 1): Can't convert 1(int64) to string but got ", err) - } - -} - -func TestMarshalMixedTypeArray(t *testing.T) { - type InnerStruct struct { - IntField int - StrField string - } - - type TestStruct struct { - ArrayField []interface{} - } - - expected := []byte(`ArrayField = [3.14, 100, true, "hello world", { IntField = 100, StrField = "inner1" }, [{ IntField = 200, StrField = "inner2" }, { IntField = 300, StrField = "inner3" }]] -`) - - if result, err := Marshal(TestStruct{ - ArrayField: []interface{}{ - 3.14, - 100, - true, - "hello world", - InnerStruct{ - IntField: 100, - StrField: "inner1", - }, - []InnerStruct{ - {IntField: 200, StrField: "inner2"}, - {IntField: 300, StrField: "inner3"}, - }, - }, - }); err == nil { - if !bytes.Equal(result, expected) { - t.Errorf("Bad marshal: expected\n----\n%s\n----\ngot\n----\n%s\n----\n", expected, result) - } - } else { - t.Fatal(err) - } -} - -func TestUnmarshalMixedTypeArray(t *testing.T) { - type TestStruct struct { - ArrayField []interface{} - } - - toml := []byte(`ArrayField = [3.14,100,true,"hello world",{Field = "inner1"},[{Field = "inner2"},{Field = "inner3"}]] -`) - - actual := TestStruct{} - expected := TestStruct{ - ArrayField: []interface{}{ - 3.14, - int64(100), - true, - "hello world", - map[string]interface{}{ - "Field": "inner1", - }, - []map[string]interface{}{ - {"Field": "inner2"}, - {"Field": "inner3"}, - }, - }, - } - - if err := Unmarshal(toml, &actual); err == nil { - if !reflect.DeepEqual(actual, expected) { - t.Errorf("Bad unmarshal: expected %#v, got %#v", expected, actual) - } - } else { - t.Fatal(err) - } -} - -func TestUnmarshalArray(t *testing.T) { - var tree *Tree - var err error - - tree, _ = LoadBytes(sliceTomlDemo) - var actual1 arrayStruct - err = tree.Unmarshal(&actual1) - if err != nil { - t.Error("shound not err", err) - } - - tree, _ = TreeFromMap(tree.ToMap()) - var actual2 arrayStruct - err = tree.Unmarshal(&actual2) - if err != nil { - t.Error("shound not err", err) - } - - expected := arrayStruct{ - Slice: [4]string{"Howdy", "Hey There"}, - SlicePtr: &[4]string{"Howdy", "Hey There"}, - IntSlice: [4]int{1, 2}, - IntSlicePtr: &[4]int{1, 2}, - StructSlice: [4]basicMarshalTestSubStruct{{"1"}, {"2"}}, - StructSlicePtr: &[4]basicMarshalTestSubStruct{{"1"}, {"2"}}, - } - if !reflect.DeepEqual(actual1, expected) { - t.Errorf("Bad unmarshal: expected %v, got %v", expected, actual1) - } - if !reflect.DeepEqual(actual2, expected) { - t.Errorf("Bad unmarshal: expected %v, got %v", expected, actual2) - } -} - -func TestUnmarshalArrayFail(t *testing.T) { - tree, _ := TreeFromMap(map[string]interface{}{ - "str_slice": []string{"Howdy", "Hey There"}, - }) - - var actual arrayTooSmallStruct - err := tree.Unmarshal(&actual) - if err.Error() != "(0, 0): unmarshal: TOML array length (2) exceeds destination array length (1)" { - t.Error("expect err:(0, 0): unmarshal: TOML array length (2) exceeds destination array length (1) but got ", err) - } -} - -func TestUnmarshalArrayFail2(t *testing.T) { - tree, _ := Load(`str_slice=["Howdy","Hey There"]`) - - var actual arrayTooSmallStruct - err := tree.Unmarshal(&actual) - if err.Error() != "(1, 1): unmarshal: TOML array length (2) exceeds destination array length (1)" { - t.Error("expect err:(1, 1): unmarshal: TOML array length (2) exceeds destination array length (1) but got ", err) - } -} - -func TestUnmarshalArrayFail3(t *testing.T) { - tree, _ := Load(`[[struct_slice]] -String2="1" -[[struct_slice]] -String2="2"`) - - var actual arrayTooSmallStruct - err := tree.Unmarshal(&actual) - if err.Error() != "(3, 1): unmarshal: TOML array length (2) exceeds destination array length (1)" { - t.Error("expect err:(3, 1): unmarshal: TOML array length (2) exceeds destination array length (1) but got ", err) - } -} - -func TestDecoderStrict(t *testing.T) { - input := ` -[decoded] - key = "" - -[undecoded] - key = "" - - [undecoded.inner] - key = "" - - [[undecoded.array]] - key = "" - - [[undecoded.array]] - key = "" - -` - var doc struct { - Decoded struct { - Key string - } - } - - expected := `undecoded keys: ["undecoded.array.0.key" "undecoded.array.1.key" "undecoded.inner.key" "undecoded.key"]` - - err := NewDecoder(bytes.NewReader([]byte(input))).Strict(true).Decode(&doc) - if err == nil { - t.Error("expected error, got none") - } else if err.Error() != expected { - t.Errorf("expect err: %s, got: %s", expected, err.Error()) - } - - if err := NewDecoder(bytes.NewReader([]byte(input))).Decode(&doc); err != nil { - t.Errorf("unexpected err: %s", err) - } - - var m map[string]interface{} - if err := NewDecoder(bytes.NewReader([]byte(input))).Decode(&m); err != nil { - t.Errorf("unexpected err: %s", err) - } -} - -func TestDecoderStrictValid(t *testing.T) { - input := ` -[decoded] - key = "" -` - var doc struct { - Decoded struct { - Key string - } - } - - err := NewDecoder(bytes.NewReader([]byte(input))).Strict(true).Decode(&doc) - if err != nil { - t.Fatal("unexpected error:", err) - } -} - -type docUnmarshalTOML struct { - Decoded struct { - Key string - } -} - -func (d *docUnmarshalTOML) UnmarshalTOML(i interface{}) error { - if iMap, ok := i.(map[string]interface{}); !ok { - return fmt.Errorf("type assertion error: wants %T, have %T", map[string]interface{}{}, i) - } else if key, ok := iMap["key"]; !ok { - return fmt.Errorf("key '%s' not in map", "key") - } else if keyString, ok := key.(string); !ok { - return fmt.Errorf("type assertion error: wants %T, have %T", "", key) - } else { - d.Decoded.Key = keyString - } - return nil -} - -func TestDecoderStrictCustomUnmarshal(t *testing.T) { - input := `key = "ok"` - var doc docUnmarshalTOML - err := NewDecoder(bytes.NewReader([]byte(input))).Strict(true).Decode(&doc) - if err != nil { - t.Fatal("unexpected error:", err) - } - if doc.Decoded.Key != "ok" { - t.Errorf("Bad unmarshal: expected ok, got %v", doc.Decoded.Key) - } -} - -type parent struct { - Doc docUnmarshalTOML - DocPointer *docUnmarshalTOML -} - -func TestCustomUnmarshal(t *testing.T) { - input := ` -[Doc] - key = "ok1" -[DocPointer] - key = "ok2" -` - - var d parent - if err := Unmarshal([]byte(input), &d); err != nil { - t.Fatalf("unexpected err: %s", err.Error()) - } - if d.Doc.Decoded.Key != "ok1" { - t.Errorf("Bad unmarshal: expected ok, got %v", d.Doc.Decoded.Key) - } - if d.DocPointer.Decoded.Key != "ok2" { - t.Errorf("Bad unmarshal: expected ok, got %v", d.DocPointer.Decoded.Key) - } -} - -func TestCustomUnmarshalError(t *testing.T) { - input := ` -[Doc] - key = 1 -[DocPointer] - key = "ok2" -` - - expected := "(2, 1): unmarshal toml: type assertion error: wants string, have int64" - - var d parent - err := Unmarshal([]byte(input), &d) - if err == nil { - t.Error("expected error, got none") - } else if err.Error() != expected { - t.Errorf("expect err: %s, got: %s", expected, err.Error()) - } -} - -type intWrapper struct { - Value int -} - -func (w *intWrapper) UnmarshalText(text []byte) error { - var err error - if w.Value, err = strconv.Atoi(string(text)); err == nil { - return nil - } - if b, err := strconv.ParseBool(string(text)); err == nil { - if b { - w.Value = 1 - } - return nil - } - if f, err := strconv.ParseFloat(string(text), 32); err == nil { - w.Value = int(f) - return nil - } - return fmt.Errorf("unsupported: %s", text) -} - -func TestTextUnmarshal(t *testing.T) { - var doc struct { - UnixTime intWrapper - Version *intWrapper - - Bool intWrapper - Int intWrapper - Float intWrapper - } - - input := ` -UnixTime = "12" -Version = "42" -Bool = true -Int = 21 -Float = 2.0 -` - - if err := Unmarshal([]byte(input), &doc); err != nil { - t.Fatalf("unexpected err: %s", err.Error()) - } - if doc.UnixTime.Value != 12 { - t.Fatalf("expected UnixTime: 12 got: %d", doc.UnixTime.Value) - } - if doc.Version.Value != 42 { - t.Fatalf("expected Version: 42 got: %d", doc.Version.Value) - } - if doc.Bool.Value != 1 { - t.Fatalf("expected Bool: 1 got: %d", doc.Bool.Value) - } - if doc.Int.Value != 21 { - t.Fatalf("expected Int: 21 got: %d", doc.Int.Value) - } - if doc.Float.Value != 2 { - t.Fatalf("expected Float: 2 got: %d", doc.Float.Value) - } -} - -func TestTextUnmarshalError(t *testing.T) { - var doc struct { - Failer intWrapper - } - - input := `Failer = "hello"` - if err := Unmarshal([]byte(input), &doc); err == nil { - t.Fatalf("expected err, got none") - } -} - -// issue406 -func TestPreserveNotEmptyField(t *testing.T) { - toml := []byte(`Field1 = "ccc"`) - type Inner struct { - InnerField1 string - InnerField2 int - } - type TestStruct struct { - Field1 string - Field2 int - Field3 Inner - } - - actual := TestStruct{ - "aaa", - 100, - Inner{ - "bbb", - 200, - }, - } - - expected := TestStruct{ - "ccc", - 100, - Inner{ - "bbb", - 200, - }, - } - - err := Unmarshal(toml, &actual) - if err != nil { - t.Fatal(err) - } - - if !reflect.DeepEqual(actual, expected) { - t.Errorf("Bad unmarshal: expected %+v, got %+v", expected, actual) - } -} - -// github issue 432 -func TestUnmarshalEmptyInterface(t *testing.T) { - doc := []byte(`User = "pelletier"`) - - var v interface{} - - err := Unmarshal(doc, &v) - if err != nil { - t.Fatal(err) - } - - x, ok := v.(map[string]interface{}) - if !ok { - t.Fatal(err) - } - - if x["User"] != "pelletier" { - t.Fatalf("expected User=pelletier, but got %v", x) - } -} - -func TestUnmarshalEmptyInterfaceDeep(t *testing.T) { - doc := []byte(` -User = "pelletier" -Age = 99 - -[foo] -bar = 42 -`) - - var v interface{} - - err := Unmarshal(doc, &v) - if err != nil { - t.Fatal(err) - } - - x, ok := v.(map[string]interface{}) - if !ok { - t.Fatal(err) - } - - expected := map[string]interface{}{ - "User": "pelletier", - "Age": 99, - "foo": map[string]interface{}{ - "bar": 42, - }, - } - - reflect.DeepEqual(x, expected) -} - -type Config struct { - Key string `toml:"key"` - Obj Custom `toml:"obj"` -} - -type Custom struct { - v string -} - -func (c *Custom) UnmarshalTOML(v interface{}) error { - c.v = "called" - return nil -} - -func TestGithubIssue431(t *testing.T) { - doc := `key = "value"` - tree, err := LoadBytes([]byte(doc)) - if err != nil { - t.Fatalf("unexpected error: %s", err) - } - - var c Config - if err := tree.Unmarshal(&c); err != nil { - t.Fatalf("unexpected error: %s", err) - } - - if c.Key != "value" { - t.Errorf("expected c.Key='value', not '%s'", c.Key) - } - - if c.Obj.v == "called" { - t.Errorf("UnmarshalTOML should not have been called") - } -} - -type durationString struct { - time.Duration -} - -func (d *durationString) UnmarshalTOML(v interface{}) error { - d.Duration = 10 * time.Second - return nil -} - -type config437Error struct { -} - -func (e *config437Error) UnmarshalTOML(v interface{}) error { - return errors.New("expected") -} - -type config437 struct { - HTTP struct { - PingTimeout durationString `toml:"PingTimeout"` - ErrorField config437Error - } `toml:"HTTP"` -} - -func TestGithubIssue437(t *testing.T) { - src := ` -[HTTP] -PingTimeout = "32m" -` - cfg := &config437{} - cfg.HTTP.PingTimeout = durationString{time.Second} - - r := strings.NewReader(src) - err := NewDecoder(r).Decode(cfg) - if err != nil { - t.Fatalf("unexpected errors %s", err) - } - expected := durationString{10 * time.Second} - if cfg.HTTP.PingTimeout != expected { - t.Fatalf("expected '%s', got '%s'", expected, cfg.HTTP.PingTimeout) - } -} - -func TestLeafUnmarshalerError(t *testing.T) { - src := ` -[HTTP] -ErrorField = "foo" -` - cfg := &config437{} - - r := strings.NewReader(src) - err := NewDecoder(r).Decode(cfg) - if err == nil { - t.Fatalf("error was expected") - } -} diff --git a/marshal_test.toml b/marshal_test.toml deleted file mode 100644 index ba5e110b..00000000 --- a/marshal_test.toml +++ /dev/null @@ -1,39 +0,0 @@ -title = "TOML Marshal Testing" - -[basic] - bool = true - date = 1979-05-27T07:32:00Z - float = 123.4 - float64 = 123.456782132399 - int = 5000 - string = "Bite me" - uint = 5001 - -[basic_lists] - bools = [true,false,true] - dates = [1979-05-27T07:32:00Z,1980-05-27T07:32:00Z] - floats = [12.3,45.6,78.9] - ints = [8001,8001,8002] - strings = ["One","Two","Three"] - uints = [5002,5003] - -[basic_map] - one = "one" - two = "two" - -[subdoc] - - [subdoc.first] - name = "First" - - [subdoc.second] - name = "Second" - -[[subdoclist]] - name = "List.First" - -[[subdoclist]] - name = "List.Second" - -[[subdocptrs]] - name = "Second" diff --git a/parser.go b/parser.go deleted file mode 100644 index f5e1a44f..00000000 --- a/parser.go +++ /dev/null @@ -1,508 +0,0 @@ -// TOML Parser. - -package toml - -import ( - "errors" - "fmt" - "math" - "reflect" - "strconv" - "strings" - "time" -) - -type tomlParser struct { - flowIdx int - flow []token - tree *Tree - currentTable []string - seenTableKeys []string -} - -type tomlParserStateFn func() tomlParserStateFn - -// Formats and panics an error message based on a token -func (p *tomlParser) raiseError(tok *token, msg string, args ...interface{}) { - panic(tok.Position.String() + ": " + fmt.Sprintf(msg, args...)) -} - -func (p *tomlParser) run() { - for state := p.parseStart; state != nil; { - state = state() - } -} - -func (p *tomlParser) peek() *token { - if p.flowIdx >= len(p.flow) { - return nil - } - return &p.flow[p.flowIdx] -} - -func (p *tomlParser) assume(typ tokenType) { - tok := p.getToken() - if tok == nil { - p.raiseError(tok, "was expecting token %s, but token stream is empty", tok) - } - if tok.typ != typ { - p.raiseError(tok, "was expecting token %s, but got %s instead", typ, tok) - } -} - -func (p *tomlParser) getToken() *token { - tok := p.peek() - if tok == nil { - return nil - } - p.flowIdx++ - return tok -} - -func (p *tomlParser) parseStart() tomlParserStateFn { - tok := p.peek() - - // end of stream, parsing is finished - if tok == nil { - return nil - } - - switch tok.typ { - case tokenDoubleLeftBracket: - return p.parseGroupArray - case tokenLeftBracket: - return p.parseGroup - case tokenKey: - return p.parseAssign - case tokenEOF: - return nil - case tokenError: - p.raiseError(tok, "parsing error: %s", tok.String()) - default: - p.raiseError(tok, "unexpected token %s", tok.typ) - } - return nil -} - -func (p *tomlParser) parseGroupArray() tomlParserStateFn { - startToken := p.getToken() // discard the [[ - key := p.getToken() - if key.typ != tokenKeyGroupArray { - p.raiseError(key, "unexpected token %s, was expecting a table array key", key) - } - - // get or create table array element at the indicated part in the path - keys, err := parseKey(key.val) - if err != nil { - p.raiseError(key, "invalid table array key: %s", err) - } - p.tree.createSubTree(keys[:len(keys)-1], startToken.Position) // create parent entries - destTree := p.tree.GetPath(keys) - var array []*Tree - if destTree == nil { - array = make([]*Tree, 0) - } else if target, ok := destTree.([]*Tree); ok && target != nil { - array = destTree.([]*Tree) - } else { - p.raiseError(key, "key %s is already assigned and not of type table array", key) - } - p.currentTable = keys - - // add a new tree to the end of the table array - newTree := newTree() - newTree.position = startToken.Position - array = append(array, newTree) - p.tree.SetPath(p.currentTable, array) - - // remove all keys that were children of this table array - prefix := key.val + "." - found := false - for ii := 0; ii < len(p.seenTableKeys); { - tableKey := p.seenTableKeys[ii] - if strings.HasPrefix(tableKey, prefix) { - p.seenTableKeys = append(p.seenTableKeys[:ii], p.seenTableKeys[ii+1:]...) - } else { - found = (tableKey == key.val) - ii++ - } - } - - // keep this key name from use by other kinds of assignments - if !found { - p.seenTableKeys = append(p.seenTableKeys, key.val) - } - - // move to next parser state - p.assume(tokenDoubleRightBracket) - return p.parseStart -} - -func (p *tomlParser) parseGroup() tomlParserStateFn { - startToken := p.getToken() // discard the [ - key := p.getToken() - if key.typ != tokenKeyGroup { - p.raiseError(key, "unexpected token %s, was expecting a table key", key) - } - for _, item := range p.seenTableKeys { - if item == key.val { - p.raiseError(key, "duplicated tables") - } - } - - p.seenTableKeys = append(p.seenTableKeys, key.val) - keys, err := parseKey(key.val) - if err != nil { - p.raiseError(key, "invalid table array key: %s", err) - } - if err := p.tree.createSubTree(keys, startToken.Position); err != nil { - p.raiseError(key, "%s", err) - } - destTree := p.tree.GetPath(keys) - if target, ok := destTree.(*Tree); ok && target != nil && target.inline { - p.raiseError(key, "could not re-define exist inline table or its sub-table : %s", - strings.Join(keys, ".")) - } - p.assume(tokenRightBracket) - p.currentTable = keys - return p.parseStart -} - -func (p *tomlParser) parseAssign() tomlParserStateFn { - key := p.getToken() - p.assume(tokenEqual) - - parsedKey, err := parseKey(key.val) - if err != nil { - p.raiseError(key, "invalid key: %s", err.Error()) - } - - value := p.parseRvalue() - var tableKey []string - if len(p.currentTable) > 0 { - tableKey = p.currentTable - } else { - tableKey = []string{} - } - - prefixKey := parsedKey[0 : len(parsedKey)-1] - tableKey = append(tableKey, prefixKey...) - - // find the table to assign, looking out for arrays of tables - var targetNode *Tree - switch node := p.tree.GetPath(tableKey).(type) { - case []*Tree: - targetNode = node[len(node)-1] - case *Tree: - targetNode = node - case nil: - // create intermediate - if err := p.tree.createSubTree(tableKey, key.Position); err != nil { - p.raiseError(key, "could not create intermediate group: %s", err) - } - targetNode = p.tree.GetPath(tableKey).(*Tree) - default: - p.raiseError(key, "Unknown table type for path: %s", - strings.Join(tableKey, ".")) - } - - if targetNode.inline { - p.raiseError(key, "could not add key or sub-table to exist inline table or its sub-table : %s", - strings.Join(tableKey, ".")) - } - - // assign value to the found table - keyVal := parsedKey[len(parsedKey)-1] - localKey := []string{keyVal} - finalKey := append(tableKey, keyVal) - if targetNode.GetPath(localKey) != nil { - p.raiseError(key, "The following key was defined twice: %s", - strings.Join(finalKey, ".")) - } - var toInsert interface{} - - switch value.(type) { - case *Tree, []*Tree: - toInsert = value - default: - toInsert = &tomlValue{value: value, position: key.Position} - } - targetNode.values[keyVal] = toInsert - return p.parseStart -} - -var errInvalidUnderscore = errors.New("invalid use of _ in number") - -func numberContainsInvalidUnderscore(value string) error { - // For large numbers, you may use underscores between digits to enhance - // readability. Each underscore must be surrounded by at least one digit on - // each side. - - hasBefore := false - for idx, r := range value { - if r == '_' { - if !hasBefore || idx+1 >= len(value) { - // can't end with an underscore - return errInvalidUnderscore - } - } - hasBefore = isDigit(r) - } - return nil -} - -var errInvalidUnderscoreHex = errors.New("invalid use of _ in hex number") - -func hexNumberContainsInvalidUnderscore(value string) error { - hasBefore := false - for idx, r := range value { - if r == '_' { - if !hasBefore || idx+1 >= len(value) { - // can't end with an underscore - return errInvalidUnderscoreHex - } - } - hasBefore = isHexDigit(r) - } - return nil -} - -func cleanupNumberToken(value string) string { - cleanedVal := strings.Replace(value, "_", "", -1) - return cleanedVal -} - -func (p *tomlParser) parseRvalue() interface{} { - tok := p.getToken() - if tok == nil || tok.typ == tokenEOF { - p.raiseError(tok, "expecting a value") - } - - switch tok.typ { - case tokenString: - return tok.val - case tokenTrue: - return true - case tokenFalse: - return false - case tokenInf: - if tok.val[0] == '-' { - return math.Inf(-1) - } - return math.Inf(1) - case tokenNan: - return math.NaN() - case tokenInteger: - cleanedVal := cleanupNumberToken(tok.val) - var err error - var val int64 - if len(cleanedVal) >= 3 && cleanedVal[0] == '0' { - switch cleanedVal[1] { - case 'x': - err = hexNumberContainsInvalidUnderscore(tok.val) - if err != nil { - p.raiseError(tok, "%s", err) - } - val, err = strconv.ParseInt(cleanedVal[2:], 16, 64) - case 'o': - err = numberContainsInvalidUnderscore(tok.val) - if err != nil { - p.raiseError(tok, "%s", err) - } - val, err = strconv.ParseInt(cleanedVal[2:], 8, 64) - case 'b': - err = numberContainsInvalidUnderscore(tok.val) - if err != nil { - p.raiseError(tok, "%s", err) - } - val, err = strconv.ParseInt(cleanedVal[2:], 2, 64) - default: - panic("invalid base") // the lexer should catch this first - } - } else { - err = numberContainsInvalidUnderscore(tok.val) - if err != nil { - p.raiseError(tok, "%s", err) - } - val, err = strconv.ParseInt(cleanedVal, 10, 64) - } - if err != nil { - p.raiseError(tok, "%s", err) - } - return val - case tokenFloat: - err := numberContainsInvalidUnderscore(tok.val) - if err != nil { - p.raiseError(tok, "%s", err) - } - cleanedVal := cleanupNumberToken(tok.val) - val, err := strconv.ParseFloat(cleanedVal, 64) - if err != nil { - p.raiseError(tok, "%s", err) - } - return val - case tokenLocalTime: - val, err := ParseLocalTime(tok.val) - if err != nil { - p.raiseError(tok, "%s", err) - } - return val - case tokenLocalDate: - // a local date may be followed by: - // * nothing: this is a local date - // * a local time: this is a local date-time - - next := p.peek() - if next == nil || next.typ != tokenLocalTime { - val, err := ParseLocalDate(tok.val) - if err != nil { - p.raiseError(tok, "%s", err) - } - return val - } - - localDate := tok - localTime := p.getToken() - - next = p.peek() - if next == nil || next.typ != tokenTimeOffset { - v := localDate.val + "T" + localTime.val - val, err := ParseLocalDateTime(v) - if err != nil { - p.raiseError(tok, "%s", err) - } - return val - } - - offset := p.getToken() - - layout := time.RFC3339Nano - v := localDate.val + "T" + localTime.val + offset.val - val, err := time.ParseInLocation(layout, v, time.UTC) - if err != nil { - p.raiseError(tok, "%s", err) - } - return val - case tokenLeftBracket: - return p.parseArray() - case tokenLeftCurlyBrace: - return p.parseInlineTable() - case tokenEqual: - p.raiseError(tok, "cannot have multiple equals for the same key") - case tokenError: - p.raiseError(tok, "%s", tok) - default: - panic(fmt.Errorf("unhandled token: %v", tok)) - } - - return nil -} - -func tokenIsComma(t *token) bool { - return t != nil && t.typ == tokenComma -} - -func (p *tomlParser) parseInlineTable() *Tree { - tree := newTree() - var previous *token -Loop: - for { - follow := p.peek() - if follow == nil || follow.typ == tokenEOF { - p.raiseError(follow, "unterminated inline table") - } - switch follow.typ { - case tokenRightCurlyBrace: - p.getToken() - break Loop - case tokenKey, tokenInteger, tokenString: - if !tokenIsComma(previous) && previous != nil { - p.raiseError(follow, "comma expected between fields in inline table") - } - key := p.getToken() - p.assume(tokenEqual) - - parsedKey, err := parseKey(key.val) - if err != nil { - p.raiseError(key, "invalid key: %s", err) - } - - value := p.parseRvalue() - tree.SetPath(parsedKey, value) - case tokenComma: - if tokenIsComma(previous) { - p.raiseError(follow, "need field between two commas in inline table") - } - p.getToken() - default: - p.raiseError(follow, "unexpected token type in inline table: %s", follow.String()) - } - previous = follow - } - if tokenIsComma(previous) { - p.raiseError(previous, "trailing comma at the end of inline table") - } - tree.inline = true - return tree -} - -func (p *tomlParser) parseArray() interface{} { - var array []interface{} - arrayType := reflect.TypeOf(newTree()) - for { - follow := p.peek() - if follow == nil || follow.typ == tokenEOF { - p.raiseError(follow, "unterminated array") - } - if follow.typ == tokenRightBracket { - p.getToken() - break - } - val := p.parseRvalue() - if reflect.TypeOf(val) != arrayType { - arrayType = nil - } - array = append(array, val) - follow = p.peek() - if follow == nil || follow.typ == tokenEOF { - p.raiseError(follow, "unterminated array") - } - if follow.typ != tokenRightBracket && follow.typ != tokenComma { - p.raiseError(follow, "missing comma") - } - if follow.typ == tokenComma { - p.getToken() - } - } - - // if the array is a mixed-type array or its length is 0, - // don't convert it to a table array - if len(array) <= 0 { - arrayType = nil - } - // An array of Trees is actually an array of inline - // tables, which is a shorthand for a table array. If the - // array was not converted from []interface{} to []*Tree, - // the two notations would not be equivalent. - if arrayType == reflect.TypeOf(newTree()) { - tomlArray := make([]*Tree, len(array)) - for i, v := range array { - tomlArray[i] = v.(*Tree) - } - return tomlArray - } - return array -} - -func parseToml(flow []token) *Tree { - result := newTree() - result.position = Position{1, 1} - parser := &tomlParser{ - flowIdx: 0, - flow: flow, - tree: result, - currentTable: make([]string, 0), - seenTableKeys: make([]string, 0), - } - parser.run() - return result -} diff --git a/parser_test.go b/parser_test.go deleted file mode 100644 index 60fa375b..00000000 --- a/parser_test.go +++ /dev/null @@ -1,1166 +0,0 @@ -package toml - -import ( - "fmt" - "math" - "reflect" - "testing" - "time" - - "github.com/davecgh/go-spew/spew" -) - -func assertSubTree(t *testing.T, path []string, tree *Tree, err error, ref map[string]interface{}) { - if err != nil { - t.Error("Non-nil error:", err.Error()) - return - } - for k, v := range ref { - nextPath := append(path, k) - t.Log("asserting path", nextPath) - // NOTE: directly access key instead of resolve by path - // NOTE: see TestSpecialKV - switch node := tree.GetPath([]string{k}).(type) { - case []*Tree: - t.Log("\tcomparing key", nextPath, "by array iteration") - for idx, item := range node { - assertSubTree(t, nextPath, item, err, v.([]map[string]interface{})[idx]) - } - case *Tree: - t.Log("\tcomparing key", nextPath, "by subtree assestion") - assertSubTree(t, nextPath, node, err, v.(map[string]interface{})) - default: - t.Log("\tcomparing key", nextPath, "by string representation because it's of type", reflect.TypeOf(node)) - if fmt.Sprintf("%v", node) != fmt.Sprintf("%v", v) { - t.Errorf("was expecting %v at %v but got %v", v, k, node) - } - } - } -} - -func assertTree(t *testing.T, tree *Tree, err error, ref map[string]interface{}) { - t.Log("Asserting tree:\n", spew.Sdump(tree)) - assertSubTree(t, []string{}, tree, err, ref) - t.Log("Finished tree assertion.") -} - -func TestCreateSubTree(t *testing.T) { - tree := newTree() - tree.createSubTree([]string{"a", "b", "c"}, Position{}) - tree.Set("a.b.c", 42) - if tree.Get("a.b.c") != 42 { - t.Fail() - } -} - -func TestSimpleKV(t *testing.T) { - tree, err := Load("a = 42") - assertTree(t, tree, err, map[string]interface{}{ - "a": int64(42), - }) - - tree, _ = Load("a = 42\nb = 21") - assertTree(t, tree, err, map[string]interface{}{ - "a": int64(42), - "b": int64(21), - }) -} - -func TestNumberInKey(t *testing.T) { - tree, err := Load("hello2 = 42") - assertTree(t, tree, err, map[string]interface{}{ - "hello2": int64(42), - }) -} - -func TestIncorrectKeyExtraSquareBracket(t *testing.T) { - _, err := Load(`[a]b] -zyx = 42`) - if err == nil { - t.Error("Error should have been returned.") - } - if err.Error() != "(1, 4): parsing error: keys cannot contain ] character" { - t.Error("Bad error message:", err.Error()) - } -} - -func TestSimpleNumbers(t *testing.T) { - tree, err := Load("a = +42\nb = -21\nc = +4.2\nd = -2.1") - assertTree(t, tree, err, map[string]interface{}{ - "a": int64(42), - "b": int64(-21), - "c": float64(4.2), - "d": float64(-2.1), - }) -} - -func TestSpecialFloats(t *testing.T) { - tree, err := Load(` -normalinf = inf -plusinf = +inf -minusinf = -inf -normalnan = nan -plusnan = +nan -minusnan = -nan -`) - assertTree(t, tree, err, map[string]interface{}{ - "normalinf": math.Inf(1), - "plusinf": math.Inf(1), - "minusinf": math.Inf(-1), - "normalnan": math.NaN(), - "plusnan": math.NaN(), - "minusnan": math.NaN(), - }) -} - -func TestHexIntegers(t *testing.T) { - tree, err := Load(`a = 0xDEADBEEF`) - assertTree(t, tree, err, map[string]interface{}{"a": int64(3735928559)}) - - tree, err = Load(`a = 0xdeadbeef`) - assertTree(t, tree, err, map[string]interface{}{"a": int64(3735928559)}) - - tree, err = Load(`a = 0xdead_beef`) - assertTree(t, tree, err, map[string]interface{}{"a": int64(3735928559)}) - - _, err = Load(`a = 0x_1`) - if err.Error() != "(1, 5): invalid use of _ in hex number" { - t.Error("Bad error message:", err.Error()) - } -} - -func TestOctIntegers(t *testing.T) { - tree, err := Load(`a = 0o01234567`) - assertTree(t, tree, err, map[string]interface{}{"a": int64(342391)}) - - tree, err = Load(`a = 0o755`) - assertTree(t, tree, err, map[string]interface{}{"a": int64(493)}) - - _, err = Load(`a = 0o_1`) - if err.Error() != "(1, 5): invalid use of _ in number" { - t.Error("Bad error message:", err.Error()) - } -} - -func TestBinIntegers(t *testing.T) { - tree, err := Load(`a = 0b11010110`) - assertTree(t, tree, err, map[string]interface{}{"a": int64(214)}) - - _, err = Load(`a = 0b_1`) - if err.Error() != "(1, 5): invalid use of _ in number" { - t.Error("Bad error message:", err.Error()) - } -} - -func TestBadIntegerBase(t *testing.T) { - _, err := Load(`a = 0k1`) - if err.Error() != "(1, 5): unknown number base: k. possible options are x (hex) o (octal) b (binary)" { - t.Error("Error should have been returned.") - } -} - -func TestIntegerNoDigit(t *testing.T) { - _, err := Load(`a = 0b`) - if err.Error() != "(1, 5): number needs at least one digit" { - t.Error("Bad error message:", err.Error()) - } -} - -func TestNumbersWithUnderscores(t *testing.T) { - tree, err := Load("a = 1_000") - assertTree(t, tree, err, map[string]interface{}{ - "a": int64(1000), - }) - - tree, err = Load("a = 5_349_221") - assertTree(t, tree, err, map[string]interface{}{ - "a": int64(5349221), - }) - - tree, err = Load("a = 1_2_3_4_5") - assertTree(t, tree, err, map[string]interface{}{ - "a": int64(12345), - }) - - tree, err = Load("flt8 = 9_224_617.445_991_228_313") - assertTree(t, tree, err, map[string]interface{}{ - "flt8": float64(9224617.445991228313), - }) - - tree, err = Load("flt9 = 1e1_00") - assertTree(t, tree, err, map[string]interface{}{ - "flt9": float64(1e100), - }) -} - -func TestFloatsWithExponents(t *testing.T) { - tree, err := Load("a = 5e+22\nb = 5E+22\nc = -5e+22\nd = -5e-22\ne = 6.626e-34") - assertTree(t, tree, err, map[string]interface{}{ - "a": float64(5e+22), - "b": float64(5e+22), - "c": float64(-5e+22), - "d": float64(-5e-22), - "e": float64(6.626e-34), - }) -} - -func TestSimpleDate(t *testing.T) { - tree, err := Load("a = 1979-05-27T07:32:00Z") - assertTree(t, tree, err, map[string]interface{}{ - "a": time.Date(1979, time.May, 27, 7, 32, 0, 0, time.UTC), - }) -} - -func TestDateOffset(t *testing.T) { - tree, err := Load("a = 1979-05-27T00:32:00-07:00") - assertTree(t, tree, err, map[string]interface{}{ - "a": time.Date(1979, time.May, 27, 0, 32, 0, 0, time.FixedZone("", -7*60*60)), - }) -} - -func TestDateNano(t *testing.T) { - tree, err := Load("a = 1979-05-27T00:32:00.999999999-07:00") - assertTree(t, tree, err, map[string]interface{}{ - "a": time.Date(1979, time.May, 27, 0, 32, 0, 999999999, time.FixedZone("", -7*60*60)), - }) -} - -func TestLocalDateTime(t *testing.T) { - tree, err := Load("a = 1979-05-27T07:32:00") - assertTree(t, tree, err, map[string]interface{}{ - "a": LocalDateTime{ - Date: LocalDate{ - Year: 1979, - Month: 5, - Day: 27, - }, - Time: LocalTime{ - Hour: 7, - Minute: 32, - Second: 0, - Nanosecond: 0, - }, - }, - }) -} - -func TestLocalDateTimeNano(t *testing.T) { - tree, err := Load("a = 1979-05-27T07:32:00.999999") - assertTree(t, tree, err, map[string]interface{}{ - "a": LocalDateTime{ - Date: LocalDate{ - Year: 1979, - Month: 5, - Day: 27, - }, - Time: LocalTime{ - Hour: 7, - Minute: 32, - Second: 0, - Nanosecond: 999999000, - }, - }, - }) -} - -func TestLocalDate(t *testing.T) { - tree, err := Load("a = 1979-05-27") - assertTree(t, tree, err, map[string]interface{}{ - "a": LocalDate{ - Year: 1979, - Month: 5, - Day: 27, - }, - }) -} - -func TestLocalDateError(t *testing.T) { - _, err := Load("a = 2020-09-31") - if err == nil { - t.Fatalf("should error") - } -} - -func TestLocalTimeError(t *testing.T) { - _, err := Load("a = 07:99:00") - if err == nil { - t.Fatalf("should error") - } -} - -func TestLocalDateTimeError(t *testing.T) { - _, err := Load("a = 2020-09-31T07:99:00") - if err == nil { - t.Fatalf("should error") - } -} - -func TestDateTimeOffsetError(t *testing.T) { - _, err := Load("a = 2020-09-31T07:99:00Z") - if err == nil { - t.Fatalf("should error") - } -} - -func TestLocalTime(t *testing.T) { - tree, err := Load("a = 07:32:00") - assertTree(t, tree, err, map[string]interface{}{ - "a": LocalTime{ - Hour: 7, - Minute: 32, - Second: 0, - Nanosecond: 0, - }, - }) -} - -func TestLocalTimeNano(t *testing.T) { - tree, err := Load("a = 00:32:00.999999") - assertTree(t, tree, err, map[string]interface{}{ - "a": LocalTime{ - Hour: 0, - Minute: 32, - Second: 0, - Nanosecond: 999999000, - }, - }) -} - -func TestSimpleString(t *testing.T) { - tree, err := Load("a = \"hello world\"") - assertTree(t, tree, err, map[string]interface{}{ - "a": "hello world", - }) -} - -func TestSpaceKey(t *testing.T) { - tree, err := Load("\"a b\" = \"hello world\"") - assertTree(t, tree, err, map[string]interface{}{ - "a b": "hello world", - }) -} - -func TestDoubleQuotedKey(t *testing.T) { - tree, err := Load(` - "key" = "a" - "\t" = "b" - "\U0001F914" = "c" - "\u2764" = "d" - `) - assertTree(t, tree, err, map[string]interface{}{ - "key": "a", - "\t": "b", - "\U0001F914": "c", - "\u2764": "d", - }) -} - -func TestSingleQuotedKey(t *testing.T) { - tree, err := Load(` - 'key' = "a" - '\t' = "b" - '\U0001F914' = "c" - '\u2764' = "d" - `) - assertTree(t, tree, err, map[string]interface{}{ - `key`: "a", - `\t`: "b", - `\U0001F914`: "c", - `\u2764`: "d", - }) -} - -func TestStringEscapables(t *testing.T) { - tree, err := Load("a = \"a \\n b\"") - assertTree(t, tree, err, map[string]interface{}{ - "a": "a \n b", - }) - - tree, err = Load("a = \"a \\t b\"") - assertTree(t, tree, err, map[string]interface{}{ - "a": "a \t b", - }) - - tree, err = Load("a = \"a \\r b\"") - assertTree(t, tree, err, map[string]interface{}{ - "a": "a \r b", - }) - - tree, err = Load("a = \"a \\\\ b\"") - assertTree(t, tree, err, map[string]interface{}{ - "a": "a \\ b", - }) -} - -func TestEmptyQuotedString(t *testing.T) { - tree, err := Load(`[""] -"" = 1`) - assertTree(t, tree, err, map[string]interface{}{ - "": map[string]interface{}{ - "": int64(1), - }, - }) -} - -func TestBools(t *testing.T) { - tree, err := Load("a = true\nb = false") - assertTree(t, tree, err, map[string]interface{}{ - "a": true, - "b": false, - }) -} - -func TestNestedKeys(t *testing.T) { - tree, err := Load("[a.b.c]\nd = 42") - assertTree(t, tree, err, map[string]interface{}{ - "a": map[string]interface{}{ - "b": map[string]interface{}{ - "c": map[string]interface{}{ - "d": int64(42), - }, - }, - }, - }) -} - -func TestNestedQuotedUnicodeKeys(t *testing.T) { - tree, err := Load("[ j . \"ʞ\" . l ]\nd = 42") - assertTree(t, tree, err, map[string]interface{}{ - "j": map[string]interface{}{ - "ʞ": map[string]interface{}{ - "l": map[string]interface{}{ - "d": int64(42), - }, - }, - }, - }) - - tree, err = Load("[ g . h . i ]\nd = 42") - assertTree(t, tree, err, map[string]interface{}{ - "g": map[string]interface{}{ - "h": map[string]interface{}{ - "i": map[string]interface{}{ - "d": int64(42), - }, - }, - }, - }) - - tree, err = Load("[ d.e.f ]\nk = 42") - assertTree(t, tree, err, map[string]interface{}{ - "d": map[string]interface{}{ - "e": map[string]interface{}{ - "f": map[string]interface{}{ - "k": int64(42), - }, - }, - }, - }) -} - -func TestArrayOne(t *testing.T) { - tree, err := Load("a = [1]") - assertTree(t, tree, err, map[string]interface{}{ - "a": []int64{int64(1)}, - }) -} - -func TestArrayZero(t *testing.T) { - tree, err := Load("a = []") - assertTree(t, tree, err, map[string]interface{}{ - "a": []interface{}{}, - }) -} - -func TestArraySimple(t *testing.T) { - tree, err := Load("a = [42, 21, 10]") - assertTree(t, tree, err, map[string]interface{}{ - "a": []int64{int64(42), int64(21), int64(10)}, - }) - - tree, _ = Load("a = [42, 21, 10,]") - assertTree(t, tree, err, map[string]interface{}{ - "a": []int64{int64(42), int64(21), int64(10)}, - }) -} - -func TestArrayMultiline(t *testing.T) { - tree, err := Load("a = [42,\n21, 10,]") - assertTree(t, tree, err, map[string]interface{}{ - "a": []int64{int64(42), int64(21), int64(10)}, - }) -} - -func TestArrayNested(t *testing.T) { - tree, err := Load("a = [[42, 21], [10]]") - assertTree(t, tree, err, map[string]interface{}{ - "a": [][]int64{{int64(42), int64(21)}, {int64(10)}}, - }) -} - -func TestNestedArrayComment(t *testing.T) { - tree, err := Load(` -someArray = [ -# does not work -["entry1"] -]`) - assertTree(t, tree, err, map[string]interface{}{ - "someArray": [][]string{{"entry1"}}, - }) -} - -func TestNestedEmptyArrays(t *testing.T) { - tree, err := Load("a = [[[]]]") - assertTree(t, tree, err, map[string]interface{}{ - "a": [][][]interface{}{{{}}}, - }) -} - -func TestArrayNestedStrings(t *testing.T) { - tree, err := Load("data = [ [\"gamma\", \"delta\"], [\"Foo\"] ]") - assertTree(t, tree, err, map[string]interface{}{ - "data": [][]string{{"gamma", "delta"}, {"Foo"}}, - }) -} - -func TestParseUnknownRvalue(t *testing.T) { - _, err := Load("a = !bssss") - if err == nil { - t.Error("Expecting a parse error") - } - - _, err = Load("a = /b") - if err == nil { - t.Error("Expecting a parse error") - } -} - -func TestMissingValue(t *testing.T) { - _, err := Load("a = ") - if err.Error() != "(1, 5): expecting a value" { - t.Error("Bad error message:", err.Error()) - } -} - -func TestUnterminatedArray(t *testing.T) { - _, err := Load("a = [1,") - if err.Error() != "(1, 8): unterminated array" { - t.Error("Bad error message:", err.Error()) - } - - _, err = Load("a = [1") - if err.Error() != "(1, 7): unterminated array" { - t.Error("Bad error message:", err.Error()) - } - - _, err = Load("a = [1 2") - if err.Error() != "(1, 8): missing comma" { - t.Error("Bad error message:", err.Error()) - } -} - -func TestNewlinesInArrays(t *testing.T) { - tree, err := Load("a = [1,\n2,\n3]") - assertTree(t, tree, err, map[string]interface{}{ - "a": []int64{int64(1), int64(2), int64(3)}, - }) -} - -func TestArrayWithExtraComma(t *testing.T) { - tree, err := Load("a = [1,\n2,\n3,\n]") - assertTree(t, tree, err, map[string]interface{}{ - "a": []int64{int64(1), int64(2), int64(3)}, - }) -} - -func TestArrayWithExtraCommaComment(t *testing.T) { - tree, err := Load("a = [1, # wow\n2, # such items\n3, # so array\n]") - assertTree(t, tree, err, map[string]interface{}{ - "a": []int64{int64(1), int64(2), int64(3)}, - }) -} - -func TestSimpleInlineGroup(t *testing.T) { - tree, err := Load("key = {a = 42}") - assertTree(t, tree, err, map[string]interface{}{ - "key": map[string]interface{}{ - "a": int64(42), - }, - }) -} - -func TestDoubleInlineGroup(t *testing.T) { - tree, err := Load("key = {a = 42, b = \"foo\"}") - assertTree(t, tree, err, map[string]interface{}{ - "key": map[string]interface{}{ - "a": int64(42), - "b": "foo", - }, - }) -} - -func TestNestedInlineGroup(t *testing.T) { - tree, err := Load("out = {block0 = {x = 99, y = 100}, block1 = {p = \"999\", q = \"1000\"}}") - assertTree(t, tree, err, map[string]interface{}{ - "out": map[string]interface{}{ - "block0": map[string]interface{}{ - "x": int64(99), - "y": int64(100), - }, - "block1": map[string]interface{}{ - "p": "999", - "q": "1000", - }, - }, - }) -} - -func TestArrayInNestedInlineGroup(t *testing.T) { - tree, err := Load(`image = {name = "xxx", palette = {id = 100, colors = ["red", "blue", "green"]}}`) - assertTree(t, tree, err, map[string]interface{}{ - "image": map[string]interface{}{ - "name": "xxx", - "palette": map[string]interface{}{ - "id": int64(100), - "colors": []string{ - "red", - "blue", - "green", - }, - }, - }, - }) -} - -func TestExampleInlineGroup(t *testing.T) { - tree, err := Load(`name = { first = "Tom", last = "Preston-Werner" } -point = { x = 1, y = 2 }`) - assertTree(t, tree, err, map[string]interface{}{ - "name": map[string]interface{}{ - "first": "Tom", - "last": "Preston-Werner", - }, - "point": map[string]interface{}{ - "x": int64(1), - "y": int64(2), - }, - }) -} - -func TestInlineGroupBareKeysUnderscore(t *testing.T) { - tree, err := Load(`foo = { _bar = "buz" }`) - assertTree(t, tree, err, map[string]interface{}{ - "foo": map[string]interface{}{ - "_bar": "buz", - }, - }) -} - -func TestInlineGroupBareKeysDash(t *testing.T) { - tree, err := Load(`foo = { -bar = "buz" }`) - assertTree(t, tree, err, map[string]interface{}{ - "foo": map[string]interface{}{ - "-bar": "buz", - }, - }) -} - -func TestInlineGroupKeyQuoted(t *testing.T) { - tree, err := Load(`foo = { "bar" = "buz" }`) - assertTree(t, tree, err, map[string]interface{}{ - "foo": map[string]interface{}{ - "bar": "buz", - }, - }) -} - -func TestExampleInlineGroupInArray(t *testing.T) { - tree, err := Load(`points = [{ x = 1, y = 2 }]`) - assertTree(t, tree, err, map[string]interface{}{ - "points": []map[string]interface{}{ - { - "x": int64(1), - "y": int64(2), - }, - }, - }) -} - -func TestInlineTableUnterminated(t *testing.T) { - _, err := Load("foo = {") - if err.Error() != "(1, 8): unterminated inline table" { - t.Error("Bad error message:", err.Error()) - } -} - -func TestInlineTableCommaExpected(t *testing.T) { - _, err := Load("foo = {hello = 53 test = foo}") - if err.Error() != "(1, 19): unexpected token type in inline table: no value can start with t" { - t.Error("Bad error message:", err.Error()) - } -} - -func TestInlineTableCommaStart(t *testing.T) { - _, err := Load("foo = {, hello = 53}") - if err.Error() != "(1, 8): unexpected token type in inline table: keys cannot contain , character" { - t.Error("Bad error message:", err.Error()) - } -} - -func TestInlineTableDoubleComma(t *testing.T) { - _, err := Load("foo = {hello = 53,, foo = 17}") - if err.Error() != "(1, 19): unexpected token type in inline table: keys cannot contain , character" { - t.Error("Bad error message:", err.Error()) - } -} - -func TestInlineTableTrailingComma(t *testing.T) { - _, err := Load("foo = {hello = 53, foo = 17,}") - if err.Error() != "(1, 28): trailing comma at the end of inline table" { - t.Error("Bad error message:", err.Error()) - } -} - -func TestAddKeyToInlineTable(t *testing.T) { - _, err := Load("type = { name = \"Nail\" }\ntype.edible = false") - if err.Error() != "(2, 1): could not add key or sub-table to exist inline table or its sub-table : type" { - t.Error("Bad error message:", err.Error()) - } -} - -func TestAddSubTableToInlineTable(t *testing.T) { - _, err := Load("a = { b = \"c\" }\na.d.e = \"f\"") - if err.Error() != "(2, 1): could not add key or sub-table to exist inline table or its sub-table : a.d" { - t.Error("Bad error message:", err.Error()) - } -} - -func TestAddKeyToSubTableOfInlineTable(t *testing.T) { - _, err := Load("a = { b = { c = \"d\" } }\na.b.e = \"f\"") - if err.Error() != "(2, 1): could not add key or sub-table to exist inline table or its sub-table : a.b" { - t.Error("Bad error message:", err.Error()) - } -} - -func TestReDefineInlineTable(t *testing.T) { - _, err := Load("a = { b = \"c\" }\n[a]\n d = \"e\"") - if err.Error() != "(2, 2): could not re-define exist inline table or its sub-table : a" { - t.Error("Bad error message:", err.Error()) - } -} - -func TestDuplicateGroups(t *testing.T) { - _, err := Load("[foo]\na=2\n[foo]b=3") - if err.Error() != "(3, 2): duplicated tables" { - t.Error("Bad error message:", err.Error()) - } -} - -func TestDuplicateKeys(t *testing.T) { - _, err := Load("foo = 2\nfoo = 3") - if err.Error() != "(2, 1): The following key was defined twice: foo" { - t.Error("Bad error message:", err.Error()) - } -} - -func TestEmptyIntermediateTable(t *testing.T) { - _, err := Load("[foo..bar]") - if err.Error() != "(1, 2): invalid table array key: expecting key part after dot" { - t.Error("Bad error message:", err.Error()) - } -} - -func TestImplicitDeclarationBefore(t *testing.T) { - tree, err := Load("[a.b.c]\nanswer = 42\n[a]\nbetter = 43") - assertTree(t, tree, err, map[string]interface{}{ - "a": map[string]interface{}{ - "b": map[string]interface{}{ - "c": map[string]interface{}{ - "answer": int64(42), - }, - }, - "better": int64(43), - }, - }) -} - -func TestFloatsWithoutLeadingZeros(t *testing.T) { - _, err := Load("a = .42") - if err.Error() != "(1, 5): cannot start float with a dot" { - t.Error("Bad error message:", err.Error()) - } - - _, err = Load("a = -.42") - if err.Error() != "(1, 5): cannot start float with a dot" { - t.Error("Bad error message:", err.Error()) - } -} - -func TestMissingFile(t *testing.T) { - _, err := LoadFile("foo.toml") - if err.Error() != "open foo.toml: no such file or directory" && - err.Error() != "open foo.toml: The system cannot find the file specified." { - t.Error("Bad error message:", err.Error()) - } -} - -func TestParseFile(t *testing.T) { - tree, err := LoadFile("example.toml") - - assertTree(t, tree, err, map[string]interface{}{ - "title": "TOML Example", - "owner": map[string]interface{}{ - "name": "Tom Preston-Werner", - "organization": "GitHub", - "bio": "GitHub Cofounder & CEO\nLikes tater tots and beer.", - "dob": time.Date(1979, time.May, 27, 7, 32, 0, 0, time.UTC), - }, - "database": map[string]interface{}{ - "server": "192.168.1.1", - "ports": []int64{8001, 8001, 8002}, - "connection_max": 5000, - "enabled": true, - }, - "servers": map[string]interface{}{ - "alpha": map[string]interface{}{ - "ip": "10.0.0.1", - "dc": "eqdc10", - }, - "beta": map[string]interface{}{ - "ip": "10.0.0.2", - "dc": "eqdc10", - }, - }, - "clients": map[string]interface{}{ - "data": []interface{}{ - []string{"gamma", "delta"}, - []int64{1, 2}, - }, - "score": 4e-08, - }, - }) -} - -func TestParseFileCRLF(t *testing.T) { - tree, err := LoadFile("example-crlf.toml") - - assertTree(t, tree, err, map[string]interface{}{ - "title": "TOML Example", - "owner": map[string]interface{}{ - "name": "Tom Preston-Werner", - "organization": "GitHub", - "bio": "GitHub Cofounder & CEO\nLikes tater tots and beer.", - "dob": time.Date(1979, time.May, 27, 7, 32, 0, 0, time.UTC), - }, - "database": map[string]interface{}{ - "server": "192.168.1.1", - "ports": []int64{8001, 8001, 8002}, - "connection_max": 5000, - "enabled": true, - }, - "servers": map[string]interface{}{ - "alpha": map[string]interface{}{ - "ip": "10.0.0.1", - "dc": "eqdc10", - }, - "beta": map[string]interface{}{ - "ip": "10.0.0.2", - "dc": "eqdc10", - }, - }, - "clients": map[string]interface{}{ - "data": []interface{}{ - []string{"gamma", "delta"}, - []int64{1, 2}, - }, - "score": 4e-08, - }, - }) -} - -func TestParseKeyGroupArray(t *testing.T) { - tree, err := Load("[[foo.bar]] a = 42\n[[foo.bar]] a = 69") - assertTree(t, tree, err, map[string]interface{}{ - "foo": map[string]interface{}{ - "bar": []map[string]interface{}{ - {"a": int64(42)}, - {"a": int64(69)}, - }, - }, - }) -} - -func TestParseKeyGroupArrayUnfinished(t *testing.T) { - _, err := Load("[[foo.bar]\na = 42") - if err.Error() != "(1, 10): was expecting token [[, but got unclosed table array key instead" { - t.Error("Bad error message:", err.Error()) - } - - _, err = Load("[[foo.[bar]\na = 42") - if err.Error() != "(1, 3): unexpected token table array key cannot contain ']', was expecting a table array key" { - t.Error("Bad error message:", err.Error()) - } -} - -func TestParseKeyGroupArrayQueryExample(t *testing.T) { - tree, err := Load(` - [[book]] - title = "The Stand" - author = "Stephen King" - [[book]] - title = "For Whom the Bell Tolls" - author = "Ernest Hemmingway" - [[book]] - title = "Neuromancer" - author = "William Gibson" - `) - - assertTree(t, tree, err, map[string]interface{}{ - "book": []map[string]interface{}{ - {"title": "The Stand", "author": "Stephen King"}, - {"title": "For Whom the Bell Tolls", "author": "Ernest Hemmingway"}, - {"title": "Neuromancer", "author": "William Gibson"}, - }, - }) -} - -func TestParseKeyGroupArraySpec(t *testing.T) { - tree, err := Load("[[fruit]]\n name=\"apple\"\n [fruit.physical]\n color=\"red\"\n shape=\"round\"\n [[fruit]]\n name=\"banana\"") - assertTree(t, tree, err, map[string]interface{}{ - "fruit": []map[string]interface{}{ - {"name": "apple", "physical": map[string]interface{}{"color": "red", "shape": "round"}}, - {"name": "banana"}, - }, - }) -} - -func TestTomlValueStringRepresentation(t *testing.T) { - for idx, item := range []struct { - Value interface{} - Expect string - }{ - {int64(12345), "12345"}, - {uint64(50), "50"}, - {float64(123.45), "123.45"}, - {true, "true"}, - {"hello world", "\"hello world\""}, - {"\b\t\n\f\r\"\\", "\"\\b\\t\\n\\f\\r\\\"\\\\\""}, - {"\x05", "\"\\u0005\""}, - {time.Date(1979, time.May, 27, 7, 32, 0, 0, time.UTC), "1979-05-27T07:32:00Z"}, - {[]interface{}{"gamma", "delta"}, "[\"gamma\", \"delta\"]"}, - {nil, ""}, - } { - result, err := tomlValueStringRepresentation(item.Value, "", "", OrderAlphabetical, false) - if err != nil { - t.Errorf("Test %d - unexpected error: %s", idx, err) - } - if result != item.Expect { - t.Errorf("Test %d - got '%s', expected '%s'", idx, result, item.Expect) - } - } -} - -func TestToStringMapStringString(t *testing.T) { - tree, err := TreeFromMap(map[string]interface{}{"m": map[string]interface{}{"v": "abc"}}) - if err != nil { - t.Fatalf("unexpected error: %s", err) - } - want := "\n[m]\n v = \"abc\"\n" - got := tree.String() - - if got != want { - t.Errorf("want:\n%q\ngot:\n%q", want, got) - } -} - -func assertPosition(t *testing.T, text string, ref map[string]Position) { - tree, err := Load(text) - if err != nil { - t.Errorf("Error loading document text: `%v`", text) - t.Errorf("Error: %v", err) - } - for path, pos := range ref { - testPos := tree.GetPosition(path) - if testPos.Invalid() { - t.Errorf("Failed to query tree path or path has invalid position: %s", path) - } else if pos != testPos { - t.Errorf("Expected position %v, got %v instead", pos, testPos) - } - } -} - -func TestDocumentPositions(t *testing.T) { - assertPosition(t, - "[foo]\nbar=42\nbaz=69", - map[string]Position{ - "": {1, 1}, - "foo": {1, 1}, - "foo.bar": {2, 1}, - "foo.baz": {3, 1}, - }) -} - -func TestDocumentPositionsWithSpaces(t *testing.T) { - assertPosition(t, - " [foo]\n bar=42\n baz=69", - map[string]Position{ - "": {1, 1}, - "foo": {1, 3}, - "foo.bar": {2, 3}, - "foo.baz": {3, 3}, - }) -} - -func TestDocumentPositionsWithGroupArray(t *testing.T) { - assertPosition(t, - "[[foo]]\nbar=42\nbaz=69", - map[string]Position{ - "": {1, 1}, - "foo": {1, 1}, - "foo.bar": {2, 1}, - "foo.baz": {3, 1}, - }) -} - -func TestNestedTreePosition(t *testing.T) { - assertPosition(t, - "[foo.bar]\na=42\nb=69", - map[string]Position{ - "": {1, 1}, - "foo": {1, 1}, - "foo.bar": {1, 1}, - "foo.bar.a": {2, 1}, - "foo.bar.b": {3, 1}, - }) -} - -func TestInvalidGroupArray(t *testing.T) { - _, err := Load("[table#key]\nanswer = 42") - if err == nil { - t.Error("Should error") - } - - _, err = Load("[foo.[bar]\na = 42") - if err.Error() != "(1, 2): unexpected token table key cannot contain ']', was expecting a table key" { - t.Error("Bad error message:", err.Error()) - } -} - -func TestDoubleEqual(t *testing.T) { - _, err := Load("foo= = 2") - if err.Error() != "(1, 6): cannot have multiple equals for the same key" { - t.Error("Bad error message:", err.Error()) - } -} - -func TestGroupArrayReassign(t *testing.T) { - _, err := Load("[hello]\n[[hello]]") - if err.Error() != "(2, 3): key \"hello\" is already assigned and not of type table array" { - t.Error("Bad error message:", err.Error()) - } -} - -func TestInvalidFloatParsing(t *testing.T) { - _, err := Load("a=1e_2") - if err.Error() != "(1, 3): invalid use of _ in number" { - t.Error("Bad error message:", err.Error()) - } - - _, err = Load("a=1e2_") - if err.Error() != "(1, 3): invalid use of _ in number" { - t.Error("Bad error message:", err.Error()) - } - - _, err = Load("a=1__2") - if err.Error() != "(1, 3): invalid use of _ in number" { - t.Error("Bad error message:", err.Error()) - } - - _, err = Load("a=_1_2") - if err.Error() != "(1, 3): no value can start with _" { - t.Error("Bad error message:", err.Error()) - } -} - -func TestMapKeyIsNum(t *testing.T) { - _, err := Load("table={2018=1,2019=2}") - if err != nil { - t.Error("should be passed") - } - _, err = Load(`table={"2018"=1,"2019"=2}`) - if err != nil { - t.Error("should be passed") - } -} - -func TestInvalidKeyInlineTable(t *testing.T) { - _, err := Load("table={invalid..key = 1}") - if err.Error() != "(1, 8): invalid key: expecting key part after dot" { - t.Error("Bad error message:", err.Error()) - } -} - -func TestDottedKeys(t *testing.T) { - tree, err := Load(` -name = "Orange" -physical.color = "orange" -physical.shape = "round" -site."google.com" = true`) - - assertTree(t, tree, err, map[string]interface{}{ - "name": "Orange", - "physical": map[string]interface{}{ - "color": "orange", - "shape": "round", - }, - "site": map[string]interface{}{ - "google.com": true, - }, - }) -} - -func TestInvalidDottedKeyEmptyGroup(t *testing.T) { - _, err := Load(`a..b = true`) - if err == nil { - t.Fatal("should return an error") - } - if err.Error() != "(1, 1): invalid key: expecting key part after dot" { - t.Fatalf("invalid error message: %s", err) - } -} - -func TestAccidentalNewlines(t *testing.T) { - expected := "The quick brown fox jumps over the lazy dog." - tree, err := Load(`str1 = "The quick brown fox jumps over the lazy dog." - -str2 = """ -The quick brown \ - - - fox jumps over \ - the lazy dog.""" - -str3 = """\ - The quick brown \` + " " + ` - fox jumps over \` + " " + ` - the lazy dog.\` + " " + ` - """`) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - - got := tree.Get("str1") - if got != expected { - t.Errorf("expected '%s', got '%s'", expected, got) - } - - got = tree.Get("str2") - if got != expected { - t.Errorf("expected '%s', got '%s'", expected, got) - } - - got = tree.Get("str3") - if got != expected { - t.Errorf("expected '%s', got '%s'", expected, got) - } -} diff --git a/position.go b/position.go deleted file mode 100644 index c17bff87..00000000 --- a/position.go +++ /dev/null @@ -1,29 +0,0 @@ -// Position support for go-toml - -package toml - -import ( - "fmt" -) - -// Position of a document element within a TOML document. -// -// Line and Col are both 1-indexed positions for the element's line number and -// column number, respectively. Values of zero or less will cause Invalid(), -// to return true. -type Position struct { - Line int // line within the document - Col int // column within the line -} - -// String representation of the position. -// Displays 1-indexed line and column numbers. -func (p Position) String() string { - return fmt.Sprintf("(%d, %d)", p.Line, p.Col) -} - -// Invalid returns whether or not the position is valid (i.e. with negative or -// null values) -func (p Position) Invalid() bool { - return p.Line <= 0 || p.Col <= 0 -} diff --git a/position_test.go b/position_test.go deleted file mode 100644 index 63ad1afc..00000000 --- a/position_test.go +++ /dev/null @@ -1,29 +0,0 @@ -// Testing support for go-toml - -package toml - -import ( - "testing" -) - -func TestPositionString(t *testing.T) { - p := Position{123, 456} - expected := "(123, 456)" - value := p.String() - - if value != expected { - t.Errorf("Expected %v, got %v instead", expected, value) - } -} - -func TestInvalid(t *testing.T) { - for i, v := range []Position{ - {0, 1234}, - {1234, 0}, - {0, 0}, - } { - if !v.Invalid() { - t.Errorf("Position at %v is valid: %v", i, v) - } - } -} diff --git a/query/README.md b/query/README.md deleted file mode 100644 index 75b3759c..00000000 --- a/query/README.md +++ /dev/null @@ -1,201 +0,0 @@ -# Query package - -## Overview - -Package query performs JSONPath-like queries on a TOML document. - -The query path implementation is based loosely on the JSONPath specification: -http://goessner.net/articles/JsonPath/. - -The idea behind a query path is to allow quick access to any element, or set -of elements within TOML document, with a single expression. - -```go -result, err := query.CompileAndExecute("$.foo.bar.baz", tree) -``` - -This is roughly equivalent to: - -```go -next := tree.Get("foo") -if next != nil { - next = next.Get("bar") - if next != nil { - next = next.Get("baz") - } -} -result := next -``` - -err is nil if any parsing exception occurs. - -If no node in the tree matches the query, result will simply contain an empty list of -items. - -As illustrated above, the query path is much more efficient, especially since -the structure of the TOML file can vary. Rather than making assumptions about -a document's structure, a query allows the programmer to make structured -requests into the document, and get zero or more values as a result. - -## Query syntax - -The syntax of a query begins with a root token, followed by any number -sub-expressions: - -``` -$ - Root of the TOML tree. This must always come first. -.name - Selects child of this node, where 'name' is a TOML key - name. -['name'] - Selects child of this node, where 'name' is a string - containing a TOML key name. -[index] - Selcts child array element at 'index'. -..expr - Recursively selects all children, filtered by an a union, - index, or slice expression. -..* - Recursive selection of all nodes at this point in the - tree. -.* - Selects all children of the current node. -[expr,expr] - Union operator - a logical 'or' grouping of two or more - sub-expressions: index, key name, or filter. -[start:end:step] - Slice operator - selects array elements from start to - end-1, at the given step. All three arguments are - optional. -[?(filter)] - Named filter expression - the function 'filter' is - used to filter children at this node. -``` - -## Query Indexes And Slices - -Index expressions perform no bounds checking, and will contribute no -values to the result set if the provided index or index range is invalid. -Negative indexes represent values from the end of the array, counting backwards. - -```go -// select the last index of the array named 'foo' -query.CompileAndExecute("$.foo[-1]", tree) -``` - -Slice expressions are supported, by using ':' to separate a start/end index pair. - -```go -// select up to the first five elements in the array -query.CompileAndExecute("$.foo[0:5]", tree) -``` - -Slice expressions also allow negative indexes for the start and stop -arguments. - -```go -// select all array elements except the last one. -query.CompileAndExecute("$.foo[0:-1]", tree) -``` - -Slice expressions may have an optional stride/step parameter: - -```go -// select every other element -query.CompileAndExecute("$.foo[0::2]", tree) -``` - -Slice start and end parameters are also optional: - -```go -// these are all equivalent and select all the values in the array -query.CompileAndExecute("$.foo[:]", tree) -query.CompileAndExecute("$.foo[::]", tree) -query.CompileAndExecute("$.foo[::1]", tree) -query.CompileAndExecute("$.foo[0:]", tree) -query.CompileAndExecute("$.foo[0::]", tree) -query.CompileAndExecute("$.foo[0::1]", tree) -``` - -## Query Filters - -Query filters are used within a Union [,] or single Filter [] expression. -A filter only allows nodes that qualify through to the next expression, -and/or into the result set. - -```go -// returns children of foo that are permitted by the 'bar' filter. -query.CompileAndExecute("$.foo[?(bar)]", tree) -``` - -There are several filters provided with the library: - -``` -tree - Allows nodes of type Tree. -int - Allows nodes of type int64. -float - Allows nodes of type float64. -string - Allows nodes of type string. -time - Allows nodes of type time.Time. -bool - Allows nodes of type bool. -``` - -## Query Results - -An executed query returns a Result object. This contains the nodes -in the TOML tree that qualify the query expression. Position information -is also available for each value in the set. - -```go -// display the results of a query -results := query.CompileAndExecute("$.foo.bar.baz", tree) -for idx, value := results.Values() { - fmt.Println("%v: %v", results.Positions()[idx], value) -} -``` - -## Compiled Queries - -Queries may be executed directly on a Tree object, or compiled ahead -of time and executed discretely. The former is more convenient, but has the -penalty of having to recompile the query expression each time. - -```go -// basic query -results := query.CompileAndExecute("$.foo.bar.baz", tree) - -// compiled query -query, err := toml.Compile("$.foo.bar.baz") -results := query.Execute(tree) - -// run the compiled query again on a different tree -moreResults := query.Execute(anotherTree) -``` - -## User Defined Query Filters - -Filter expressions may also be user defined by using the SetFilter() -function on the Query object. The function must return true/false, which -signifies if the passed node is kept or discarded, respectively. - -```go -// create a query that references a user-defined filter -query, _ := query.Compile("$[?(bazOnly)]") - -// define the filter, and assign it to the query -query.SetFilter("bazOnly", func(node interface{}) bool{ - if tree, ok := node.(*Tree); ok { - return tree.Has("baz") - } - return false // reject all other node types -}) - -// run the query -query.Execute(tree) -``` diff --git a/query/doc.go b/query/doc.go deleted file mode 100644 index d0efb219..00000000 --- a/query/doc.go +++ /dev/null @@ -1,173 +0,0 @@ -// Package query performs JSONPath-like queries on a TOML document. -// -// The query path implementation is based loosely on the JSONPath specification: -// http://goessner.net/articles/JsonPath/. -// -// The idea behind a query path is to allow quick access to any element, or set -// of elements within TOML document, with a single expression. -// -// result, err := query.CompileAndExecute("$.foo.bar.baz", tree) -// -// This is roughly equivalent to: -// -// next := tree.Get("foo") -// if next != nil { -// next = next.Get("bar") -// if next != nil { -// next = next.Get("baz") -// } -// } -// result := next -// -// err is nil if any parsing exception occurs. -// -// If no node in the tree matches the query, result will simply contain an empty list of -// items. -// -// As illustrated above, the query path is much more efficient, especially since -// the structure of the TOML file can vary. Rather than making assumptions about -// a document's structure, a query allows the programmer to make structured -// requests into the document, and get zero or more values as a result. -// -// Query syntax -// -// The syntax of a query begins with a root token, followed by any number -// sub-expressions: -// -// $ -// Root of the TOML tree. This must always come first. -// .name -// Selects child of this node, where 'name' is a TOML key -// name. -// ['name'] -// Selects child of this node, where 'name' is a string -// containing a TOML key name. -// [index] -// Selcts child array element at 'index'. -// ..expr -// Recursively selects all children, filtered by an a union, -// index, or slice expression. -// ..* -// Recursive selection of all nodes at this point in the -// tree. -// .* -// Selects all children of the current node. -// [expr,expr] -// Union operator - a logical 'or' grouping of two or more -// sub-expressions: index, key name, or filter. -// [start:end:step] -// Slice operator - selects array elements from start to -// end-1, at the given step. All three arguments are -// optional. -// [?(filter)] -// Named filter expression - the function 'filter' is -// used to filter children at this node. -// -// Query Indexes And Slices -// -// Index expressions perform no bounds checking, and will contribute no -// values to the result set if the provided index or index range is invalid. -// Negative indexes represent values from the end of the array, counting backwards. -// -// // select the last index of the array named 'foo' -// query.CompileAndExecute("$.foo[-1]", tree) -// -// Slice expressions are supported, by using ':' to separate a start/end index pair. -// -// // select up to the first five elements in the array -// query.CompileAndExecute("$.foo[0:5]", tree) -// -// Slice expressions also allow negative indexes for the start and stop -// arguments. -// -// // select all array elements except the last one. -// query.CompileAndExecute("$.foo[0:-1]", tree) -// -// Slice expressions may have an optional stride/step parameter: -// -// // select every other element -// query.CompileAndExecute("$.foo[0::2]", tree) -// -// Slice start and end parameters are also optional: -// -// // these are all equivalent and select all the values in the array -// query.CompileAndExecute("$.foo[:]", tree) -// query.CompileAndExecute("$.foo[::]", tree) -// query.CompileAndExecute("$.foo[::1]", tree) -// query.CompileAndExecute("$.foo[0:]", tree) -// query.CompileAndExecute("$.foo[0::]", tree) -// query.CompileAndExecute("$.foo[0::1]", tree) -// -// Query Filters -// -// Query filters are used within a Union [,] or single Filter [] expression. -// A filter only allows nodes that qualify through to the next expression, -// and/or into the result set. -// -// // returns children of foo that are permitted by the 'bar' filter. -// query.CompileAndExecute("$.foo[?(bar)]", tree) -// -// There are several filters provided with the library: -// -// tree -// Allows nodes of type Tree. -// int -// Allows nodes of type int64. -// float -// Allows nodes of type float64. -// string -// Allows nodes of type string. -// time -// Allows nodes of type time.Time. -// bool -// Allows nodes of type bool. -// -// Query Results -// -// An executed query returns a Result object. This contains the nodes -// in the TOML tree that qualify the query expression. Position information -// is also available for each value in the set. -// -// // display the results of a query -// results := query.CompileAndExecute("$.foo.bar.baz", tree) -// for idx, value := results.Values() { -// fmt.Println("%v: %v", results.Positions()[idx], value) -// } -// -// Compiled Queries -// -// Queries may be executed directly on a Tree object, or compiled ahead -// of time and executed discretely. The former is more convenient, but has the -// penalty of having to recompile the query expression each time. -// -// // basic query -// results := query.CompileAndExecute("$.foo.bar.baz", tree) -// -// // compiled query -// query, err := toml.Compile("$.foo.bar.baz") -// results := query.Execute(tree) -// -// // run the compiled query again on a different tree -// moreResults := query.Execute(anotherTree) -// -// User Defined Query Filters -// -// Filter expressions may also be user defined by using the SetFilter() -// function on the Query object. The function must return true/false, which -// signifies if the passed node is kept or discarded, respectively. -// -// // create a query that references a user-defined filter -// query, _ := query.Compile("$[?(bazOnly)]") -// -// // define the filter, and assign it to the query -// query.SetFilter("bazOnly", func(node interface{}) bool{ -// if tree, ok := node.(*Tree); ok { -// return tree.Has("baz") -// } -// return false // reject all other node types -// }) -// -// // run the query -// query.Execute(tree) -// -package query diff --git a/query/lexer.go b/query/lexer.go deleted file mode 100644 index 2dc31940..00000000 --- a/query/lexer.go +++ /dev/null @@ -1,357 +0,0 @@ -// TOML JSONPath lexer. -// -// Written using the principles developed by Rob Pike in -// http://www.youtube.com/watch?v=HxaD_trXwRE - -package query - -import ( - "fmt" - "github.com/pelletier/go-toml" - "strconv" - "strings" - "unicode/utf8" -) - -// Lexer state function -type queryLexStateFn func() queryLexStateFn - -// Lexer definition -type queryLexer struct { - input string - start int - pos int - width int - tokens chan token - depth int - line int - col int - stringTerm string -} - -func (l *queryLexer) run() { - for state := l.lexVoid; state != nil; { - state = state() - } - close(l.tokens) -} - -func (l *queryLexer) nextStart() { - // iterate by runes (utf8 characters) - // search for newlines and advance line/col counts - for i := l.start; i < l.pos; { - r, width := utf8.DecodeRuneInString(l.input[i:]) - if r == '\n' { - l.line++ - l.col = 1 - } else { - l.col++ - } - i += width - } - // advance start position to next token - l.start = l.pos -} - -func (l *queryLexer) emit(t tokenType) { - l.tokens <- token{ - Position: toml.Position{Line: l.line, Col: l.col}, - typ: t, - val: l.input[l.start:l.pos], - } - l.nextStart() -} - -func (l *queryLexer) emitWithValue(t tokenType, value string) { - l.tokens <- token{ - Position: toml.Position{Line: l.line, Col: l.col}, - typ: t, - val: value, - } - l.nextStart() -} - -func (l *queryLexer) next() rune { - if l.pos >= len(l.input) { - l.width = 0 - return eof - } - var r rune - r, l.width = utf8.DecodeRuneInString(l.input[l.pos:]) - l.pos += l.width - return r -} - -func (l *queryLexer) ignore() { - l.nextStart() -} - -func (l *queryLexer) backup() { - l.pos -= l.width -} - -func (l *queryLexer) errorf(format string, args ...interface{}) queryLexStateFn { - l.tokens <- token{ - Position: toml.Position{Line: l.line, Col: l.col}, - typ: tokenError, - val: fmt.Sprintf(format, args...), - } - return nil -} - -func (l *queryLexer) peek() rune { - r := l.next() - l.backup() - return r -} - -func (l *queryLexer) accept(valid string) bool { - if strings.ContainsRune(valid, l.next()) { - return true - } - l.backup() - return false -} - -func (l *queryLexer) follow(next string) bool { - return strings.HasPrefix(l.input[l.pos:], next) -} - -func (l *queryLexer) lexVoid() queryLexStateFn { - for { - next := l.peek() - switch next { - case '$': - l.pos++ - l.emit(tokenDollar) - continue - case '.': - if l.follow("..") { - l.pos += 2 - l.emit(tokenDotDot) - } else { - l.pos++ - l.emit(tokenDot) - } - continue - case '[': - l.pos++ - l.emit(tokenLeftBracket) - continue - case ']': - l.pos++ - l.emit(tokenRightBracket) - continue - case ',': - l.pos++ - l.emit(tokenComma) - continue - case '*': - l.pos++ - l.emit(tokenStar) - continue - case '(': - l.pos++ - l.emit(tokenLeftParen) - continue - case ')': - l.pos++ - l.emit(tokenRightParen) - continue - case '?': - l.pos++ - l.emit(tokenQuestion) - continue - case ':': - l.pos++ - l.emit(tokenColon) - continue - case '\'': - l.ignore() - l.stringTerm = string(next) - return l.lexString - case '"': - l.ignore() - l.stringTerm = string(next) - return l.lexString - } - - if isSpace(next) { - l.next() - l.ignore() - continue - } - - if isAlphanumeric(next) { - return l.lexKey - } - - if next == '+' || next == '-' || isDigit(next) { - return l.lexNumber - } - - if l.next() == eof { - break - } - - return l.errorf("unexpected char: '%v'", next) - } - l.emit(tokenEOF) - return nil -} - -func (l *queryLexer) lexKey() queryLexStateFn { - for { - next := l.peek() - if !isAlphanumeric(next) { - l.emit(tokenKey) - return l.lexVoid - } - - if l.next() == eof { - break - } - } - l.emit(tokenEOF) - return nil -} - -func (l *queryLexer) lexString() queryLexStateFn { - l.pos++ - l.ignore() - growingString := "" - - for { - if l.follow(l.stringTerm) { - l.emitWithValue(tokenString, growingString) - l.pos++ - l.ignore() - return l.lexVoid - } - - if l.follow("\\\"") { - l.pos++ - growingString += "\"" - } else if l.follow("\\'") { - l.pos++ - growingString += "'" - } else if l.follow("\\n") { - l.pos++ - growingString += "\n" - } else if l.follow("\\b") { - l.pos++ - growingString += "\b" - } else if l.follow("\\f") { - l.pos++ - growingString += "\f" - } else if l.follow("\\/") { - l.pos++ - growingString += "/" - } else if l.follow("\\t") { - l.pos++ - growingString += "\t" - } else if l.follow("\\r") { - l.pos++ - growingString += "\r" - } else if l.follow("\\\\") { - l.pos++ - growingString += "\\" - } else if l.follow("\\u") { - l.pos += 2 - code := "" - for i := 0; i < 4; i++ { - c := l.peek() - l.pos++ - if !isHexDigit(c) { - return l.errorf("unfinished unicode escape") - } - code = code + string(c) - } - l.pos-- - intcode, err := strconv.ParseInt(code, 16, 32) - if err != nil { - return l.errorf("invalid unicode escape: \\u" + code) - } - growingString += string(rune(intcode)) - } else if l.follow("\\U") { - l.pos += 2 - code := "" - for i := 0; i < 8; i++ { - c := l.peek() - l.pos++ - if !isHexDigit(c) { - return l.errorf("unfinished unicode escape") - } - code = code + string(c) - } - l.pos-- - intcode, err := strconv.ParseInt(code, 16, 32) - if err != nil { - return l.errorf("invalid unicode escape: \\u" + code) - } - growingString += string(rune(intcode)) - } else if l.follow("\\") { - l.pos++ - return l.errorf("invalid escape sequence: \\" + string(l.peek())) - } else { - growingString += string(l.peek()) - } - - if l.next() == eof { - break - } - } - - return l.errorf("unclosed string") -} - -func (l *queryLexer) lexNumber() queryLexStateFn { - l.ignore() - if !l.accept("+") { - l.accept("-") - } - pointSeen := false - digitSeen := false - for { - next := l.next() - if next == '.' { - if pointSeen { - return l.errorf("cannot have two dots in one float") - } - if !isDigit(l.peek()) { - return l.errorf("float cannot end with a dot") - } - pointSeen = true - } else if isDigit(next) { - digitSeen = true - } else { - l.backup() - break - } - if pointSeen && !digitSeen { - return l.errorf("cannot start float with a dot") - } - } - - if !digitSeen { - return l.errorf("no digit in that number") - } - if pointSeen { - l.emit(tokenFloat) - } else { - l.emit(tokenInteger) - } - return l.lexVoid -} - -// Entry point -func lexQuery(input string) chan token { - l := &queryLexer{ - input: input, - tokens: make(chan token), - line: 1, - col: 1, - } - go l.run() - return l.tokens -} diff --git a/query/lexer_test.go b/query/lexer_test.go deleted file mode 100644 index 8ce0501f..00000000 --- a/query/lexer_test.go +++ /dev/null @@ -1,179 +0,0 @@ -package query - -import ( - "github.com/pelletier/go-toml" - "testing" -) - -func testQLFlow(t *testing.T, input string, expectedFlow []token) { - ch := lexQuery(input) - for idx, expected := range expectedFlow { - token := <-ch - if token != expected { - t.Log("While testing #", idx, ":", input) - t.Log("compared (got)", token, "to (expected)", expected) - t.Log("\tvalue:", token.val, "<->", expected.val) - t.Log("\tvalue as bytes:", []byte(token.val), "<->", []byte(expected.val)) - t.Log("\ttype:", token.typ.String(), "<->", expected.typ.String()) - t.Log("\tline:", token.Line, "<->", expected.Line) - t.Log("\tcolumn:", token.Col, "<->", expected.Col) - t.Log("compared", token, "to", expected) - t.FailNow() - } - } - - tok, ok := <-ch - if ok { - t.Log("channel is not closed!") - t.Log(len(ch)+1, "tokens remaining:") - - t.Log("token ->", tok) - for token := range ch { - t.Log("token ->", token) - } - t.FailNow() - } -} - -func TestLexSpecialChars(t *testing.T) { - testQLFlow(t, " .$[]..()?*", []token{ - {toml.Position{1, 2}, tokenDot, "."}, - {toml.Position{1, 3}, tokenDollar, "$"}, - {toml.Position{1, 4}, tokenLeftBracket, "["}, - {toml.Position{1, 5}, tokenRightBracket, "]"}, - {toml.Position{1, 6}, tokenDotDot, ".."}, - {toml.Position{1, 8}, tokenLeftParen, "("}, - {toml.Position{1, 9}, tokenRightParen, ")"}, - {toml.Position{1, 10}, tokenQuestion, "?"}, - {toml.Position{1, 11}, tokenStar, "*"}, - {toml.Position{1, 12}, tokenEOF, ""}, - }) -} - -func TestLexString(t *testing.T) { - testQLFlow(t, "'foo\n'", []token{ - {toml.Position{1, 2}, tokenString, "foo\n"}, - {toml.Position{2, 2}, tokenEOF, ""}, - }) -} - -func TestLexDoubleString(t *testing.T) { - testQLFlow(t, `"bar"`, []token{ - {toml.Position{1, 2}, tokenString, "bar"}, - {toml.Position{1, 6}, tokenEOF, ""}, - }) -} - -func TestLexStringEscapes(t *testing.T) { - testQLFlow(t, `"foo \" \' \b \f \/ \t \r \\ \u03A9 \U00012345 \n bar"`, []token{ - {toml.Position{1, 2}, tokenString, "foo \" ' \b \f / \t \r \\ \u03A9 \U00012345 \n bar"}, - {toml.Position{1, 55}, tokenEOF, ""}, - }) -} - -func TestLexStringUnfinishedUnicode4(t *testing.T) { - testQLFlow(t, `"\u000"`, []token{ - {toml.Position{1, 2}, tokenError, "unfinished unicode escape"}, - }) -} - -func TestLexStringUnfinishedUnicode8(t *testing.T) { - testQLFlow(t, `"\U0000"`, []token{ - {toml.Position{1, 2}, tokenError, "unfinished unicode escape"}, - }) -} - -func TestLexStringInvalidEscape(t *testing.T) { - testQLFlow(t, `"\x"`, []token{ - {toml.Position{1, 2}, tokenError, "invalid escape sequence: \\x"}, - }) -} - -func TestLexStringUnfinished(t *testing.T) { - testQLFlow(t, `"bar`, []token{ - {toml.Position{1, 2}, tokenError, "unclosed string"}, - }) -} - -func TestLexKey(t *testing.T) { - testQLFlow(t, "foo", []token{ - {toml.Position{1, 1}, tokenKey, "foo"}, - {toml.Position{1, 4}, tokenEOF, ""}, - }) -} - -func TestLexRecurse(t *testing.T) { - testQLFlow(t, "$..*", []token{ - {toml.Position{1, 1}, tokenDollar, "$"}, - {toml.Position{1, 2}, tokenDotDot, ".."}, - {toml.Position{1, 4}, tokenStar, "*"}, - {toml.Position{1, 5}, tokenEOF, ""}, - }) -} - -func TestLexBracketKey(t *testing.T) { - testQLFlow(t, "$[foo]", []token{ - {toml.Position{1, 1}, tokenDollar, "$"}, - {toml.Position{1, 2}, tokenLeftBracket, "["}, - {toml.Position{1, 3}, tokenKey, "foo"}, - {toml.Position{1, 6}, tokenRightBracket, "]"}, - {toml.Position{1, 7}, tokenEOF, ""}, - }) -} - -func TestLexSpace(t *testing.T) { - testQLFlow(t, "foo bar baz", []token{ - {toml.Position{1, 1}, tokenKey, "foo"}, - {toml.Position{1, 5}, tokenKey, "bar"}, - {toml.Position{1, 9}, tokenKey, "baz"}, - {toml.Position{1, 12}, tokenEOF, ""}, - }) -} - -func TestLexInteger(t *testing.T) { - testQLFlow(t, "100 +200 -300", []token{ - {toml.Position{1, 1}, tokenInteger, "100"}, - {toml.Position{1, 5}, tokenInteger, "+200"}, - {toml.Position{1, 10}, tokenInteger, "-300"}, - {toml.Position{1, 14}, tokenEOF, ""}, - }) -} - -func TestLexFloat(t *testing.T) { - testQLFlow(t, "100.0 +200.0 -300.0", []token{ - {toml.Position{1, 1}, tokenFloat, "100.0"}, - {toml.Position{1, 7}, tokenFloat, "+200.0"}, - {toml.Position{1, 14}, tokenFloat, "-300.0"}, - {toml.Position{1, 20}, tokenEOF, ""}, - }) -} - -func TestLexFloatWithMultipleDots(t *testing.T) { - testQLFlow(t, "4.2.", []token{ - {toml.Position{1, 1}, tokenError, "cannot have two dots in one float"}, - }) -} - -func TestLexFloatLeadingDot(t *testing.T) { - testQLFlow(t, "+.1", []token{ - {toml.Position{1, 1}, tokenError, "cannot start float with a dot"}, - }) -} - -func TestLexFloatWithTrailingDot(t *testing.T) { - testQLFlow(t, "42.", []token{ - {toml.Position{1, 1}, tokenError, "float cannot end with a dot"}, - }) -} - -func TestLexNumberWithoutDigit(t *testing.T) { - testQLFlow(t, "+", []token{ - {toml.Position{1, 1}, tokenError, "no digit in that number"}, - }) -} - -func TestLexUnknown(t *testing.T) { - testQLFlow(t, "^", []token{ - {toml.Position{1, 1}, tokenError, "unexpected char: '94'"}, - }) -} diff --git a/query/match.go b/query/match.go deleted file mode 100644 index 37b43da2..00000000 --- a/query/match.go +++ /dev/null @@ -1,311 +0,0 @@ -package query - -import ( - "fmt" - "reflect" - - "github.com/pelletier/go-toml" -) - -// base match -type matchBase struct { - next pathFn -} - -func (f *matchBase) setNext(next pathFn) { - f.next = next -} - -// terminating functor - gathers results -type terminatingFn struct { - // empty -} - -func newTerminatingFn() *terminatingFn { - return &terminatingFn{} -} - -func (f *terminatingFn) setNext(next pathFn) { - // do nothing -} - -func (f *terminatingFn) call(node interface{}, ctx *queryContext) { - ctx.result.appendResult(node, ctx.lastPosition) -} - -// match single key -type matchKeyFn struct { - matchBase - Name string -} - -func newMatchKeyFn(name string) *matchKeyFn { - return &matchKeyFn{Name: name} -} - -func (f *matchKeyFn) call(node interface{}, ctx *queryContext) { - if array, ok := node.([]*toml.Tree); ok { - for _, tree := range array { - item := tree.GetPath([]string{f.Name}) - if item != nil { - ctx.lastPosition = tree.GetPositionPath([]string{f.Name}) - f.next.call(item, ctx) - } - } - } else if tree, ok := node.(*toml.Tree); ok { - item := tree.GetPath([]string{f.Name}) - if item != nil { - ctx.lastPosition = tree.GetPositionPath([]string{f.Name}) - f.next.call(item, ctx) - } - } -} - -// match single index -type matchIndexFn struct { - matchBase - Idx int -} - -func newMatchIndexFn(idx int) *matchIndexFn { - return &matchIndexFn{Idx: idx} -} - -func (f *matchIndexFn) call(node interface{}, ctx *queryContext) { - v := reflect.ValueOf(node) - if v.Kind() == reflect.Slice { - if v.Len() == 0 { - return - } - - // Manage negative values - idx := f.Idx - if idx < 0 { - idx += v.Len() - } - if 0 <= idx && idx < v.Len() { - callNextIndexSlice(f.next, node, ctx, v.Index(idx).Interface()) - } - } -} - -func callNextIndexSlice(next pathFn, node interface{}, ctx *queryContext, value interface{}) { - if treesArray, ok := node.([]*toml.Tree); ok { - ctx.lastPosition = treesArray[0].Position() - } - next.call(value, ctx) -} - -// filter by slicing -type matchSliceFn struct { - matchBase - Start, End, Step *int -} - -func newMatchSliceFn() *matchSliceFn { - return &matchSliceFn{} -} - -func (f *matchSliceFn) setStart(start int) *matchSliceFn { - f.Start = &start - return f -} - -func (f *matchSliceFn) setEnd(end int) *matchSliceFn { - f.End = &end - return f -} - -func (f *matchSliceFn) setStep(step int) *matchSliceFn { - f.Step = &step - return f -} - -func (f *matchSliceFn) call(node interface{}, ctx *queryContext) { - v := reflect.ValueOf(node) - if v.Kind() == reflect.Slice { - if v.Len() == 0 { - return - } - - var start, end, step int - - // Initialize step - if f.Step != nil { - step = *f.Step - } else { - step = 1 - } - - // Initialize start - if f.Start != nil { - start = *f.Start - // Manage negative values - if start < 0 { - start += v.Len() - } - // Manage out of range values - start = max(start, 0) - start = min(start, v.Len()-1) - } else if step > 0 { - start = 0 - } else { - start = v.Len() - 1 - } - - // Initialize end - if f.End != nil { - end = *f.End - // Manage negative values - if end < 0 { - end += v.Len() - } - // Manage out of range values - end = max(end, -1) - end = min(end, v.Len()) - } else if step > 0 { - end = v.Len() - } else { - end = -1 - } - - // Loop on values - if step > 0 { - for idx := start; idx < end; idx += step { - callNextIndexSlice(f.next, node, ctx, v.Index(idx).Interface()) - } - } else { - for idx := start; idx > end; idx += step { - callNextIndexSlice(f.next, node, ctx, v.Index(idx).Interface()) - } - } - } -} - -func min(a, b int) int { - if a < b { - return a - } - return b -} - -func max(a, b int) int { - if a > b { - return a - } - return b -} - -// match anything -type matchAnyFn struct { - matchBase -} - -func newMatchAnyFn() *matchAnyFn { - return &matchAnyFn{} -} - -func (f *matchAnyFn) call(node interface{}, ctx *queryContext) { - if tree, ok := node.(*toml.Tree); ok { - for _, k := range tree.Keys() { - v := tree.GetPath([]string{k}) - ctx.lastPosition = tree.GetPositionPath([]string{k}) - f.next.call(v, ctx) - } - } -} - -// filter through union -type matchUnionFn struct { - Union []pathFn -} - -func (f *matchUnionFn) setNext(next pathFn) { - for _, fn := range f.Union { - fn.setNext(next) - } -} - -func (f *matchUnionFn) call(node interface{}, ctx *queryContext) { - for _, fn := range f.Union { - fn.call(node, ctx) - } -} - -// match every single last node in the tree -type matchRecursiveFn struct { - matchBase -} - -func newMatchRecursiveFn() *matchRecursiveFn { - return &matchRecursiveFn{} -} - -func (f *matchRecursiveFn) call(node interface{}, ctx *queryContext) { - originalPosition := ctx.lastPosition - if tree, ok := node.(*toml.Tree); ok { - var visit func(tree *toml.Tree) - visit = func(tree *toml.Tree) { - for _, k := range tree.Keys() { - v := tree.GetPath([]string{k}) - ctx.lastPosition = tree.GetPositionPath([]string{k}) - f.next.call(v, ctx) - switch node := v.(type) { - case *toml.Tree: - visit(node) - case []*toml.Tree: - for _, subtree := range node { - visit(subtree) - } - } - } - } - ctx.lastPosition = originalPosition - f.next.call(tree, ctx) - visit(tree) - } -} - -// match based on an externally provided functional filter -type matchFilterFn struct { - matchBase - Pos toml.Position - Name string -} - -func newMatchFilterFn(name string, pos toml.Position) *matchFilterFn { - return &matchFilterFn{Name: name, Pos: pos} -} - -func (f *matchFilterFn) call(node interface{}, ctx *queryContext) { - fn, ok := (*ctx.filters)[f.Name] - if !ok { - panic(fmt.Sprintf("%s: query context does not have filter '%s'", - f.Pos.String(), f.Name)) - } - switch castNode := node.(type) { - case *toml.Tree: - for _, k := range castNode.Keys() { - v := castNode.GetPath([]string{k}) - if fn(v) { - ctx.lastPosition = castNode.GetPositionPath([]string{k}) - f.next.call(v, ctx) - } - } - case []*toml.Tree: - for _, v := range castNode { - if fn(v) { - if len(castNode) > 0 { - ctx.lastPosition = castNode[0].Position() - } - f.next.call(v, ctx) - } - } - case []interface{}: - for _, v := range castNode { - if fn(v) { - f.next.call(v, ctx) - } - } - } -} diff --git a/query/match_test.go b/query/match_test.go deleted file mode 100644 index 47472c16..00000000 --- a/query/match_test.go +++ /dev/null @@ -1,213 +0,0 @@ -package query - -import ( - "fmt" - "strconv" - "testing" - - "github.com/pelletier/go-toml" -) - -// dump path tree to a string -func pathString(root pathFn) string { - result := fmt.Sprintf("%T:", root) - switch fn := root.(type) { - case *terminatingFn: - result += "{}" - case *matchKeyFn: - result += fmt.Sprintf("{%s}", fn.Name) - result += pathString(fn.next) - case *matchIndexFn: - result += fmt.Sprintf("{%d}", fn.Idx) - result += pathString(fn.next) - case *matchSliceFn: - startString, endString, stepString := "nil", "nil", "nil" - if fn.Start != nil { - startString = strconv.Itoa(*fn.Start) - } - if fn.End != nil { - endString = strconv.Itoa(*fn.End) - } - if fn.Step != nil { - stepString = strconv.Itoa(*fn.Step) - } - result += fmt.Sprintf("{%s:%s:%s}", startString, endString, stepString) - result += pathString(fn.next) - case *matchAnyFn: - result += "{}" - result += pathString(fn.next) - case *matchUnionFn: - result += "{[" - for _, v := range fn.Union { - result += pathString(v) + ", " - } - result += "]}" - case *matchRecursiveFn: - result += "{}" - result += pathString(fn.next) - case *matchFilterFn: - result += fmt.Sprintf("{%s}", fn.Name) - result += pathString(fn.next) - } - return result -} - -func assertPathMatch(t *testing.T, path, ref *Query) bool { - pathStr := pathString(path.root) - refStr := pathString(ref.root) - if pathStr != refStr { - t.Errorf("paths do not match") - t.Log("test:", pathStr) - t.Log("ref: ", refStr) - return false - } - return true -} - -func assertPath(t *testing.T, query string, ref *Query) { - path, _ := parseQuery(lexQuery(query)) - assertPathMatch(t, path, ref) -} - -func buildPath(parts ...pathFn) *Query { - query := newQuery() - for _, v := range parts { - query.appendPath(v) - } - return query -} - -func TestPathRoot(t *testing.T) { - assertPath(t, - "$", - buildPath( - // empty - )) -} - -func TestPathKey(t *testing.T) { - assertPath(t, - "$.foo", - buildPath( - newMatchKeyFn("foo"), - )) -} - -func TestPathBracketKey(t *testing.T) { - assertPath(t, - "$[foo]", - buildPath( - newMatchKeyFn("foo"), - )) -} - -func TestPathBracketStringKey(t *testing.T) { - assertPath(t, - "$['foo']", - buildPath( - newMatchKeyFn("foo"), - )) -} - -func TestPathIndex(t *testing.T) { - assertPath(t, - "$[123]", - buildPath( - newMatchIndexFn(123), - )) -} - -func TestPathSliceStart(t *testing.T) { - assertPath(t, - "$[123:]", - buildPath( - newMatchSliceFn().setStart(123), - )) -} - -func TestPathSliceStartEnd(t *testing.T) { - assertPath(t, - "$[123:456]", - buildPath( - newMatchSliceFn().setStart(123).setEnd(456), - )) -} - -func TestPathSliceStartEndColon(t *testing.T) { - assertPath(t, - "$[123:456:]", - buildPath( - newMatchSliceFn().setStart(123).setEnd(456), - )) -} - -func TestPathSliceStartStep(t *testing.T) { - assertPath(t, - "$[123::7]", - buildPath( - newMatchSliceFn().setStart(123).setStep(7), - )) -} - -func TestPathSliceEndStep(t *testing.T) { - assertPath(t, - "$[:456:7]", - buildPath( - newMatchSliceFn().setEnd(456).setStep(7), - )) -} - -func TestPathSliceStep(t *testing.T) { - assertPath(t, - "$[::7]", - buildPath( - newMatchSliceFn().setStep(7), - )) -} - -func TestPathSliceAll(t *testing.T) { - assertPath(t, - "$[123:456:7]", - buildPath( - newMatchSliceFn().setStart(123).setEnd(456).setStep(7), - )) -} - -func TestPathAny(t *testing.T) { - assertPath(t, - "$.*", - buildPath( - newMatchAnyFn(), - )) -} - -func TestPathUnion(t *testing.T) { - assertPath(t, - "$[foo, bar, baz]", - buildPath( - &matchUnionFn{[]pathFn{ - newMatchKeyFn("foo"), - newMatchKeyFn("bar"), - newMatchKeyFn("baz"), - }}, - )) -} - -func TestPathRecurse(t *testing.T) { - assertPath(t, - "$..*", - buildPath( - newMatchRecursiveFn(), - )) -} - -func TestPathFilterExpr(t *testing.T) { - assertPath(t, - "$[?('foo'),?(bar)]", - buildPath( - &matchUnionFn{[]pathFn{ - newMatchFilterFn("foo", toml.Position{}), - newMatchFilterFn("bar", toml.Position{}), - }}, - )) -} diff --git a/query/parser.go b/query/parser.go deleted file mode 100644 index be27d350..00000000 --- a/query/parser.go +++ /dev/null @@ -1,278 +0,0 @@ -/* - Based on the "jsonpath" spec/concept. - - http://goessner.net/articles/JsonPath/ - https://code.google.com/p/json-path/ -*/ - -package query - -import ( - "fmt" -) - -const maxInt = int(^uint(0) >> 1) - -type queryParser struct { - flow chan token - tokensBuffer []token - query *Query - union []pathFn - err error -} - -type queryParserStateFn func() queryParserStateFn - -// Formats and panics an error message based on a token -func (p *queryParser) parseError(tok *token, msg string, args ...interface{}) queryParserStateFn { - p.err = fmt.Errorf(tok.Position.String()+": "+msg, args...) - return nil // trigger parse to end -} - -func (p *queryParser) run() { - for state := p.parseStart; state != nil; { - state = state() - } -} - -func (p *queryParser) backup(tok *token) { - p.tokensBuffer = append(p.tokensBuffer, *tok) -} - -func (p *queryParser) peek() *token { - if len(p.tokensBuffer) != 0 { - return &(p.tokensBuffer[0]) - } - - tok, ok := <-p.flow - if !ok { - return nil - } - p.backup(&tok) - return &tok -} - -func (p *queryParser) lookahead(types ...tokenType) bool { - result := true - buffer := []token{} - - for _, typ := range types { - tok := p.getToken() - if tok == nil { - result = false - break - } - buffer = append(buffer, *tok) - if tok.typ != typ { - result = false - break - } - } - // add the tokens back to the buffer, and return - p.tokensBuffer = append(p.tokensBuffer, buffer...) - return result -} - -func (p *queryParser) getToken() *token { - if len(p.tokensBuffer) != 0 { - tok := p.tokensBuffer[0] - p.tokensBuffer = p.tokensBuffer[1:] - return &tok - } - tok, ok := <-p.flow - if !ok { - return nil - } - return &tok -} - -func (p *queryParser) parseStart() queryParserStateFn { - tok := p.getToken() - - if tok == nil || tok.typ == tokenEOF { - return nil - } - - if tok.typ != tokenDollar { - return p.parseError(tok, "Expected '$' at start of expression") - } - - return p.parseMatchExpr -} - -// handle '.' prefix, '[]', and '..' -func (p *queryParser) parseMatchExpr() queryParserStateFn { - tok := p.getToken() - switch tok.typ { - case tokenDotDot: - p.query.appendPath(&matchRecursiveFn{}) - // nested parse for '..' - tok := p.getToken() - switch tok.typ { - case tokenKey: - p.query.appendPath(newMatchKeyFn(tok.val)) - return p.parseMatchExpr - case tokenLeftBracket: - return p.parseBracketExpr - case tokenStar: - // do nothing - the recursive predicate is enough - return p.parseMatchExpr - } - - case tokenDot: - // nested parse for '.' - tok := p.getToken() - switch tok.typ { - case tokenKey: - p.query.appendPath(newMatchKeyFn(tok.val)) - return p.parseMatchExpr - case tokenStar: - p.query.appendPath(&matchAnyFn{}) - return p.parseMatchExpr - } - - case tokenLeftBracket: - return p.parseBracketExpr - - case tokenEOF: - return nil // allow EOF at this stage - } - return p.parseError(tok, "expected match expression") -} - -func (p *queryParser) parseBracketExpr() queryParserStateFn { - if p.lookahead(tokenInteger, tokenColon) { - return p.parseSliceExpr - } - if p.peek().typ == tokenColon { - return p.parseSliceExpr - } - return p.parseUnionExpr -} - -func (p *queryParser) parseUnionExpr() queryParserStateFn { - var tok *token - - // this state can be traversed after some sub-expressions - // so be careful when setting up state in the parser - if p.union == nil { - p.union = []pathFn{} - } - -loop: // labeled loop for easy breaking - for { - if len(p.union) > 0 { - // parse delimiter or terminator - tok = p.getToken() - switch tok.typ { - case tokenComma: - // do nothing - case tokenRightBracket: - break loop - default: - return p.parseError(tok, "expected ',' or ']', not '%s'", tok.val) - } - } - - // parse sub expression - tok = p.getToken() - switch tok.typ { - case tokenInteger: - p.union = append(p.union, newMatchIndexFn(tok.Int())) - case tokenKey: - p.union = append(p.union, newMatchKeyFn(tok.val)) - case tokenString: - p.union = append(p.union, newMatchKeyFn(tok.val)) - case tokenQuestion: - return p.parseFilterExpr - default: - return p.parseError(tok, "expected union sub expression, not '%s', %d", tok.val, len(p.union)) - } - } - - // if there is only one sub-expression, use that instead - if len(p.union) == 1 { - p.query.appendPath(p.union[0]) - } else { - p.query.appendPath(&matchUnionFn{p.union}) - } - - p.union = nil // clear out state - return p.parseMatchExpr -} - -func (p *queryParser) parseSliceExpr() queryParserStateFn { - // init slice to grab all elements - var start, end, step *int = nil, nil, nil - - // parse optional start - tok := p.getToken() - if tok.typ == tokenInteger { - v := tok.Int() - start = &v - tok = p.getToken() - } - if tok.typ != tokenColon { - return p.parseError(tok, "expected ':'") - } - - // parse optional end - tok = p.getToken() - if tok.typ == tokenInteger { - v := tok.Int() - end = &v - tok = p.getToken() - } - if tok.typ == tokenRightBracket { - p.query.appendPath(&matchSliceFn{Start: start, End: end, Step: step}) - return p.parseMatchExpr - } - if tok.typ != tokenColon { - return p.parseError(tok, "expected ']' or ':'") - } - - // parse optional step - tok = p.getToken() - if tok.typ == tokenInteger { - v := tok.Int() - if v == 0 { - return p.parseError(tok, "step cannot be zero") - } - step = &v - tok = p.getToken() - } - if tok.typ != tokenRightBracket { - return p.parseError(tok, "expected ']'") - } - - p.query.appendPath(&matchSliceFn{Start: start, End: end, Step: step}) - return p.parseMatchExpr -} - -func (p *queryParser) parseFilterExpr() queryParserStateFn { - tok := p.getToken() - if tok.typ != tokenLeftParen { - return p.parseError(tok, "expected left-parenthesis for filter expression") - } - tok = p.getToken() - if tok.typ != tokenKey && tok.typ != tokenString { - return p.parseError(tok, "expected key or string for filter function name") - } - name := tok.val - tok = p.getToken() - if tok.typ != tokenRightParen { - return p.parseError(tok, "expected right-parenthesis for filter expression") - } - p.union = append(p.union, newMatchFilterFn(name, tok.Position)) - return p.parseUnionExpr -} - -func parseQuery(flow chan token) (*Query, error) { - parser := &queryParser{ - flow: flow, - tokensBuffer: []token{}, - query: newQuery(), - } - parser.run() - return parser.query, parser.err -} diff --git a/query/parser_test.go b/query/parser_test.go deleted file mode 100644 index 91d3f709..00000000 --- a/query/parser_test.go +++ /dev/null @@ -1,613 +0,0 @@ -package query - -import ( - "fmt" - "io/ioutil" - "sort" - "strings" - "testing" - "time" - - "github.com/pelletier/go-toml" -) - -type queryTestNode struct { - value interface{} - position toml.Position -} - -func valueString(root interface{}) string { - result := "" //fmt.Sprintf("%T:", root) - switch node := root.(type) { - case *Result: - items := []string{} - for i, v := range node.Values() { - items = append(items, fmt.Sprintf("%s:%s", - node.Positions()[i].String(), valueString(v))) - } - sort.Strings(items) - result = "[" + strings.Join(items, ", ") + "]" - case queryTestNode: - result = fmt.Sprintf("%s:%s", - node.position.String(), valueString(node.value)) - case []interface{}: - items := []string{} - for _, v := range node { - items = append(items, valueString(v)) - } - sort.Strings(items) - result = "[" + strings.Join(items, ", ") + "]" - case *toml.Tree: - // workaround for unreliable map key ordering - items := []string{} - for _, k := range node.Keys() { - v := node.GetPath([]string{k}) - items = append(items, k+":"+valueString(v)) - } - sort.Strings(items) - result = "{" + strings.Join(items, ", ") + "}" - case map[string]interface{}: - // workaround for unreliable map key ordering - items := []string{} - for k, v := range node { - items = append(items, k+":"+valueString(v)) - } - sort.Strings(items) - result = "{" + strings.Join(items, ", ") + "}" - case int64: - result += fmt.Sprintf("%d", node) - case string: - result += "'" + node + "'" - case float64: - result += fmt.Sprintf("%f", node) - case bool: - result += fmt.Sprintf("%t", node) - case time.Time: - result += fmt.Sprintf("'%v'", node) - } - return result -} - -func assertValue(t *testing.T, result, ref interface{}) { - pathStr := valueString(result) - refStr := valueString(ref) - if pathStr != refStr { - t.Errorf("values do not match") - t.Log("test:", pathStr) - t.Log("ref: ", refStr) - } -} - -func assertParseError(t *testing.T, query string, errString string) { - _, err := Compile(query) - if err == nil { - t.Error("error should be non-nil") - return - } - if err.Error() != errString { - t.Errorf("error does not match") - t.Log("test:", err.Error()) - t.Log("ref: ", errString) - } -} - -func assertQueryPositions(t *testing.T, tomlDoc string, query string, ref []interface{}) { - tree, err := toml.Load(tomlDoc) - if err != nil { - t.Errorf("Non-nil toml parse error: %v", err) - return - } - q, err := Compile(query) - if err != nil { - t.Error(err) - return - } - results := q.Execute(tree) - assertValue(t, results, ref) -} - -func TestQueryRoot(t *testing.T) { - assertQueryPositions(t, - "a = 42", - "$", - []interface{}{ - queryTestNode{ - map[string]interface{}{ - "a": int64(42), - }, toml.Position{1, 1}, - }, - }) -} - -func TestQueryKey(t *testing.T) { - assertQueryPositions(t, - "[foo]\na = 42", - "$.foo.a", - []interface{}{ - queryTestNode{ - int64(42), toml.Position{2, 1}, - }, - }) -} - -func TestQueryKeyString(t *testing.T) { - assertQueryPositions(t, - "[foo]\na = 42", - "$.foo['a']", - []interface{}{ - queryTestNode{ - int64(42), toml.Position{2, 1}, - }, - }) -} - -func TestQueryKeyUnicodeString(t *testing.T) { - assertQueryPositions(t, - "['f𝟘.o']\na = 42", - "$['f𝟘.o']['a']", - []interface{}{ - queryTestNode{ - int64(42), toml.Position{2, 1}, - }, - }) -} - -func TestQueryIndexError1(t *testing.T) { - assertParseError(t, "$.foo.a[5", "(1, 10): expected ',' or ']', not ''") -} - -func TestQueryIndexError2(t *testing.T) { - assertParseError(t, "$.foo.a[]", "(1, 9): expected union sub expression, not ']', 0") -} - -func TestQueryIndex(t *testing.T) { - assertQueryPositions(t, - "[foo]\na = [0,1,2,3,4,5,6,7,8,9]", - "$.foo.a[5]", - []interface{}{ - queryTestNode{int64(5), toml.Position{2, 1}}, - }) -} - -func TestQueryIndexNegative(t *testing.T) { - assertQueryPositions(t, - "[foo]\na = [0,1,2,3,4,5,6,7,8,9]", - "$.foo.a[-2]", - []interface{}{ - queryTestNode{int64(8), toml.Position{2, 1}}, - }) -} - -func TestQueryIndexWrong(t *testing.T) { - assertQueryPositions(t, - "[foo]\na = [0,1,2,3,4,5,6,7,8,9]", - "$.foo.a[99]", - []interface{}{}) -} - -func TestQueryIndexEmpty(t *testing.T) { - assertQueryPositions(t, - "[foo]\na = []", - "$.foo.a[5]", - []interface{}{}) -} - -func TestQueryIndexTree(t *testing.T) { - assertQueryPositions(t, - "[[foo]]\na = [0,1,2,3,4,5,6,7,8,9]\n[[foo]]\nb = 3", - "$.foo[1].b", - []interface{}{ - queryTestNode{int64(3), toml.Position{4, 1}}, - }) -} - -func TestQuerySliceError1(t *testing.T) { - assertParseError(t, "$.foo.a[3:?]", "(1, 11): expected ']' or ':'") -} - -func TestQuerySliceError2(t *testing.T) { - assertParseError(t, "$.foo.a[:::]", "(1, 11): expected ']'") -} - -func TestQuerySliceError3(t *testing.T) { - assertParseError(t, "$.foo.a[::0]", "(1, 11): step cannot be zero") -} - -func TestQuerySliceRange(t *testing.T) { - assertQueryPositions(t, - "[foo]\na = [0,1,2,3,4,5,6,7,8,9]", - "$.foo.a[:5]", - []interface{}{ - queryTestNode{int64(0), toml.Position{2, 1}}, - queryTestNode{int64(1), toml.Position{2, 1}}, - queryTestNode{int64(2), toml.Position{2, 1}}, - queryTestNode{int64(3), toml.Position{2, 1}}, - queryTestNode{int64(4), toml.Position{2, 1}}, - }) -} - -func TestQuerySliceStep(t *testing.T) { - assertQueryPositions(t, - "[foo]\na = [0,1,2,3,4,5,6,7,8,9]", - "$.foo.a[0:5:2]", - []interface{}{ - queryTestNode{int64(0), toml.Position{2, 1}}, - queryTestNode{int64(2), toml.Position{2, 1}}, - queryTestNode{int64(4), toml.Position{2, 1}}, - }) -} - -func TestQuerySliceStartNegative(t *testing.T) { - assertQueryPositions(t, - "[foo]\na = [0,1,2,3,4,5,6,7,8,9]", - "$.foo.a[-3:]", - []interface{}{ - queryTestNode{int64(7), toml.Position{2, 1}}, - queryTestNode{int64(8), toml.Position{2, 1}}, - queryTestNode{int64(9), toml.Position{2, 1}}, - }) -} - -func TestQuerySliceEndNegative(t *testing.T) { - assertQueryPositions(t, - "[foo]\na = [0,1,2,3,4,5,6,7,8,9]", - "$.foo.a[:-6]", - []interface{}{ - queryTestNode{int64(0), toml.Position{2, 1}}, - queryTestNode{int64(1), toml.Position{2, 1}}, - queryTestNode{int64(2), toml.Position{2, 1}}, - queryTestNode{int64(3), toml.Position{2, 1}}, - }) -} - -func TestQuerySliceStepNegative(t *testing.T) { - assertQueryPositions(t, - "[foo]\na = [0,1,2,3,4,5,6,7,8,9]", - "$.foo.a[::-2]", - []interface{}{ - queryTestNode{int64(9), toml.Position{2, 1}}, - queryTestNode{int64(7), toml.Position{2, 1}}, - queryTestNode{int64(5), toml.Position{2, 1}}, - queryTestNode{int64(3), toml.Position{2, 1}}, - queryTestNode{int64(1), toml.Position{2, 1}}, - }) -} - -func TestQuerySliceStartOverRange(t *testing.T) { - assertQueryPositions(t, - "[foo]\na = [0,1,2,3,4,5,6,7,8,9]", - "$.foo.a[-99:3]", - []interface{}{ - queryTestNode{int64(0), toml.Position{2, 1}}, - queryTestNode{int64(1), toml.Position{2, 1}}, - queryTestNode{int64(2), toml.Position{2, 1}}, - }) -} - -func TestQuerySliceStartOverRangeNegative(t *testing.T) { - assertQueryPositions(t, - "[foo]\na = [0,1,2,3,4,5,6,7,8,9]", - "$.foo.a[99:7:-1]", - []interface{}{ - queryTestNode{int64(9), toml.Position{2, 1}}, - queryTestNode{int64(8), toml.Position{2, 1}}, - }) -} - -func TestQuerySliceEndOverRange(t *testing.T) { - assertQueryPositions(t, - "[foo]\na = [0,1,2,3,4,5,6,7,8,9]", - "$.foo.a[7:99]", - []interface{}{ - queryTestNode{int64(7), toml.Position{2, 1}}, - queryTestNode{int64(8), toml.Position{2, 1}}, - queryTestNode{int64(9), toml.Position{2, 1}}, - }) -} - -func TestQuerySliceEndOverRangeNegative(t *testing.T) { - assertQueryPositions(t, - "[foo]\na = [0,1,2,3,4,5,6,7,8,9]", - "$.foo.a[2:-99:-1]", - []interface{}{ - queryTestNode{int64(2), toml.Position{2, 1}}, - queryTestNode{int64(1), toml.Position{2, 1}}, - queryTestNode{int64(0), toml.Position{2, 1}}, - }) -} - -func TestQuerySliceWrongRange(t *testing.T) { - assertQueryPositions(t, - "[foo]\na = [0,1,2,3,4,5,6,7,8,9]", - "$.foo.a[5:3]", - []interface{}{}) -} - -func TestQuerySliceWrongRangeNegative(t *testing.T) { - assertQueryPositions(t, - "[foo]\na = [0,1,2,3,4,5,6,7,8,9]", - "$.foo.a[3:5:-1]", - []interface{}{}) -} - -func TestQuerySliceEmpty(t *testing.T) { - assertQueryPositions(t, - "[foo]\na = []", - "$.foo.a[5:]", - []interface{}{}) -} - -func TestQuerySliceTree(t *testing.T) { - assertQueryPositions(t, - "[[foo]]\na='nok'\n[[foo]]\na = [0,1,2,3,4,5,6,7,8,9]\n[[foo]]\na='ok'\nb = 3", - "$.foo[1:].a", - []interface{}{ - queryTestNode{ - []interface{}{ - int64(0), int64(1), int64(2), int64(3), int64(4), - int64(5), int64(6), int64(7), int64(8), int64(9)}, - toml.Position{4, 1}}, - queryTestNode{"ok", toml.Position{6, 1}}, - }) -} - -func TestQueryAny(t *testing.T) { - assertQueryPositions(t, - "[foo.bar]\na=1\nb=2\n[foo.baz]\na=3\nb=4", - "$.foo.*", - []interface{}{ - queryTestNode{ - map[string]interface{}{ - "a": int64(1), - "b": int64(2), - }, toml.Position{1, 1}, - }, - queryTestNode{ - map[string]interface{}{ - "a": int64(3), - "b": int64(4), - }, toml.Position{4, 1}, - }, - }) -} -func TestQueryUnionSimple(t *testing.T) { - assertQueryPositions(t, - "[foo.bar]\na=1\nb=2\n[baz.foo]\na=3\nb=4\n[gorf.foo]\na=5\nb=6", - "$.*[bar,foo]", - []interface{}{ - queryTestNode{ - map[string]interface{}{ - "a": int64(1), - "b": int64(2), - }, toml.Position{1, 1}, - }, - queryTestNode{ - map[string]interface{}{ - "a": int64(3), - "b": int64(4), - }, toml.Position{4, 1}, - }, - queryTestNode{ - map[string]interface{}{ - "a": int64(5), - "b": int64(6), - }, toml.Position{7, 1}, - }, - }) -} - -func TestQueryRecursionAll(t *testing.T) { - assertQueryPositions(t, - "[foo.bar]\na=1\nb=2\n[baz.foo]\na=3\nb=4\n[gorf.foo]\na=5\nb=6", - "$..*", - []interface{}{ - queryTestNode{ - map[string]interface{}{ - "foo": map[string]interface{}{ - "bar": map[string]interface{}{ - "a": int64(1), - "b": int64(2), - }, - }, - "baz": map[string]interface{}{ - "foo": map[string]interface{}{ - "a": int64(3), - "b": int64(4), - }, - }, - "gorf": map[string]interface{}{ - "foo": map[string]interface{}{ - "a": int64(5), - "b": int64(6), - }, - }, - }, toml.Position{1, 1}, - }, - queryTestNode{ - map[string]interface{}{ - "bar": map[string]interface{}{ - "a": int64(1), - "b": int64(2), - }, - }, toml.Position{1, 1}, - }, - queryTestNode{ - map[string]interface{}{ - "a": int64(1), - "b": int64(2), - }, toml.Position{1, 1}, - }, - queryTestNode{int64(1), toml.Position{2, 1}}, - queryTestNode{int64(2), toml.Position{3, 1}}, - queryTestNode{ - map[string]interface{}{ - "foo": map[string]interface{}{ - "a": int64(3), - "b": int64(4), - }, - }, toml.Position{4, 1}, - }, - queryTestNode{ - map[string]interface{}{ - "a": int64(3), - "b": int64(4), - }, toml.Position{4, 1}, - }, - queryTestNode{int64(3), toml.Position{5, 1}}, - queryTestNode{int64(4), toml.Position{6, 1}}, - queryTestNode{ - map[string]interface{}{ - "foo": map[string]interface{}{ - "a": int64(5), - "b": int64(6), - }, - }, toml.Position{7, 1}, - }, - queryTestNode{ - map[string]interface{}{ - "a": int64(5), - "b": int64(6), - }, toml.Position{7, 1}, - }, - queryTestNode{int64(5), toml.Position{8, 1}}, - queryTestNode{int64(6), toml.Position{9, 1}}, - }) -} - -func TestQueryRecursionUnionSimple(t *testing.T) { - assertQueryPositions(t, - "[foo.bar]\na=1\nb=2\n[baz.foo]\na=3\nb=4\n[gorf.foo]\na=5\nb=6", - "$..['foo','bar']", - []interface{}{ - queryTestNode{ - map[string]interface{}{ - "bar": map[string]interface{}{ - "a": int64(1), - "b": int64(2), - }, - }, toml.Position{1, 1}, - }, - queryTestNode{ - map[string]interface{}{ - "a": int64(3), - "b": int64(4), - }, toml.Position{4, 1}, - }, - queryTestNode{ - map[string]interface{}{ - "a": int64(1), - "b": int64(2), - }, toml.Position{1, 1}, - }, - queryTestNode{ - map[string]interface{}{ - "a": int64(5), - "b": int64(6), - }, toml.Position{7, 1}, - }, - }) -} - -func TestQueryFilterFn(t *testing.T) { - buff, err := ioutil.ReadFile("../example.toml") - if err != nil { - t.Error(err) - return - } - - assertQueryPositions(t, string(buff), - "$..[?(int)]", - []interface{}{ - queryTestNode{int64(8001), toml.Position{13, 1}}, - queryTestNode{int64(8001), toml.Position{13, 1}}, - queryTestNode{int64(8002), toml.Position{13, 1}}, - queryTestNode{int64(5000), toml.Position{14, 1}}, - }) - - assertQueryPositions(t, string(buff), - "$..[?(string)]", - []interface{}{ - queryTestNode{"TOML Example", toml.Position{3, 1}}, - queryTestNode{"Tom Preston-Werner", toml.Position{6, 1}}, - queryTestNode{"GitHub", toml.Position{7, 1}}, - queryTestNode{"GitHub Cofounder & CEO\nLikes tater tots and beer.", toml.Position{8, 1}}, - queryTestNode{"192.168.1.1", toml.Position{12, 1}}, - queryTestNode{"10.0.0.1", toml.Position{21, 3}}, - queryTestNode{"eqdc10", toml.Position{22, 3}}, - queryTestNode{"10.0.0.2", toml.Position{25, 3}}, - queryTestNode{"eqdc10", toml.Position{26, 3}}, - }) - - assertQueryPositions(t, string(buff), - "$..[?(float)]", - []interface{}{ - queryTestNode{4e-08, toml.Position{30, 1}}, - }) - - tv, _ := time.Parse(time.RFC3339, "1979-05-27T07:32:00Z") - assertQueryPositions(t, string(buff), - "$..[?(tree)]", - []interface{}{ - queryTestNode{ - map[string]interface{}{ - "name": "Tom Preston-Werner", - "organization": "GitHub", - "bio": "GitHub Cofounder & CEO\nLikes tater tots and beer.", - "dob": tv, - }, toml.Position{5, 1}, - }, - queryTestNode{ - map[string]interface{}{ - "server": "192.168.1.1", - "ports": []interface{}{int64(8001), int64(8001), int64(8002)}, - "connection_max": int64(5000), - "enabled": true, - }, toml.Position{11, 1}, - }, - queryTestNode{ - map[string]interface{}{ - "alpha": map[string]interface{}{ - "ip": "10.0.0.1", - "dc": "eqdc10", - }, - "beta": map[string]interface{}{ - "ip": "10.0.0.2", - "dc": "eqdc10", - }, - }, toml.Position{17, 1}, - }, - queryTestNode{ - map[string]interface{}{ - "ip": "10.0.0.1", - "dc": "eqdc10", - }, toml.Position{20, 3}, - }, - queryTestNode{ - map[string]interface{}{ - "ip": "10.0.0.2", - "dc": "eqdc10", - }, toml.Position{24, 3}, - }, - queryTestNode{ - map[string]interface{}{ - "data": []interface{}{ - []interface{}{"gamma", "delta"}, - []interface{}{int64(1), int64(2)}, - }, - "score": 4e-08, - }, toml.Position{28, 1}, - }, - }) - - assertQueryPositions(t, string(buff), - "$..[?(time)]", - []interface{}{ - queryTestNode{tv, toml.Position{9, 1}}, - }) - - assertQueryPositions(t, string(buff), - "$..[?(bool)]", - []interface{}{ - queryTestNode{true, toml.Position{15, 1}}, - }) -} diff --git a/query/query.go b/query/query.go deleted file mode 100644 index 1c6cd801..00000000 --- a/query/query.go +++ /dev/null @@ -1,158 +0,0 @@ -package query - -import ( - "time" - - "github.com/pelletier/go-toml" -) - -// NodeFilterFn represents a user-defined filter function, for use with -// Query.SetFilter(). -// -// The return value of the function must indicate if 'node' is to be included -// at this stage of the TOML path. Returning true will include the node, and -// returning false will exclude it. -// -// NOTE: Care should be taken to write script callbacks such that they are safe -// to use from multiple goroutines. -type NodeFilterFn func(node interface{}) bool - -// Result is the result of Executing a Query. -type Result struct { - items []interface{} - positions []toml.Position -} - -// appends a value/position pair to the result set. -func (r *Result) appendResult(node interface{}, pos toml.Position) { - r.items = append(r.items, node) - r.positions = append(r.positions, pos) -} - -// Values is a set of values within a Result. The order of values is not -// guaranteed to be in document order, and may be different each time a query is -// executed. -func (r Result) Values() []interface{} { - return r.items -} - -// Positions is a set of positions for values within a Result. Each index -// in Positions() corresponds to the entry in Value() of the same index. -func (r Result) Positions() []toml.Position { - return r.positions -} - -// runtime context for executing query paths -type queryContext struct { - result *Result - filters *map[string]NodeFilterFn - lastPosition toml.Position -} - -// generic path functor interface -type pathFn interface { - setNext(next pathFn) - // it is the caller's responsibility to set the ctx.lastPosition before invoking call() - // node can be one of: *toml.Tree, []*toml.Tree, or a scalar - call(node interface{}, ctx *queryContext) -} - -// A Query is the representation of a compiled TOML path. A Query is safe -// for concurrent use by multiple goroutines. -type Query struct { - root pathFn - tail pathFn - filters *map[string]NodeFilterFn -} - -func newQuery() *Query { - return &Query{ - root: nil, - tail: nil, - filters: &defaultFilterFunctions, - } -} - -func (q *Query) appendPath(next pathFn) { - if q.root == nil { - q.root = next - } else { - q.tail.setNext(next) - } - q.tail = next - next.setNext(newTerminatingFn()) // init the next functor -} - -// Compile compiles a TOML path expression. The returned Query can be used -// to match elements within a Tree and its descendants. See Execute. -func Compile(path string) (*Query, error) { - return parseQuery(lexQuery(path)) -} - -// Execute executes a query against a Tree, and returns the result of the query. -func (q *Query) Execute(tree *toml.Tree) *Result { - result := &Result{ - items: []interface{}{}, - positions: []toml.Position{}, - } - if q.root == nil { - result.appendResult(tree, tree.GetPosition("")) - } else { - ctx := &queryContext{ - result: result, - filters: q.filters, - } - ctx.lastPosition = tree.Position() - q.root.call(tree, ctx) - } - return result -} - -// CompileAndExecute is a shorthand for Compile(path) followed by Execute(tree). -func CompileAndExecute(path string, tree *toml.Tree) (*Result, error) { - query, err := Compile(path) - if err != nil { - return nil, err - } - return query.Execute(tree), nil -} - -// SetFilter sets a user-defined filter function. These may be used inside -// "?(..)" query expressions to filter TOML document elements within a query. -func (q *Query) SetFilter(name string, fn NodeFilterFn) { - if q.filters == &defaultFilterFunctions { - // clone the static table - q.filters = &map[string]NodeFilterFn{} - for k, v := range defaultFilterFunctions { - (*q.filters)[k] = v - } - } - (*q.filters)[name] = fn -} - -var defaultFilterFunctions = map[string]NodeFilterFn{ - "tree": func(node interface{}) bool { - _, ok := node.(*toml.Tree) - return ok - }, - "int": func(node interface{}) bool { - _, ok := node.(int64) - return ok - }, - "float": func(node interface{}) bool { - _, ok := node.(float64) - return ok - }, - "string": func(node interface{}) bool { - _, ok := node.(string) - return ok - }, - "time": func(node interface{}) bool { - _, ok := node.(time.Time) - return ok - }, - "bool": func(node interface{}) bool { - _, ok := node.(bool) - return ok - }, -} diff --git a/query/query_test.go b/query/query_test.go deleted file mode 100644 index 87d13518..00000000 --- a/query/query_test.go +++ /dev/null @@ -1,151 +0,0 @@ -package query - -import ( - "fmt" - "testing" - - "github.com/pelletier/go-toml" -) - -func assertArrayContainsInOrder(t *testing.T, array []interface{}, objects ...interface{}) { - if len(array) != len(objects) { - t.Fatalf("array contains %d objects but %d are expected", len(array), len(objects)) - } - - for i := 0; i < len(array); i++ { - if array[i] != objects[i] { - t.Fatalf("wanted '%s', have '%s'", objects[i], array[i]) - } - } -} - -func checkQuery(t *testing.T, tree *toml.Tree, query string, objects ...interface{}) { - results, err := CompileAndExecute(query, tree) - if err != nil { - t.Fatal("unexpected error:", err) - } - assertArrayContainsInOrder(t, results.Values(), objects...) -} - -func TestQueryExample(t *testing.T) { - config, _ := toml.Load(` - [[book]] - title = "The Stand" - author = "Stephen King" - [[book]] - title = "For Whom the Bell Tolls" - author = "Ernest Hemmingway" - [[book]] - title = "Neuromancer" - author = "William Gibson" - `) - - checkQuery(t, config, "$.book.author", "Stephen King", "Ernest Hemmingway", "William Gibson") - - checkQuery(t, config, "$.book[0].author", "Stephen King") - checkQuery(t, config, "$.book[-1].author", "William Gibson") - checkQuery(t, config, "$.book[1:].author", "Ernest Hemmingway", "William Gibson") - checkQuery(t, config, "$.book[-1:].author", "William Gibson") - checkQuery(t, config, "$.book[::2].author", "Stephen King", "William Gibson") - checkQuery(t, config, "$.book[::-1].author", "William Gibson", "Ernest Hemmingway", "Stephen King") - checkQuery(t, config, "$.book[:].author", "Stephen King", "Ernest Hemmingway", "William Gibson") - checkQuery(t, config, "$.book[::].author", "Stephen King", "Ernest Hemmingway", "William Gibson") -} - -func TestQueryReadmeExample(t *testing.T) { - config, _ := toml.Load(` -[postgres] -user = "pelletier" -password = "mypassword" -`) - - checkQuery(t, config, "$..[user,password]", "pelletier", "mypassword") -} - -func TestQueryPathNotPresent(t *testing.T) { - config, _ := toml.Load(`a = "hello"`) - query, err := Compile("$.foo.bar") - if err != nil { - t.Fatal("unexpected error:", err) - } - results := query.Execute(config) - if err != nil { - t.Fatalf("err should be nil. got %s instead", err) - } - if len(results.items) != 0 { - t.Fatalf("no items should be matched. %d matched instead", len(results.items)) - } -} - -func ExampleNodeFilterFn_filterExample() { - tree, _ := toml.Load(` - [struct_one] - foo = "foo" - bar = "bar" - - [struct_two] - baz = "baz" - gorf = "gorf" - `) - - // create a query that references a user-defined-filter - query, _ := Compile("$[?(bazOnly)]") - - // define the filter, and assign it to the query - query.SetFilter("bazOnly", func(node interface{}) bool { - if tree, ok := node.(*toml.Tree); ok { - return tree.Has("baz") - } - return false // reject all other node types - }) - - // results contain only the 'struct_two' Tree - query.Execute(tree) -} - -func ExampleQuery_queryExample() { - config, _ := toml.Load(` - [[book]] - title = "The Stand" - author = "Stephen King" - [[book]] - title = "For Whom the Bell Tolls" - author = "Ernest Hemmingway" - [[book]] - title = "Neuromancer" - author = "William Gibson" - `) - - // find and print all the authors in the document - query, _ := Compile("$.book.author") - authors := query.Execute(config) - for _, name := range authors.Values() { - fmt.Println(name) - } -} - -func TestTomlQuery(t *testing.T) { - tree, err := toml.Load("[foo.bar]\na=1\nb=2\n[baz.foo]\na=3\nb=4\n[gorf.foo]\na=5\nb=6") - if err != nil { - t.Error(err) - return - } - query, err := Compile("$.foo.bar") - if err != nil { - t.Error(err) - return - } - result := query.Execute(tree) - values := result.Values() - if len(values) != 1 { - t.Errorf("Expected resultset of 1, got %d instead: %v", len(values), values) - } - - if tt, ok := values[0].(*toml.Tree); !ok { - t.Errorf("Expected type of Tree: %T", values[0]) - } else if tt.Get("a") != int64(1) { - t.Errorf("Expected 'a' with a value 1: %v", tt.Get("a")) - } else if tt.Get("b") != int64(2) { - t.Errorf("Expected 'b' with a value 2: %v", tt.Get("b")) - } -} diff --git a/query/tokens.go b/query/tokens.go deleted file mode 100644 index 098c856a..00000000 --- a/query/tokens.go +++ /dev/null @@ -1,106 +0,0 @@ -package query - -import ( - "fmt" - "strconv" - - "github.com/pelletier/go-toml" -) - -// Define tokens -type tokenType int - -const ( - eof = -(iota + 1) -) - -const ( - tokenError tokenType = iota - tokenEOF - tokenKey - tokenString - tokenInteger - tokenFloat - tokenLeftBracket - tokenRightBracket - tokenLeftParen - tokenRightParen - tokenComma - tokenColon - tokenDollar - tokenStar - tokenQuestion - tokenDot - tokenDotDot -) - -var tokenTypeNames = []string{ - "Error", - "EOF", - "Key", - "String", - "Integer", - "Float", - "[", - "]", - "(", - ")", - ",", - ":", - "$", - "*", - "?", - ".", - "..", -} - -type token struct { - toml.Position - typ tokenType - val string -} - -func (tt tokenType) String() string { - idx := int(tt) - if idx < len(tokenTypeNames) { - return tokenTypeNames[idx] - } - return "Unknown" -} - -func (t token) Int() int { - if result, err := strconv.Atoi(t.val); err != nil { - panic(err) - } else { - return result - } -} - -func (t token) String() string { - switch t.typ { - case tokenEOF: - return "EOF" - case tokenError: - return t.val - } - - return fmt.Sprintf("%q", t.val) -} - -func isSpace(r rune) bool { - return r == ' ' || r == '\t' -} - -func isAlphanumeric(r rune) bool { - return 'a' <= r && r <= 'z' || 'A' <= r && r <= 'Z' || r == '_' -} - -func isDigit(r rune) bool { - return '0' <= r && r <= '9' -} - -func isHexDigit(r rune) bool { - return isDigit(r) || - (r >= 'a' && r <= 'f') || - (r >= 'A' && r <= 'F') -} diff --git a/token.go b/token.go deleted file mode 100644 index b437fdd3..00000000 --- a/token.go +++ /dev/null @@ -1,136 +0,0 @@ -package toml - -import "fmt" - -// Define tokens -type tokenType int - -const ( - eof = -(iota + 1) -) - -const ( - tokenError tokenType = iota - tokenEOF - tokenComment - tokenKey - tokenString - tokenInteger - tokenTrue - tokenFalse - tokenFloat - tokenInf - tokenNan - tokenEqual - tokenLeftBracket - tokenRightBracket - tokenLeftCurlyBrace - tokenRightCurlyBrace - tokenLeftParen - tokenRightParen - tokenDoubleLeftBracket - tokenDoubleRightBracket - tokenLocalDate - tokenLocalTime - tokenTimeOffset - tokenKeyGroup - tokenKeyGroupArray - tokenComma - tokenColon - tokenDollar - tokenStar - tokenQuestion - tokenDot - tokenDotDot - tokenEOL -) - -var tokenTypeNames = []string{ - "Error", - "EOF", - "Comment", - "Key", - "String", - "Integer", - "True", - "False", - "Float", - "Inf", - "NaN", - "=", - "[", - "]", - "{", - "}", - "(", - ")", - "]]", - "[[", - "LocalDate", - "LocalTime", - "TimeOffset", - "KeyGroup", - "KeyGroupArray", - ",", - ":", - "$", - "*", - "?", - ".", - "..", - "EOL", -} - -type token struct { - Position - typ tokenType - val string -} - -func (tt tokenType) String() string { - idx := int(tt) - if idx < len(tokenTypeNames) { - return tokenTypeNames[idx] - } - return "Unknown" -} - -func (t token) String() string { - switch t.typ { - case tokenEOF: - return "EOF" - case tokenError: - return t.val - } - - return fmt.Sprintf("%q", t.val) -} - -func isSpace(r rune) bool { - return r == ' ' || r == '\t' -} - -func isAlphanumeric(r rune) bool { - return 'a' <= r && r <= 'z' || 'A' <= r && r <= 'Z' || r == '_' -} - -func isKeyChar(r rune) bool { - // Keys start with the first character that isn't whitespace or [ and end - // with the last non-whitespace character before the equals sign. Keys - // cannot contain a # character." - return !(r == '\r' || r == '\n' || r == eof || r == '=') -} - -func isKeyStartChar(r rune) bool { - return !(isSpace(r) || r == '\r' || r == '\n' || r == eof || r == '[') -} - -func isDigit(r rune) bool { - return '0' <= r && r <= '9' -} - -func isHexDigit(r rune) bool { - return isDigit(r) || - (r >= 'a' && r <= 'f') || - (r >= 'A' && r <= 'F') -} diff --git a/token_test.go b/token_test.go deleted file mode 100644 index 4508225a..00000000 --- a/token_test.go +++ /dev/null @@ -1,69 +0,0 @@ -package toml - -import "testing" - -func TestTokenStringer(t *testing.T) { - var tests = []struct { - tt tokenType - expect string - }{ - {tokenError, "Error"}, - {tokenEOF, "EOF"}, - {tokenComment, "Comment"}, - {tokenKey, "Key"}, - {tokenString, "String"}, - {tokenInteger, "Integer"}, - {tokenTrue, "True"}, - {tokenFalse, "False"}, - {tokenFloat, "Float"}, - {tokenEqual, "="}, - {tokenLeftBracket, "["}, - {tokenRightBracket, "]"}, - {tokenLeftCurlyBrace, "{"}, - {tokenRightCurlyBrace, "}"}, - {tokenLeftParen, "("}, - {tokenRightParen, ")"}, - {tokenDoubleLeftBracket, "]]"}, - {tokenDoubleRightBracket, "[["}, - {tokenLocalDate, "LocalDate"}, - {tokenLocalTime, "LocalTime"}, - {tokenTimeOffset, "TimeOffset"}, - {tokenKeyGroup, "KeyGroup"}, - {tokenKeyGroupArray, "KeyGroupArray"}, - {tokenComma, ","}, - {tokenColon, ":"}, - {tokenDollar, "$"}, - {tokenStar, "*"}, - {tokenQuestion, "?"}, - {tokenDot, "."}, - {tokenDotDot, ".."}, - {tokenEOL, "EOL"}, - {tokenEOL + 1, "Unknown"}, - } - - for i, test := range tests { - got := test.tt.String() - if got != test.expect { - t.Errorf("[%d] invalid string of token type; got %q, expected %q", i, got, test.expect) - } - } -} - -func TestTokenString(t *testing.T) { - var tests = []struct { - tok token - expect string - }{ - {token{Position{1, 1}, tokenEOF, ""}, "EOF"}, - {token{Position{1, 1}, tokenError, "Δt"}, "Δt"}, - {token{Position{1, 1}, tokenString, "bar"}, `"bar"`}, - {token{Position{1, 1}, tokenString, "123456789012345"}, `"123456789012345"`}, - } - - for i, test := range tests { - got := test.tok.String() - if got != test.expect { - t.Errorf("[%d] invalid of string token; got %q, expected %q", i, got, test.expect) - } - } -} diff --git a/toml.go b/toml.go deleted file mode 100644 index cbb89a9a..00000000 --- a/toml.go +++ /dev/null @@ -1,529 +0,0 @@ -package toml - -import ( - "errors" - "fmt" - "io" - "io/ioutil" - "os" - "runtime" - "strings" -) - -type tomlValue struct { - value interface{} // string, int64, uint64, float64, bool, time.Time, [] of any of this list - comment string - commented bool - multiline bool - position Position -} - -// Tree is the result of the parsing of a TOML file. -type Tree struct { - values map[string]interface{} // string -> *tomlValue, *Tree, []*Tree - comment string - commented bool - inline bool - position Position -} - -func newTree() *Tree { - return newTreeWithPosition(Position{}) -} - -func newTreeWithPosition(pos Position) *Tree { - return &Tree{ - values: make(map[string]interface{}), - position: pos, - } -} - -// TreeFromMap initializes a new Tree object using the given map. -func TreeFromMap(m map[string]interface{}) (*Tree, error) { - result, err := toTree(m) - if err != nil { - return nil, err - } - return result.(*Tree), nil -} - -// Position returns the position of the tree. -func (t *Tree) Position() Position { - return t.position -} - -// Has returns a boolean indicating if the given key exists. -func (t *Tree) Has(key string) bool { - if key == "" { - return false - } - return t.HasPath(strings.Split(key, ".")) -} - -// HasPath returns true if the given path of keys exists, false otherwise. -func (t *Tree) HasPath(keys []string) bool { - return t.GetPath(keys) != nil -} - -// Keys returns the keys of the toplevel tree (does not recurse). -func (t *Tree) Keys() []string { - keys := make([]string, len(t.values)) - i := 0 - for k := range t.values { - keys[i] = k - i++ - } - return keys -} - -// Get the value at key in the Tree. -// Key is a dot-separated path (e.g. a.b.c) without single/double quoted strings. -// If you need to retrieve non-bare keys, use GetPath. -// Returns nil if the path does not exist in the tree. -// If keys is of length zero, the current tree is returned. -func (t *Tree) Get(key string) interface{} { - if key == "" { - return t - } - return t.GetPath(strings.Split(key, ".")) -} - -// GetPath returns the element in the tree indicated by 'keys'. -// If keys is of length zero, the current tree is returned. -func (t *Tree) GetPath(keys []string) interface{} { - if len(keys) == 0 { - return t - } - subtree := t - for _, intermediateKey := range keys[:len(keys)-1] { - value, exists := subtree.values[intermediateKey] - if !exists { - return nil - } - switch node := value.(type) { - case *Tree: - subtree = node - case []*Tree: - // go to most recent element - if len(node) == 0 { - return nil - } - subtree = node[len(node)-1] - default: - return nil // cannot navigate through other node types - } - } - // branch based on final node type - switch node := subtree.values[keys[len(keys)-1]].(type) { - case *tomlValue: - return node.value - default: - return node - } -} - -// GetArray returns the value at key in the Tree. -// It returns []string, []int64, etc type if key has homogeneous lists -// Key is a dot-separated path (e.g. a.b.c) without single/double quoted strings. -// Returns nil if the path does not exist in the tree. -// If keys is of length zero, the current tree is returned. -func (t *Tree) GetArray(key string) interface{} { - if key == "" { - return t - } - return t.GetArrayPath(strings.Split(key, ".")) -} - -// GetArrayPath returns the element in the tree indicated by 'keys'. -// If keys is of length zero, the current tree is returned. -func (t *Tree) GetArrayPath(keys []string) interface{} { - if len(keys) == 0 { - return t - } - subtree := t - for _, intermediateKey := range keys[:len(keys)-1] { - value, exists := subtree.values[intermediateKey] - if !exists { - return nil - } - switch node := value.(type) { - case *Tree: - subtree = node - case []*Tree: - // go to most recent element - if len(node) == 0 { - return nil - } - subtree = node[len(node)-1] - default: - return nil // cannot navigate through other node types - } - } - // branch based on final node type - switch node := subtree.values[keys[len(keys)-1]].(type) { - case *tomlValue: - switch n := node.value.(type) { - case []interface{}: - return getArray(n) - default: - return node.value - } - default: - return node - } -} - -// if homogeneous array, then return slice type object over []interface{} -func getArray(n []interface{}) interface{} { - var s []string - var i64 []int64 - var f64 []float64 - var bl []bool - for _, value := range n { - switch v := value.(type) { - case string: - s = append(s, v) - case int64: - i64 = append(i64, v) - case float64: - f64 = append(f64, v) - case bool: - bl = append(bl, v) - default: - return n - } - } - if len(s) == len(n) { - return s - } else if len(i64) == len(n) { - return i64 - } else if len(f64) == len(n) { - return f64 - } else if len(bl) == len(n) { - return bl - } - return n -} - -// GetPosition returns the position of the given key. -func (t *Tree) GetPosition(key string) Position { - if key == "" { - return t.position - } - return t.GetPositionPath(strings.Split(key, ".")) -} - -// SetPositionPath sets the position of element in the tree indicated by 'keys'. -// If keys is of length zero, the current tree position is set. -func (t *Tree) SetPositionPath(keys []string, pos Position) { - if len(keys) == 0 { - t.position = pos - return - } - subtree := t - for _, intermediateKey := range keys[:len(keys)-1] { - value, exists := subtree.values[intermediateKey] - if !exists { - return - } - switch node := value.(type) { - case *Tree: - subtree = node - case []*Tree: - // go to most recent element - if len(node) == 0 { - return - } - subtree = node[len(node)-1] - default: - return - } - } - // branch based on final node type - switch node := subtree.values[keys[len(keys)-1]].(type) { - case *tomlValue: - node.position = pos - return - case *Tree: - node.position = pos - return - case []*Tree: - // go to most recent element - if len(node) == 0 { - return - } - node[len(node)-1].position = pos - return - } -} - -// GetPositionPath returns the element in the tree indicated by 'keys'. -// If keys is of length zero, the current tree is returned. -func (t *Tree) GetPositionPath(keys []string) Position { - if len(keys) == 0 { - return t.position - } - subtree := t - for _, intermediateKey := range keys[:len(keys)-1] { - value, exists := subtree.values[intermediateKey] - if !exists { - return Position{0, 0} - } - switch node := value.(type) { - case *Tree: - subtree = node - case []*Tree: - // go to most recent element - if len(node) == 0 { - return Position{0, 0} - } - subtree = node[len(node)-1] - default: - return Position{0, 0} - } - } - // branch based on final node type - switch node := subtree.values[keys[len(keys)-1]].(type) { - case *tomlValue: - return node.position - case *Tree: - return node.position - case []*Tree: - // go to most recent element - if len(node) == 0 { - return Position{0, 0} - } - return node[len(node)-1].position - default: - return Position{0, 0} - } -} - -// GetDefault works like Get but with a default value -func (t *Tree) GetDefault(key string, def interface{}) interface{} { - val := t.Get(key) - if val == nil { - return def - } - return val -} - -// SetOptions arguments are supplied to the SetWithOptions and SetPathWithOptions functions to modify marshalling behaviour. -// The default values within the struct are valid default options. -type SetOptions struct { - Comment string - Commented bool - Multiline bool -} - -// SetWithOptions is the same as Set, but allows you to provide formatting -// instructions to the key, that will be used by Marshal(). -func (t *Tree) SetWithOptions(key string, opts SetOptions, value interface{}) { - t.SetPathWithOptions(strings.Split(key, "."), opts, value) -} - -// SetPathWithOptions is the same as SetPath, but allows you to provide -// formatting instructions to the key, that will be reused by Marshal(). -func (t *Tree) SetPathWithOptions(keys []string, opts SetOptions, value interface{}) { - subtree := t - for i, intermediateKey := range keys[:len(keys)-1] { - nextTree, exists := subtree.values[intermediateKey] - if !exists { - nextTree = newTreeWithPosition(Position{Line: t.position.Line + i, Col: t.position.Col}) - subtree.values[intermediateKey] = nextTree // add new element here - } - switch node := nextTree.(type) { - case *Tree: - subtree = node - case []*Tree: - // go to most recent element - if len(node) == 0 { - // create element if it does not exist - node = append(node, newTreeWithPosition(Position{Line: t.position.Line + i, Col: t.position.Col})) - subtree.values[intermediateKey] = node - } - subtree = node[len(node)-1] - } - } - - var toInsert interface{} - - switch v := value.(type) { - case *Tree: - v.comment = opts.Comment - v.commented = opts.Commented - toInsert = value - case []*Tree: - for i := range v { - v[i].commented = opts.Commented - } - toInsert = value - case *tomlValue: - v.comment = opts.Comment - v.commented = opts.Commented - v.multiline = opts.Multiline - toInsert = v - default: - toInsert = &tomlValue{value: value, - comment: opts.Comment, - commented: opts.Commented, - multiline: opts.Multiline, - position: Position{Line: subtree.position.Line + len(subtree.values) + 1, Col: subtree.position.Col}} - } - - subtree.values[keys[len(keys)-1]] = toInsert -} - -// Set an element in the tree. -// Key is a dot-separated path (e.g. a.b.c). -// Creates all necessary intermediate trees, if needed. -func (t *Tree) Set(key string, value interface{}) { - t.SetWithComment(key, "", false, value) -} - -// SetWithComment is the same as Set, but allows you to provide comment -// information to the key, that will be reused by Marshal(). -func (t *Tree) SetWithComment(key string, comment string, commented bool, value interface{}) { - t.SetPathWithComment(strings.Split(key, "."), comment, commented, value) -} - -// SetPath sets an element in the tree. -// Keys is an array of path elements (e.g. {"a","b","c"}). -// Creates all necessary intermediate trees, if needed. -func (t *Tree) SetPath(keys []string, value interface{}) { - t.SetPathWithComment(keys, "", false, value) -} - -// SetPathWithComment is the same as SetPath, but allows you to provide comment -// information to the key, that will be reused by Marshal(). -func (t *Tree) SetPathWithComment(keys []string, comment string, commented bool, value interface{}) { - t.SetPathWithOptions(keys, SetOptions{Comment: comment, Commented: commented}, value) -} - -// Delete removes a key from the tree. -// Key is a dot-separated path (e.g. a.b.c). -func (t *Tree) Delete(key string) error { - keys, err := parseKey(key) - if err != nil { - return err - } - return t.DeletePath(keys) -} - -// DeletePath removes a key from the tree. -// Keys is an array of path elements (e.g. {"a","b","c"}). -func (t *Tree) DeletePath(keys []string) error { - keyLen := len(keys) - if keyLen == 1 { - delete(t.values, keys[0]) - return nil - } - tree := t.GetPath(keys[:keyLen-1]) - item := keys[keyLen-1] - switch node := tree.(type) { - case *Tree: - delete(node.values, item) - return nil - } - return errors.New("no such key to delete") -} - -// createSubTree takes a tree and a key and create the necessary intermediate -// subtrees to create a subtree at that point. In-place. -// -// e.g. passing a.b.c will create (assuming tree is empty) tree[a], tree[a][b] -// and tree[a][b][c] -// -// Returns nil on success, error object on failure -func (t *Tree) createSubTree(keys []string, pos Position) error { - subtree := t - for i, intermediateKey := range keys { - nextTree, exists := subtree.values[intermediateKey] - if !exists { - tree := newTreeWithPosition(Position{Line: t.position.Line + i, Col: t.position.Col}) - tree.position = pos - tree.inline = subtree.inline - subtree.values[intermediateKey] = tree - nextTree = tree - } - - switch node := nextTree.(type) { - case []*Tree: - subtree = node[len(node)-1] - case *Tree: - subtree = node - default: - return fmt.Errorf("unknown type for path %s (%s): %T (%#v)", - strings.Join(keys, "."), intermediateKey, nextTree, nextTree) - } - } - return nil -} - -// LoadBytes creates a Tree from a []byte. -func LoadBytes(b []byte) (tree *Tree, err error) { - defer func() { - if r := recover(); r != nil { - if _, ok := r.(runtime.Error); ok { - panic(r) - } - err = errors.New(r.(string)) - } - }() - - if len(b) >= 4 && (hasUTF32BigEndianBOM4(b) || hasUTF32LittleEndianBOM4(b)) { - b = b[4:] - } else if len(b) >= 3 && hasUTF8BOM3(b) { - b = b[3:] - } else if len(b) >= 2 && (hasUTF16BigEndianBOM2(b) || hasUTF16LittleEndianBOM2(b)) { - b = b[2:] - } - - tree = parseToml(lexToml(b)) - return -} - -func hasUTF16BigEndianBOM2(b []byte) bool { - return b[0] == 0xFE && b[1] == 0xFF -} - -func hasUTF16LittleEndianBOM2(b []byte) bool { - return b[0] == 0xFF && b[1] == 0xFE -} - -func hasUTF8BOM3(b []byte) bool { - return b[0] == 0xEF && b[1] == 0xBB && b[2] == 0xBF -} - -func hasUTF32BigEndianBOM4(b []byte) bool { - return b[0] == 0x00 && b[1] == 0x00 && b[2] == 0xFE && b[3] == 0xFF -} - -func hasUTF32LittleEndianBOM4(b []byte) bool { - return b[0] == 0xFF && b[1] == 0xFE && b[2] == 0x00 && b[3] == 0x00 -} - -// LoadReader creates a Tree from any io.Reader. -func LoadReader(reader io.Reader) (tree *Tree, err error) { - inputBytes, err := ioutil.ReadAll(reader) - if err != nil { - return - } - tree, err = LoadBytes(inputBytes) - return -} - -// Load creates a Tree from a string. -func Load(content string) (tree *Tree, err error) { - return LoadBytes([]byte(content)) -} - -// LoadFile creates a Tree from a file. -func LoadFile(path string) (tree *Tree, err error) { - file, err := os.Open(path) - if err != nil { - return nil, err - } - defer file.Close() - return LoadReader(file) -} diff --git a/toml_test.go b/toml_test.go deleted file mode 100644 index 0c7b6d34..00000000 --- a/toml_test.go +++ /dev/null @@ -1,261 +0,0 @@ -// Testing support for go-toml - -package toml - -import ( - "reflect" - "testing" -) - -func TestTomlHas(t *testing.T) { - tree, _ := Load(` - [test] - key = "value" - `) - - if !tree.Has("test.key") { - t.Errorf("Has - expected test.key to exists") - } - - if tree.Has("") { - t.Errorf("Should return false if the key is not provided") - } -} - -func TestTomlGet(t *testing.T) { - tree, _ := Load(` - [test] - key = "value" - `) - - if tree.Get("") != tree { - t.Errorf("Get should return the tree itself when given an empty path") - } - - if tree.Get("test.key") != "value" { - t.Errorf("Get should return the value") - } - if tree.Get(`\`) != nil { - t.Errorf("should return nil when the key is malformed") - } -} - -func TestTomlGetArray(t *testing.T) { - tree, _ := Load(` - [test] - key = ["one", "two"] - key2 = [true, false, false] - key3 = [1.5,2.5] - `) - - if tree.GetArray("") != tree { - t.Errorf("GetArray should return the tree itself when given an empty path") - } - - expect := []string{"one", "two"} - actual := tree.GetArray("test.key").([]string) - if !reflect.DeepEqual(actual, expect) { - t.Errorf("GetArray should return the []string value") - } - - expect2 := []bool{true, false, false} - actual2 := tree.GetArray("test.key2").([]bool) - if !reflect.DeepEqual(actual2, expect2) { - t.Errorf("GetArray should return the []bool value") - } - - expect3 := []float64{1.5, 2.5} - actual3 := tree.GetArray("test.key3").([]float64) - if !reflect.DeepEqual(actual3, expect3) { - t.Errorf("GetArray should return the []float64 value") - } - - if tree.GetArray(`\`) != nil { - t.Errorf("should return nil when the key is malformed") - } -} - -func TestTomlGetDefault(t *testing.T) { - tree, _ := Load(` - [test] - key = "value" - `) - - if tree.GetDefault("", "hello") != tree { - t.Error("GetDefault should return the tree itself when given an empty path") - } - - if tree.GetDefault("test.key", "hello") != "value" { - t.Error("Get should return the value") - } - - if tree.GetDefault("whatever", "hello") != "hello" { - t.Error("GetDefault should return the default value if the key does not exist") - } -} - -func TestTomlHasPath(t *testing.T) { - tree, _ := Load(` - [test] - key = "value" - `) - - if !tree.HasPath([]string{"test", "key"}) { - t.Errorf("HasPath - expected test.key to exists") - } -} - -func TestTomlDelete(t *testing.T) { - tree, _ := Load(` - key = "value" - `) - err := tree.Delete("key") - if err != nil { - t.Errorf("Delete - unexpected error while deleting key: %s", err.Error()) - } - - if tree.Get("key") != nil { - t.Errorf("Delete should have removed key but did not.") - } - -} - -func TestTomlDeleteUnparsableKey(t *testing.T) { - tree, _ := Load(` - key = "value" - `) - err := tree.Delete(".") - if err == nil { - t.Errorf("Delete should error") - } -} - -func TestTomlDeleteNestedKey(t *testing.T) { - tree, _ := Load(` - [foo] - [foo.bar] - key = "value" - `) - err := tree.Delete("foo.bar.key") - if err != nil { - t.Errorf("Error while deleting nested key: %s", err.Error()) - } - - if tree.Get("key") != nil { - t.Errorf("Delete should have removed nested key but did not.") - } - -} - -func TestTomlDeleteNonexistentNestedKey(t *testing.T) { - tree, _ := Load(` - [foo] - [foo.bar] - key = "value" - `) - err := tree.Delete("foo.not.there.key") - if err == nil { - t.Errorf("Delete should have thrown an error trying to delete key in nonexistent tree") - } -} - -func TestTomlGetPath(t *testing.T) { - node := newTree() - //TODO: set other node data - - for idx, item := range []struct { - Path []string - Expected *Tree - }{ - { // empty path test - []string{}, - node, - }, - } { - result := node.GetPath(item.Path) - if result != item.Expected { - t.Errorf("GetPath[%d] %v - expected %v, got %v instead.", idx, item.Path, item.Expected, result) - } - } - - tree, _ := Load("[foo.bar]\na=1\nb=2\n[baz.foo]\na=3\nb=4\n[gorf.foo]\na=5\nb=6") - if tree.GetPath([]string{"whatever"}) != nil { - t.Error("GetPath should return nil when the key does not exist") - } -} - -func TestTomlGetArrayPath(t *testing.T) { - for idx, item := range []struct { - Name string - Path []string - Make func() (tree *Tree, expected interface{}) - }{ - { - Name: "empty", - Path: []string{}, - Make: func() (tree *Tree, expected interface{}) { - tree = newTree() - expected = tree - return - }, - }, - { - Name: "int64", - Path: []string{"a"}, - Make: func() (tree *Tree, expected interface{}) { - var err error - tree, err = Load(`a = [1,2,3]`) - if err != nil { - panic(err) - } - expected = []int64{1, 2, 3} - return - }, - }, - } { - t.Run(item.Name, func(t *testing.T) { - tree, expected := item.Make() - result := tree.GetArrayPath(item.Path) - if !reflect.DeepEqual(result, expected) { - t.Errorf("GetArrayPath[%d] %v - expected %#v, got %#v instead.", idx, item.Path, expected, result) - } - }) - } - - tree, _ := Load("[foo.bar]\na=1\nb=2\n[baz.foo]\na=3\nb=4\n[gorf.foo]\na=5\nb=6") - if tree.GetArrayPath([]string{"whatever"}) != nil { - t.Error("GetArrayPath should return nil when the key does not exist") - } - -} - -func TestTomlFromMap(t *testing.T) { - simpleMap := map[string]interface{}{"hello": 42} - tree, err := TreeFromMap(simpleMap) - if err != nil { - t.Fatal("unexpected error:", err) - } - if tree.Get("hello") != int64(42) { - t.Fatal("hello should be 42, not", tree.Get("hello")) - } -} - -func TestLoadBytesBOM(t *testing.T) { - payloads := [][]byte{ - []byte("\xFE\xFFhello=1"), - []byte("\xFF\xFEhello=1"), - []byte("\xEF\xBB\xBFhello=1"), - []byte("\x00\x00\xFE\xFFhello=1"), - []byte("\xFF\xFE\x00\x00hello=1"), - } - for _, data := range payloads { - tree, err := LoadBytes(data) - if err != nil { - t.Fatal("unexpected error:", err, "for:", data) - } - v := tree.Get("hello") - if v != int64(1) { - t.Fatal("hello should be 1, not", v) - } - } -} diff --git a/toml_testgen_support_test.go b/toml_testgen_support_test.go deleted file mode 100644 index eef9b9fa..00000000 --- a/toml_testgen_support_test.go +++ /dev/null @@ -1,119 +0,0 @@ -// This is a support file for toml_testgen_test.go -package toml - -import ( - "bytes" - "encoding/json" - "fmt" - "reflect" - "testing" - "time" - - "github.com/davecgh/go-spew/spew" -) - -func testgenInvalid(t *testing.T, input string) { - t.Logf("Input TOML:\n%s", input) - tree, err := Load(input) - if err != nil { - return - } - - typedTree := testgenTranslate(*tree) - - buf := new(bytes.Buffer) - if err := json.NewEncoder(buf).Encode(typedTree); err != nil { - return - } - - t.Fatalf("test did not fail. resulting tree:\n%s", buf.String()) -} - -func testgenValid(t *testing.T, input string, jsonRef string) { - t.Logf("Input TOML:\n%s", input) - tree, err := Load(input) - if err != nil { - t.Fatalf("failed parsing toml: %s", err) - } - - typedTree := testgenTranslate(*tree) - - buf := new(bytes.Buffer) - if err := json.NewEncoder(buf).Encode(typedTree); err != nil { - t.Fatalf("failed translating to JSON: %s", err) - } - - var jsonTest interface{} - if err := json.NewDecoder(buf).Decode(&jsonTest); err != nil { - t.Logf("translated JSON:\n%s", buf.String()) - t.Fatalf("failed decoding translated JSON: %s", err) - } - - var jsonExpected interface{} - if err := json.NewDecoder(bytes.NewBufferString(jsonRef)).Decode(&jsonExpected); err != nil { - t.Logf("reference JSON:\n%s", jsonRef) - t.Fatalf("failed decoding reference JSON: %s", err) - } - - if !reflect.DeepEqual(jsonExpected, jsonTest) { - t.Logf("Diff:\n%s", spew.Sdump(jsonExpected, jsonTest)) - t.Fatal("parsed TOML tree is different than expected structure") - } -} - -func testgenTranslate(tomlData interface{}) interface{} { - switch orig := tomlData.(type) { - case map[string]interface{}: - typed := make(map[string]interface{}, len(orig)) - for k, v := range orig { - typed[k] = testgenTranslate(v) - } - return typed - case *Tree: - return testgenTranslate(*orig) - case Tree: - keys := orig.Keys() - typed := make(map[string]interface{}, len(keys)) - for _, k := range keys { - typed[k] = testgenTranslate(orig.GetPath([]string{k})) - } - return typed - case []*Tree: - typed := make([]map[string]interface{}, len(orig)) - for i, v := range orig { - typed[i] = testgenTranslate(v).(map[string]interface{}) - } - return typed - case []map[string]interface{}: - typed := make([]map[string]interface{}, len(orig)) - for i, v := range orig { - typed[i] = testgenTranslate(v).(map[string]interface{}) - } - return typed - case []interface{}: - typed := make([]interface{}, len(orig)) - for i, v := range orig { - typed[i] = testgenTranslate(v) - } - return testgenTag("array", typed) - case time.Time: - return testgenTag("datetime", orig.Format("2006-01-02T15:04:05Z")) - case bool: - return testgenTag("bool", fmt.Sprintf("%v", orig)) - case int64: - return testgenTag("integer", fmt.Sprintf("%d", orig)) - case float64: - return testgenTag("float", fmt.Sprintf("%v", orig)) - case string: - return testgenTag("string", orig) - } - - panic(fmt.Sprintf("Unknown type: %T", tomlData)) -} - -func testgenTag(typeName string, data interface{}) map[string]interface{} { - return map[string]interface{}{ - "type": typeName, - "value": data, - } -} diff --git a/toml_testgen_test.go b/toml_testgen_test.go deleted file mode 100644 index 2306926d..00000000 --- a/toml_testgen_test.go +++ /dev/null @@ -1,928 +0,0 @@ -// Generated by tomltestgen for toml-test ref 39e37e6 on 2019-03-19T23:58:45-07:00 -package toml - -import ( - "testing" -) - -func TestInvalidDatetimeMalformedNoLeads(t *testing.T) { - input := `no-leads = 1987-7-05T17:45:00Z` - testgenInvalid(t, input) -} - -func TestInvalidDatetimeMalformedNoSecs(t *testing.T) { - input := `no-secs = 1987-07-05T17:45Z` - testgenInvalid(t, input) -} - -func TestInvalidDatetimeMalformedNoT(t *testing.T) { - input := `no-t = 1987-07-0517:45:00Z` - testgenInvalid(t, input) -} - -func TestInvalidDatetimeMalformedWithMilli(t *testing.T) { - input := `with-milli = 1987-07-5T17:45:00.12Z` - testgenInvalid(t, input) -} - -func TestInvalidDuplicateKeyTable(t *testing.T) { - input := `[fruit] -type = "apple" - -[fruit.type] -apple = "yes"` - testgenInvalid(t, input) -} - -func TestInvalidDuplicateKeys(t *testing.T) { - input := `dupe = false -dupe = true` - testgenInvalid(t, input) -} - -func TestInvalidDuplicateTables(t *testing.T) { - input := `[a] -[a]` - testgenInvalid(t, input) -} - -func TestInvalidEmptyImplicitTable(t *testing.T) { - input := `[naughty..naughty]` - testgenInvalid(t, input) -} - -func TestInvalidEmptyTable(t *testing.T) { - input := `[]` - testgenInvalid(t, input) -} - -func TestInvalidFloatNoLeadingZero(t *testing.T) { - input := `answer = .12345 -neganswer = -.12345` - testgenInvalid(t, input) -} - -func TestInvalidFloatNoTrailingDigits(t *testing.T) { - input := `answer = 1. -neganswer = -1.` - testgenInvalid(t, input) -} - -func TestInvalidKeyEmpty(t *testing.T) { - input := ` = 1` - testgenInvalid(t, input) -} - -func TestInvalidKeyHash(t *testing.T) { - input := `a# = 1` - testgenInvalid(t, input) -} - -func TestInvalidKeyNewline(t *testing.T) { - input := `a -= 1` - testgenInvalid(t, input) -} - -func TestInvalidKeyOpenBracket(t *testing.T) { - input := `[abc = 1` - testgenInvalid(t, input) -} - -func TestInvalidKeySingleOpenBracket(t *testing.T) { - input := `[` - testgenInvalid(t, input) -} - -func TestInvalidKeySpace(t *testing.T) { - input := `a b = 1` - testgenInvalid(t, input) -} - -func TestInvalidKeyStartBracket(t *testing.T) { - input := `[a] -[xyz = 5 -[b]` - testgenInvalid(t, input) -} - -func TestInvalidKeyTwoEquals(t *testing.T) { - input := `key= = 1` - testgenInvalid(t, input) -} - -func TestInvalidStringBadByteEscape(t *testing.T) { - input := `naughty = "\xAg"` - testgenInvalid(t, input) -} - -func TestInvalidStringBadEscape(t *testing.T) { - input := `invalid-escape = "This string has a bad \a escape character."` - testgenInvalid(t, input) -} - -func TestInvalidStringByteEscapes(t *testing.T) { - input := `answer = "\x33"` - testgenInvalid(t, input) -} - -func TestInvalidStringNoClose(t *testing.T) { - input := `no-ending-quote = "One time, at band camp` - testgenInvalid(t, input) -} - -func TestInvalidTableArrayImplicit(t *testing.T) { - input := "# This test is a bit tricky. It should fail because the first use of\n" + - "# `[[albums.songs]]` without first declaring `albums` implies that `albums`\n" + - "# must be a table. The alternative would be quite weird. Namely, it wouldn't\n" + - "# comply with the TOML spec: \"Each double-bracketed sub-table will belong to \n" + - "# the most *recently* defined table element *above* it.\"\n" + - "#\n" + - "# This is in contrast to the *valid* test, table-array-implicit where\n" + - "# `[[albums.songs]]` works by itself, so long as `[[albums]]` isn't declared\n" + - "# later. (Although, `[albums]` could be.)\n" + - "[[albums.songs]]\n" + - "name = \"Glory Days\"\n" + - "\n" + - "[[albums]]\n" + - "name = \"Born in the USA\"\n" - testgenInvalid(t, input) -} - -func TestInvalidTableArrayMalformedBracket(t *testing.T) { - input := `[[albums] -name = "Born to Run"` - testgenInvalid(t, input) -} - -func TestInvalidTableArrayMalformedEmpty(t *testing.T) { - input := `[[]] -name = "Born to Run"` - testgenInvalid(t, input) -} - -func TestInvalidTableEmpty(t *testing.T) { - input := `[]` - testgenInvalid(t, input) -} - -func TestInvalidTableNestedBracketsClose(t *testing.T) { - input := `[a]b] -zyx = 42` - testgenInvalid(t, input) -} - -func TestInvalidTableNestedBracketsOpen(t *testing.T) { - input := `[a[b] -zyx = 42` - testgenInvalid(t, input) -} - -func TestInvalidTableWhitespace(t *testing.T) { - input := `[invalid key]` - testgenInvalid(t, input) -} - -func TestInvalidTableWithPound(t *testing.T) { - input := `[key#group] -answer = 42` - testgenInvalid(t, input) -} - -func TestInvalidTextAfterArrayEntries(t *testing.T) { - input := `array = [ - "Is there life after an array separator?", No - "Entry" -]` - testgenInvalid(t, input) -} - -func TestInvalidTextAfterInteger(t *testing.T) { - input := `answer = 42 the ultimate answer?` - testgenInvalid(t, input) -} - -func TestInvalidTextAfterString(t *testing.T) { - input := `string = "Is there life after strings?" No.` - testgenInvalid(t, input) -} - -func TestInvalidTextAfterTable(t *testing.T) { - input := `[error] this shouldn't be here` - testgenInvalid(t, input) -} - -func TestInvalidTextBeforeArraySeparator(t *testing.T) { - input := `array = [ - "Is there life before an array separator?" No, - "Entry" -]` - testgenInvalid(t, input) -} - -func TestInvalidTextInArray(t *testing.T) { - input := `array = [ - "Entry 1", - I don't belong, - "Entry 2", -]` - testgenInvalid(t, input) -} - -func TestValidArrayEmpty(t *testing.T) { - input := `thevoid = [[[[[]]]]]` - jsonRef := `{ - "thevoid": { "type": "array", "value": [ - {"type": "array", "value": [ - {"type": "array", "value": [ - {"type": "array", "value": [ - {"type": "array", "value": []} - ]} - ]} - ]} - ]} -}` - testgenValid(t, input, jsonRef) -} - -func TestValidArrayNospaces(t *testing.T) { - input := `ints = [1,2,3]` - jsonRef := `{ - "ints": { - "type": "array", - "value": [ - {"type": "integer", "value": "1"}, - {"type": "integer", "value": "2"}, - {"type": "integer", "value": "3"} - ] - } -}` - testgenValid(t, input, jsonRef) -} - -func TestValidArraysHetergeneous(t *testing.T) { - input := `mixed = [[1, 2], ["a", "b"], [1.1, 2.1]]` - jsonRef := `{ - "mixed": { - "type": "array", - "value": [ - {"type": "array", "value": [ - {"type": "integer", "value": "1"}, - {"type": "integer", "value": "2"} - ]}, - {"type": "array", "value": [ - {"type": "string", "value": "a"}, - {"type": "string", "value": "b"} - ]}, - {"type": "array", "value": [ - {"type": "float", "value": "1.1"}, - {"type": "float", "value": "2.1"} - ]} - ] - } -}` - testgenValid(t, input, jsonRef) -} - -func TestValidArraysNested(t *testing.T) { - input := `nest = [["a"], ["b"]]` - jsonRef := `{ - "nest": { - "type": "array", - "value": [ - {"type": "array", "value": [ - {"type": "string", "value": "a"} - ]}, - {"type": "array", "value": [ - {"type": "string", "value": "b"} - ]} - ] - } -}` - testgenValid(t, input, jsonRef) -} - -func TestValidArrays(t *testing.T) { - input := `ints = [1, 2, 3] -floats = [1.1, 2.1, 3.1] -strings = ["a", "b", "c"] -dates = [ - 1987-07-05T17:45:00Z, - 1979-05-27T07:32:00Z, - 2006-06-01T11:00:00Z, -]` - jsonRef := `{ - "ints": { - "type": "array", - "value": [ - {"type": "integer", "value": "1"}, - {"type": "integer", "value": "2"}, - {"type": "integer", "value": "3"} - ] - }, - "floats": { - "type": "array", - "value": [ - {"type": "float", "value": "1.1"}, - {"type": "float", "value": "2.1"}, - {"type": "float", "value": "3.1"} - ] - }, - "strings": { - "type": "array", - "value": [ - {"type": "string", "value": "a"}, - {"type": "string", "value": "b"}, - {"type": "string", "value": "c"} - ] - }, - "dates": { - "type": "array", - "value": [ - {"type": "datetime", "value": "1987-07-05T17:45:00Z"}, - {"type": "datetime", "value": "1979-05-27T07:32:00Z"}, - {"type": "datetime", "value": "2006-06-01T11:00:00Z"} - ] - } -}` - testgenValid(t, input, jsonRef) -} - -func TestValidBool(t *testing.T) { - input := `t = true -f = false` - jsonRef := `{ - "f": {"type": "bool", "value": "false"}, - "t": {"type": "bool", "value": "true"} -}` - testgenValid(t, input, jsonRef) -} - -func TestValidCommentsEverywhere(t *testing.T) { - input := `# Top comment. - # Top comment. -# Top comment. - -# [no-extraneous-groups-please] - -[group] # Comment -answer = 42 # Comment -# no-extraneous-keys-please = 999 -# Inbetween comment. -more = [ # Comment - # What about multiple # comments? - # Can you handle it? - # - # Evil. -# Evil. - 42, 42, # Comments within arrays are fun. - # What about multiple # comments? - # Can you handle it? - # - # Evil. -# Evil. -# ] Did I fool you? -] # Hopefully not.` - jsonRef := `{ - "group": { - "answer": {"type": "integer", "value": "42"}, - "more": { - "type": "array", - "value": [ - {"type": "integer", "value": "42"}, - {"type": "integer", "value": "42"} - ] - } - } -}` - testgenValid(t, input, jsonRef) -} - -func TestValidDatetime(t *testing.T) { - input := `bestdayever = 1987-07-05T17:45:00Z` - jsonRef := `{ - "bestdayever": {"type": "datetime", "value": "1987-07-05T17:45:00Z"} -}` - testgenValid(t, input, jsonRef) -} - -func TestValidEmpty(t *testing.T) { - input := `` - jsonRef := `{}` - testgenValid(t, input, jsonRef) -} - -func TestValidExample(t *testing.T) { - input := `best-day-ever = 1987-07-05T17:45:00Z - -[numtheory] -boring = false -perfection = [6, 28, 496]` - jsonRef := `{ - "best-day-ever": {"type": "datetime", "value": "1987-07-05T17:45:00Z"}, - "numtheory": { - "boring": {"type": "bool", "value": "false"}, - "perfection": { - "type": "array", - "value": [ - {"type": "integer", "value": "6"}, - {"type": "integer", "value": "28"}, - {"type": "integer", "value": "496"} - ] - } - } -}` - testgenValid(t, input, jsonRef) -} - -func TestValidFloat(t *testing.T) { - input := `pi = 3.14 -negpi = -3.14` - jsonRef := `{ - "pi": {"type": "float", "value": "3.14"}, - "negpi": {"type": "float", "value": "-3.14"} -}` - testgenValid(t, input, jsonRef) -} - -func TestValidImplicitAndExplicitAfter(t *testing.T) { - input := `[a.b.c] -answer = 42 - -[a] -better = 43` - jsonRef := `{ - "a": { - "better": {"type": "integer", "value": "43"}, - "b": { - "c": { - "answer": {"type": "integer", "value": "42"} - } - } - } -}` - testgenValid(t, input, jsonRef) -} - -func TestValidImplicitAndExplicitBefore(t *testing.T) { - input := `[a] -better = 43 - -[a.b.c] -answer = 42` - jsonRef := `{ - "a": { - "better": {"type": "integer", "value": "43"}, - "b": { - "c": { - "answer": {"type": "integer", "value": "42"} - } - } - } -}` - testgenValid(t, input, jsonRef) -} - -func TestValidImplicitGroups(t *testing.T) { - input := `[a.b.c] -answer = 42` - jsonRef := `{ - "a": { - "b": { - "c": { - "answer": {"type": "integer", "value": "42"} - } - } - } -}` - testgenValid(t, input, jsonRef) -} - -func TestValidInteger(t *testing.T) { - input := `answer = 42 -neganswer = -42` - jsonRef := `{ - "answer": {"type": "integer", "value": "42"}, - "neganswer": {"type": "integer", "value": "-42"} -}` - testgenValid(t, input, jsonRef) -} - -func TestValidKeyEqualsNospace(t *testing.T) { - input := `answer=42` - jsonRef := `{ - "answer": {"type": "integer", "value": "42"} -}` - testgenValid(t, input, jsonRef) -} - -func TestValidKeySpace(t *testing.T) { - input := `"a b" = 1` - jsonRef := `{ - "a b": {"type": "integer", "value": "1"} -}` - testgenValid(t, input, jsonRef) -} - -func TestValidKeySpecialChars(t *testing.T) { - input := "\"~!@$^&*()_+-`1234567890[]|/?><.,;:'\" = 1\n" - jsonRef := "{\n" + - " \"~!@$^&*()_+-`1234567890[]|/?><.,;:'\": {\n" + - " \"type\": \"integer\", \"value\": \"1\"\n" + - " }\n" + - "}\n" - testgenValid(t, input, jsonRef) -} - -func TestValidLongFloat(t *testing.T) { - input := `longpi = 3.141592653589793 -neglongpi = -3.141592653589793` - jsonRef := `{ - "longpi": {"type": "float", "value": "3.141592653589793"}, - "neglongpi": {"type": "float", "value": "-3.141592653589793"} -}` - testgenValid(t, input, jsonRef) -} - -func TestValidLongInteger(t *testing.T) { - input := `answer = 9223372036854775807 -neganswer = -9223372036854775808` - jsonRef := `{ - "answer": {"type": "integer", "value": "9223372036854775807"}, - "neganswer": {"type": "integer", "value": "-9223372036854775808"} -}` - testgenValid(t, input, jsonRef) -} - -func TestValidMultilineString(t *testing.T) { - input := `multiline_empty_one = """""" -multiline_empty_two = """ -""" -multiline_empty_three = """\ - """ -multiline_empty_four = """\ - \ - \ - """ - -equivalent_one = "The quick brown fox jumps over the lazy dog." -equivalent_two = """ -The quick brown \ - - - fox jumps over \ - the lazy dog.""" - -equivalent_three = """\ - The quick brown \ - fox jumps over \ - the lazy dog.\ - """` - jsonRef := `{ - "multiline_empty_one": { - "type": "string", - "value": "" - }, - "multiline_empty_two": { - "type": "string", - "value": "" - }, - "multiline_empty_three": { - "type": "string", - "value": "" - }, - "multiline_empty_four": { - "type": "string", - "value": "" - }, - "equivalent_one": { - "type": "string", - "value": "The quick brown fox jumps over the lazy dog." - }, - "equivalent_two": { - "type": "string", - "value": "The quick brown fox jumps over the lazy dog." - }, - "equivalent_three": { - "type": "string", - "value": "The quick brown fox jumps over the lazy dog." - } -}` - testgenValid(t, input, jsonRef) -} - -func TestValidRawMultilineString(t *testing.T) { - input := `oneline = '''This string has a ' quote character.''' -firstnl = ''' -This string has a ' quote character.''' -multiline = ''' -This string -has ' a quote character -and more than -one newline -in it.'''` - jsonRef := `{ - "oneline": { - "type": "string", - "value": "This string has a ' quote character." - }, - "firstnl": { - "type": "string", - "value": "This string has a ' quote character." - }, - "multiline": { - "type": "string", - "value": "This string\nhas ' a quote character\nand more than\none newline\nin it." - } -}` - testgenValid(t, input, jsonRef) -} - -func TestValidRawString(t *testing.T) { - input := `backspace = 'This string has a \b backspace character.' -tab = 'This string has a \t tab character.' -newline = 'This string has a \n new line character.' -formfeed = 'This string has a \f form feed character.' -carriage = 'This string has a \r carriage return character.' -slash = 'This string has a \/ slash character.' -backslash = 'This string has a \\ backslash character.'` - jsonRef := `{ - "backspace": { - "type": "string", - "value": "This string has a \\b backspace character." - }, - "tab": { - "type": "string", - "value": "This string has a \\t tab character." - }, - "newline": { - "type": "string", - "value": "This string has a \\n new line character." - }, - "formfeed": { - "type": "string", - "value": "This string has a \\f form feed character." - }, - "carriage": { - "type": "string", - "value": "This string has a \\r carriage return character." - }, - "slash": { - "type": "string", - "value": "This string has a \\/ slash character." - }, - "backslash": { - "type": "string", - "value": "This string has a \\\\ backslash character." - } -}` - testgenValid(t, input, jsonRef) -} - -func TestValidStringEmpty(t *testing.T) { - input := `answer = ""` - jsonRef := `{ - "answer": { - "type": "string", - "value": "" - } -}` - testgenValid(t, input, jsonRef) -} - -func TestValidStringEscapes(t *testing.T) { - input := `backspace = "This string has a \b backspace character." -tab = "This string has a \t tab character." -newline = "This string has a \n new line character." -formfeed = "This string has a \f form feed character." -carriage = "This string has a \r carriage return character." -quote = "This string has a \" quote character." -backslash = "This string has a \\ backslash character." -notunicode1 = "This string does not have a unicode \\u escape." -notunicode2 = "This string does not have a unicode \u005Cu escape." -notunicode3 = "This string does not have a unicode \\u0075 escape." -notunicode4 = "This string does not have a unicode \\\u0075 escape."` - jsonRef := `{ - "backspace": { - "type": "string", - "value": "This string has a \u0008 backspace character." - }, - "tab": { - "type": "string", - "value": "This string has a \u0009 tab character." - }, - "newline": { - "type": "string", - "value": "This string has a \u000A new line character." - }, - "formfeed": { - "type": "string", - "value": "This string has a \u000C form feed character." - }, - "carriage": { - "type": "string", - "value": "This string has a \u000D carriage return character." - }, - "quote": { - "type": "string", - "value": "This string has a \u0022 quote character." - }, - "backslash": { - "type": "string", - "value": "This string has a \u005C backslash character." - }, - "notunicode1": { - "type": "string", - "value": "This string does not have a unicode \\u escape." - }, - "notunicode2": { - "type": "string", - "value": "This string does not have a unicode \u005Cu escape." - }, - "notunicode3": { - "type": "string", - "value": "This string does not have a unicode \\u0075 escape." - }, - "notunicode4": { - "type": "string", - "value": "This string does not have a unicode \\\u0075 escape." - } -}` - testgenValid(t, input, jsonRef) -} - -func TestValidStringSimple(t *testing.T) { - input := `answer = "You are not drinking enough whisky."` - jsonRef := `{ - "answer": { - "type": "string", - "value": "You are not drinking enough whisky." - } -}` - testgenValid(t, input, jsonRef) -} - -func TestValidStringWithPound(t *testing.T) { - input := `pound = "We see no # comments here." -poundcomment = "But there are # some comments here." # Did I # mess you up?` - jsonRef := `{ - "pound": {"type": "string", "value": "We see no # comments here."}, - "poundcomment": { - "type": "string", - "value": "But there are # some comments here." - } -}` - testgenValid(t, input, jsonRef) -} - -func TestValidTableArrayImplicit(t *testing.T) { - input := `[[albums.songs]] -name = "Glory Days"` - jsonRef := `{ - "albums": { - "songs": [ - {"name": {"type": "string", "value": "Glory Days"}} - ] - } -}` - testgenValid(t, input, jsonRef) -} - -func TestValidTableArrayMany(t *testing.T) { - input := `[[people]] -first_name = "Bruce" -last_name = "Springsteen" - -[[people]] -first_name = "Eric" -last_name = "Clapton" - -[[people]] -first_name = "Bob" -last_name = "Seger"` - jsonRef := `{ - "people": [ - { - "first_name": {"type": "string", "value": "Bruce"}, - "last_name": {"type": "string", "value": "Springsteen"} - }, - { - "first_name": {"type": "string", "value": "Eric"}, - "last_name": {"type": "string", "value": "Clapton"} - }, - { - "first_name": {"type": "string", "value": "Bob"}, - "last_name": {"type": "string", "value": "Seger"} - } - ] -}` - testgenValid(t, input, jsonRef) -} - -func TestValidTableArrayNest(t *testing.T) { - input := `[[albums]] -name = "Born to Run" - - [[albums.songs]] - name = "Jungleland" - - [[albums.songs]] - name = "Meeting Across the River" - -[[albums]] -name = "Born in the USA" - - [[albums.songs]] - name = "Glory Days" - - [[albums.songs]] - name = "Dancing in the Dark"` - jsonRef := `{ - "albums": [ - { - "name": {"type": "string", "value": "Born to Run"}, - "songs": [ - {"name": {"type": "string", "value": "Jungleland"}}, - {"name": {"type": "string", "value": "Meeting Across the River"}} - ] - }, - { - "name": {"type": "string", "value": "Born in the USA"}, - "songs": [ - {"name": {"type": "string", "value": "Glory Days"}}, - {"name": {"type": "string", "value": "Dancing in the Dark"}} - ] - } - ] -}` - testgenValid(t, input, jsonRef) -} - -func TestValidTableArrayOne(t *testing.T) { - input := `[[people]] -first_name = "Bruce" -last_name = "Springsteen"` - jsonRef := `{ - "people": [ - { - "first_name": {"type": "string", "value": "Bruce"}, - "last_name": {"type": "string", "value": "Springsteen"} - } - ] -}` - testgenValid(t, input, jsonRef) -} - -func TestValidTableEmpty(t *testing.T) { - input := `[a]` - jsonRef := `{ - "a": {} -}` - testgenValid(t, input, jsonRef) -} - -func TestValidTableSubEmpty(t *testing.T) { - input := `[a] -[a.b]` - jsonRef := `{ - "a": { "b": {} } -}` - testgenValid(t, input, jsonRef) -} - -func TestValidTableWhitespace(t *testing.T) { - input := `["valid key"]` - jsonRef := `{ - "valid key": {} -}` - testgenValid(t, input, jsonRef) -} - -func TestValidTableWithPound(t *testing.T) { - input := `["key#group"] -answer = 42` - jsonRef := `{ - "key#group": { - "answer": {"type": "integer", "value": "42"} - } -}` - testgenValid(t, input, jsonRef) -} - -func TestValidUnicodeEscape(t *testing.T) { - input := `answer4 = "\u03B4" -answer8 = "\U000003B4"` - jsonRef := `{ - "answer4": {"type": "string", "value": "\u03B4"}, - "answer8": {"type": "string", "value": "\u03B4"} -}` - testgenValid(t, input, jsonRef) -} - -func TestValidUnicodeLiteral(t *testing.T) { - input := `answer = "δ"` - jsonRef := `{ - "answer": {"type": "string", "value": "δ"} -}` - testgenValid(t, input, jsonRef) -} diff --git a/tomlpub.go b/tomlpub.go deleted file mode 100644 index 4136b462..00000000 --- a/tomlpub.go +++ /dev/null @@ -1,71 +0,0 @@ -package toml - -// PubTOMLValue wrapping tomlValue in order to access all properties from outside. -type PubTOMLValue = tomlValue - -func (ptv *PubTOMLValue) Value() interface{} { - return ptv.value -} -func (ptv *PubTOMLValue) Comment() string { - return ptv.comment -} -func (ptv *PubTOMLValue) Commented() bool { - return ptv.commented -} -func (ptv *PubTOMLValue) Multiline() bool { - return ptv.multiline -} -func (ptv *PubTOMLValue) Position() Position { - return ptv.position -} - -func (ptv *PubTOMLValue) SetValue(v interface{}) { - ptv.value = v -} -func (ptv *PubTOMLValue) SetComment(s string) { - ptv.comment = s -} -func (ptv *PubTOMLValue) SetCommented(c bool) { - ptv.commented = c -} -func (ptv *PubTOMLValue) SetMultiline(m bool) { - ptv.multiline = m -} -func (ptv *PubTOMLValue) SetPosition(p Position) { - ptv.position = p -} - -// PubTree wrapping Tree in order to access all properties from outside. -type PubTree = Tree - -func (pt *PubTree) Values() map[string]interface{} { - return pt.values -} - -func (pt *PubTree) Comment() string { - return pt.comment -} - -func (pt *PubTree) Commented() bool { - return pt.commented -} - -func (pt *PubTree) Inline() bool { - return pt.inline -} - -func (pt *PubTree) SetValues(v map[string]interface{}) { - pt.values = v -} - -func (pt *PubTree) SetComment(c string) { - pt.comment = c -} - -func (pt *PubTree) SetCommented(c bool) { - pt.commented = c -} - -func (pt *PubTree) SetInline(i bool) { - pt.inline = i -} diff --git a/tomltree_create.go b/tomltree_create.go deleted file mode 100644 index 80353500..00000000 --- a/tomltree_create.go +++ /dev/null @@ -1,155 +0,0 @@ -package toml - -import ( - "fmt" - "reflect" - "time" -) - -var kindToType = [reflect.String + 1]reflect.Type{ - reflect.Bool: reflect.TypeOf(true), - reflect.String: reflect.TypeOf(""), - reflect.Float32: reflect.TypeOf(float64(1)), - reflect.Float64: reflect.TypeOf(float64(1)), - reflect.Int: reflect.TypeOf(int64(1)), - reflect.Int8: reflect.TypeOf(int64(1)), - reflect.Int16: reflect.TypeOf(int64(1)), - reflect.Int32: reflect.TypeOf(int64(1)), - reflect.Int64: reflect.TypeOf(int64(1)), - reflect.Uint: reflect.TypeOf(uint64(1)), - reflect.Uint8: reflect.TypeOf(uint64(1)), - reflect.Uint16: reflect.TypeOf(uint64(1)), - reflect.Uint32: reflect.TypeOf(uint64(1)), - reflect.Uint64: reflect.TypeOf(uint64(1)), -} - -// typeFor returns a reflect.Type for a reflect.Kind, or nil if none is found. -// supported values: -// string, bool, int64, uint64, float64, time.Time, int, int8, int16, int32, uint, uint8, uint16, uint32, float32 -func typeFor(k reflect.Kind) reflect.Type { - if k > 0 && int(k) < len(kindToType) { - return kindToType[k] - } - return nil -} - -func simpleValueCoercion(object interface{}) (interface{}, error) { - switch original := object.(type) { - case string, bool, int64, uint64, float64, time.Time: - return original, nil - case int: - return int64(original), nil - case int8: - return int64(original), nil - case int16: - return int64(original), nil - case int32: - return int64(original), nil - case uint: - return uint64(original), nil - case uint8: - return uint64(original), nil - case uint16: - return uint64(original), nil - case uint32: - return uint64(original), nil - case float32: - return float64(original), nil - case fmt.Stringer: - return original.String(), nil - case []interface{}: - value := reflect.ValueOf(original) - length := value.Len() - arrayValue := reflect.MakeSlice(value.Type(), 0, length) - for i := 0; i < length; i++ { - val := value.Index(i).Interface() - simpleValue, err := simpleValueCoercion(val) - if err != nil { - return nil, err - } - arrayValue = reflect.Append(arrayValue, reflect.ValueOf(simpleValue)) - } - return arrayValue.Interface(), nil - default: - return nil, fmt.Errorf("cannot convert type %T to Tree", object) - } -} - -func sliceToTree(object interface{}) (interface{}, error) { - // arrays are a bit tricky, since they can represent either a - // collection of simple values, which is represented by one - // *tomlValue, or an array of tables, which is represented by an - // array of *Tree. - - // holding the assumption that this function is called from toTree only when value.Kind() is Array or Slice - value := reflect.ValueOf(object) - insideType := value.Type().Elem() - length := value.Len() - if length > 0 { - insideType = reflect.ValueOf(value.Index(0).Interface()).Type() - } - if insideType.Kind() == reflect.Map { - // this is considered as an array of tables - tablesArray := make([]*Tree, 0, length) - for i := 0; i < length; i++ { - table := value.Index(i) - tree, err := toTree(table.Interface()) - if err != nil { - return nil, err - } - tablesArray = append(tablesArray, tree.(*Tree)) - } - return tablesArray, nil - } - - sliceType := typeFor(insideType.Kind()) - if sliceType == nil { - sliceType = insideType - } - - arrayValue := reflect.MakeSlice(reflect.SliceOf(sliceType), 0, length) - - for i := 0; i < length; i++ { - val := value.Index(i).Interface() - simpleValue, err := simpleValueCoercion(val) - if err != nil { - return nil, err - } - arrayValue = reflect.Append(arrayValue, reflect.ValueOf(simpleValue)) - } - return &tomlValue{value: arrayValue.Interface(), position: Position{}}, nil -} - -func toTree(object interface{}) (interface{}, error) { - value := reflect.ValueOf(object) - - if value.Kind() == reflect.Map { - values := map[string]interface{}{} - keys := value.MapKeys() - for _, key := range keys { - if key.Kind() != reflect.String { - if _, ok := key.Interface().(string); !ok { - return nil, fmt.Errorf("map key needs to be a string, not %T (%v)", key.Interface(), key.Kind()) - } - } - - v := value.MapIndex(key) - newValue, err := toTree(v.Interface()) - if err != nil { - return nil, err - } - values[key.String()] = newValue - } - return &Tree{values: values, position: Position{}}, nil - } - - if value.Kind() == reflect.Array || value.Kind() == reflect.Slice { - return sliceToTree(object) - } - - simpleValue, err := simpleValueCoercion(object) - if err != nil { - return nil, err - } - return &tomlValue{value: simpleValue, position: Position{}}, nil -} diff --git a/tomltree_create_test.go b/tomltree_create_test.go deleted file mode 100644 index 9ea129f5..00000000 --- a/tomltree_create_test.go +++ /dev/null @@ -1,243 +0,0 @@ -package toml - -import ( - "reflect" - "strconv" - "testing" - "time" -) - -type customString string - -type stringer struct{} - -func (s stringer) String() string { - return "stringer" -} - -func validate(t *testing.T, path string, object interface{}) { - switch o := object.(type) { - case *Tree: - for key, tree := range o.values { - validate(t, path+"."+key, tree) - } - case []*Tree: - for index, tree := range o { - validate(t, path+"."+strconv.Itoa(index), tree) - } - case *tomlValue: - switch o.value.(type) { - case int64, uint64, bool, string, float64, time.Time, - []int64, []uint64, []bool, []string, []float64, []time.Time: - default: - t.Fatalf("tomlValue at key %s containing incorrect type %T", path, o.value) - } - default: - t.Fatalf("value at key %s is of incorrect type %T", path, object) - } - t.Logf("validation ok %s as %T", path, object) -} - -func validateTree(t *testing.T, tree *Tree) { - validate(t, "", tree) -} - -func TestTreeCreateToTree(t *testing.T) { - data := map[string]interface{}{ - "a_string": "bar", - "an_int": 42, - "time": time.Now(), - "int8": int8(2), - "int16": int16(2), - "int32": int32(2), - "uint8": uint8(2), - "uint16": uint16(2), - "uint32": uint32(2), - "float32": float32(2), - "a_bool": false, - "stringer": stringer{}, - "nested": map[string]interface{}{ - "foo": "bar", - }, - "array": []string{"a", "b", "c"}, - "array_uint": []uint{uint(1), uint(2)}, - "array_table": []map[string]interface{}{{"sub_map": 52}}, - "array_times": []time.Time{time.Now(), time.Now()}, - "map_times": map[string]time.Time{"now": time.Now()}, - "custom_string_map_key": map[customString]interface{}{customString("custom"): "custom"}, - } - tree, err := TreeFromMap(data) - if err != nil { - t.Fatal("unexpected error:", err) - } - validateTree(t, tree) -} - -func TestTreeCreateToTreeInvalidLeafType(t *testing.T) { - _, err := TreeFromMap(map[string]interface{}{"foo": t}) - expected := "cannot convert type *testing.T to Tree" - if err.Error() != expected { - t.Fatalf("expected error %s, got %s", expected, err.Error()) - } -} - -func TestTreeCreateToTreeInvalidMapKeyType(t *testing.T) { - _, err := TreeFromMap(map[string]interface{}{"foo": map[int]interface{}{2: 1}}) - expected := "map key needs to be a string, not int (int)" - if err.Error() != expected { - t.Fatalf("expected error %s, got %s", expected, err.Error()) - } -} - -func TestTreeCreateToTreeInvalidArrayMemberType(t *testing.T) { - _, err := TreeFromMap(map[string]interface{}{"foo": []*testing.T{t}}) - expected := "cannot convert type *testing.T to Tree" - if err.Error() != expected { - t.Fatalf("expected error %s, got %s", expected, err.Error()) - } -} - -func TestTreeCreateToTreeInvalidTableGroupType(t *testing.T) { - _, err := TreeFromMap(map[string]interface{}{"foo": []map[string]interface{}{{"hello": t}}}) - expected := "cannot convert type *testing.T to Tree" - if err.Error() != expected { - t.Fatalf("expected error %s, got %s", expected, err.Error()) - } -} - -func TestRoundTripArrayOfTables(t *testing.T) { - orig := "\n[[stuff]]\n name = \"foo\"\n things = [\"a\", \"b\"]\n" - tree, err := Load(orig) - if err != nil { - t.Fatalf("unexpected error: %s", err) - } - - m := tree.ToMap() - - tree, err = TreeFromMap(m) - if err != nil { - t.Fatalf("unexpected error: %s", err) - } - want := orig - got := tree.String() - - if got != want { - t.Errorf("want:\n%s\ngot:\n%s", want, got) - } -} - -func TestTomlSliceOfSlice(t *testing.T) { - tree, err := Load(` hosts=[["10.1.0.107:9092","10.1.0.107:9093", "192.168.0.40:9094"] ] `) - m := tree.ToMap() - tree, err = TreeFromMap(m) - if err != nil { - t.Error("should not error", err) - } - type Struct struct { - Hosts [][]string - } - var actual Struct - tree.Unmarshal(&actual) - - expected := Struct{Hosts: [][]string{[]string{"10.1.0.107:9092", "10.1.0.107:9093", "192.168.0.40:9094"}}} - - if !reflect.DeepEqual(actual, expected) { - t.Errorf("Bad unmarshal: expected %+v, got %+v", expected, actual) - } -} - -func TestTomlSliceOfSliceOfSlice(t *testing.T) { - tree, err := Load(` hosts=[[["10.1.0.107:9092","10.1.0.107:9093", "192.168.0.40:9094"] ]] `) - m := tree.ToMap() - tree, err = TreeFromMap(m) - if err != nil { - t.Error("should not error", err) - } - type Struct struct { - Hosts [][][]string - } - var actual Struct - tree.Unmarshal(&actual) - - expected := Struct{Hosts: [][][]string{[][]string{[]string{"10.1.0.107:9092", "10.1.0.107:9093", "192.168.0.40:9094"}}}} - - if !reflect.DeepEqual(actual, expected) { - t.Errorf("Bad unmarshal: expected %+v, got %+v", expected, actual) - } -} - -func TestTomlSliceOfSliceInt(t *testing.T) { - tree, err := Load(` hosts=[[1,2,3],[4,5,6] ] `) - m := tree.ToMap() - tree, err = TreeFromMap(m) - if err != nil { - t.Error("should not error", err) - } - type Struct struct { - Hosts [][]int - } - var actual Struct - err = tree.Unmarshal(&actual) - if err != nil { - t.Error("should not error", err) - } - - expected := Struct{Hosts: [][]int{[]int{1, 2, 3}, []int{4, 5, 6}}} - - if !reflect.DeepEqual(actual, expected) { - t.Errorf("Bad unmarshal: expected %+v, got %+v", expected, actual) - } - -} -func TestTomlSliceOfSliceInt64(t *testing.T) { - tree, err := Load(` hosts=[[1,2,3],[4,5,6] ] `) - m := tree.ToMap() - tree, err = TreeFromMap(m) - if err != nil { - t.Error("should not error", err) - } - type Struct struct { - Hosts [][]int64 - } - var actual Struct - err = tree.Unmarshal(&actual) - if err != nil { - t.Error("should not error", err) - } - - expected := Struct{Hosts: [][]int64{[]int64{1, 2, 3}, []int64{4, 5, 6}}} - - if !reflect.DeepEqual(actual, expected) { - t.Errorf("Bad unmarshal: expected %+v, got %+v", expected, actual) - } - -} - -func TestTomlSliceOfSliceInt64FromMap(t *testing.T) { - tree, err := TreeFromMap(map[string]interface{}{"hosts": [][]interface{}{[]interface{}{int32(1), int8(2), 3}}}) - if err != nil { - t.Error("should not error", err) - } - type Struct struct { - Hosts [][]int64 - } - var actual Struct - err = tree.Unmarshal(&actual) - if err != nil { - t.Error("should not error", err) - } - - expected := Struct{Hosts: [][]int64{[]int64{1, 2, 3}}} - - if !reflect.DeepEqual(actual, expected) { - t.Errorf("Bad unmarshal: expected %+v, got %+v", expected, actual) - } - -} -func TestTomlSliceOfSliceError(t *testing.T) { // make Codecov happy - _, err := TreeFromMap(map[string]interface{}{"hosts": [][]interface{}{[]interface{}{1, 2, []struct{}{}}}}) - expected := "cannot convert type []struct {} to Tree" - if err.Error() != expected { - t.Fatalf("unexpected error: %s", err) - } -} diff --git a/tomltree_write.go b/tomltree_write.go deleted file mode 100644 index 05861226..00000000 --- a/tomltree_write.go +++ /dev/null @@ -1,535 +0,0 @@ -package toml - -import ( - "bytes" - "fmt" - "io" - "math" - "math/big" - "reflect" - "sort" - "strconv" - "strings" - "time" -) - -type valueComplexity int - -const ( - valueSimple valueComplexity = iota + 1 - valueComplex -) - -type sortNode struct { - key string - complexity valueComplexity -} - -// Encodes a string to a TOML-compliant multi-line string value -// This function is a clone of the existing encodeTomlString function, except that whitespace characters -// are preserved. Quotation marks and backslashes are also not escaped. -func encodeMultilineTomlString(value string, commented string) string { - var b bytes.Buffer - adjacentQuoteCount := 0 - - b.WriteString(commented) - for i, rr := range value { - if rr != '"' { - adjacentQuoteCount = 0 - } else { - adjacentQuoteCount++ - } - switch rr { - case '\b': - b.WriteString(`\b`) - case '\t': - b.WriteString("\t") - case '\n': - b.WriteString("\n" + commented) - case '\f': - b.WriteString(`\f`) - case '\r': - b.WriteString("\r") - case '"': - if adjacentQuoteCount >= 3 || i == len(value)-1 { - adjacentQuoteCount = 0 - b.WriteString(`\"`) - } else { - b.WriteString(`"`) - } - case '\\': - b.WriteString(`\`) - default: - intRr := uint16(rr) - if intRr < 0x001F { - b.WriteString(fmt.Sprintf("\\u%0.4X", intRr)) - } else { - b.WriteRune(rr) - } - } - } - return b.String() -} - -// Encodes a string to a TOML-compliant string value -func encodeTomlString(value string) string { - var b bytes.Buffer - - for _, rr := range value { - switch rr { - case '\b': - b.WriteString(`\b`) - case '\t': - b.WriteString(`\t`) - case '\n': - b.WriteString(`\n`) - case '\f': - b.WriteString(`\f`) - case '\r': - b.WriteString(`\r`) - case '"': - b.WriteString(`\"`) - case '\\': - b.WriteString(`\\`) - default: - intRr := uint16(rr) - if intRr < 0x001F { - b.WriteString(fmt.Sprintf("\\u%0.4X", intRr)) - } else { - b.WriteRune(rr) - } - } - } - return b.String() -} - -func tomlTreeStringRepresentation(t *Tree, ord marshalOrder) (string, error) { - var orderedVals []sortNode - switch ord { - case OrderPreserve: - orderedVals = sortByLines(t) - default: - orderedVals = sortAlphabetical(t) - } - - var values []string - for _, node := range orderedVals { - k := node.key - v := t.values[k] - - repr, err := tomlValueStringRepresentation(v, "", "", ord, false) - if err != nil { - return "", err - } - values = append(values, quoteKeyIfNeeded(k)+" = "+repr) - } - return "{ " + strings.Join(values, ", ") + " }", nil -} - -func tomlValueStringRepresentation(v interface{}, commented string, indent string, ord marshalOrder, arraysOneElementPerLine bool) (string, error) { - // this interface check is added to dereference the change made in the writeTo function. - // That change was made to allow this function to see formatting options. - tv, ok := v.(*tomlValue) - if ok { - v = tv.value - } else { - tv = &tomlValue{} - } - - switch value := v.(type) { - case uint64: - return strconv.FormatUint(value, 10), nil - case int64: - return strconv.FormatInt(value, 10), nil - case float64: - // Default bit length is full 64 - bits := 64 - // Float panics if nan is used - if !math.IsNaN(value) { - // if 32 bit accuracy is enough to exactly show, use 32 - _, acc := big.NewFloat(value).Float32() - if acc == big.Exact { - bits = 32 - } - } - if math.Trunc(value) == value { - return strings.ToLower(strconv.FormatFloat(value, 'f', 1, bits)), nil - } - return strings.ToLower(strconv.FormatFloat(value, 'f', -1, bits)), nil - case string: - if tv.multiline { - return "\"\"\"\n" + encodeMultilineTomlString(value, commented) + "\"\"\"", nil - } - return "\"" + encodeTomlString(value) + "\"", nil - case []byte: - b, _ := v.([]byte) - return string(b), nil - case bool: - if value { - return "true", nil - } - return "false", nil - case time.Time: - return value.Format(time.RFC3339), nil - case LocalDate: - return value.String(), nil - case LocalDateTime: - return value.String(), nil - case LocalTime: - return value.String(), nil - case *Tree: - return tomlTreeStringRepresentation(value, ord) - case nil: - return "", nil - } - - rv := reflect.ValueOf(v) - - if rv.Kind() == reflect.Slice { - var values []string - for i := 0; i < rv.Len(); i++ { - item := rv.Index(i).Interface() - itemRepr, err := tomlValueStringRepresentation(item, commented, indent, ord, arraysOneElementPerLine) - if err != nil { - return "", err - } - values = append(values, itemRepr) - } - if arraysOneElementPerLine && len(values) > 1 { - stringBuffer := bytes.Buffer{} - valueIndent := indent + ` ` // TODO: move that to a shared encoder state - - stringBuffer.WriteString("[\n") - - for _, value := range values { - stringBuffer.WriteString(valueIndent) - stringBuffer.WriteString(commented + value) - stringBuffer.WriteString(`,`) - stringBuffer.WriteString("\n") - } - - stringBuffer.WriteString(indent + commented + "]") - - return stringBuffer.String(), nil - } - return "[" + strings.Join(values, ", ") + "]", nil - } - return "", fmt.Errorf("unsupported value type %T: %v", v, v) -} - -func getTreeArrayLine(trees []*Tree) (line int) { - // get lowest line number that is not 0 - for _, tv := range trees { - if tv.position.Line < line || line == 0 { - line = tv.position.Line - } - } - return -} - -func sortByLines(t *Tree) (vals []sortNode) { - var ( - line int - lines []int - tv *Tree - tom *tomlValue - node sortNode - ) - vals = make([]sortNode, 0) - m := make(map[int]sortNode) - - for k := range t.values { - v := t.values[k] - switch v.(type) { - case *Tree: - tv = v.(*Tree) - line = tv.position.Line - node = sortNode{key: k, complexity: valueComplex} - case []*Tree: - line = getTreeArrayLine(v.([]*Tree)) - node = sortNode{key: k, complexity: valueComplex} - default: - tom = v.(*tomlValue) - line = tom.position.Line - node = sortNode{key: k, complexity: valueSimple} - } - lines = append(lines, line) - vals = append(vals, node) - m[line] = node - } - sort.Ints(lines) - - for i, line := range lines { - vals[i] = m[line] - } - - return vals -} - -func sortAlphabetical(t *Tree) (vals []sortNode) { - var ( - node sortNode - simpVals []string - compVals []string - ) - vals = make([]sortNode, 0) - m := make(map[string]sortNode) - - for k := range t.values { - v := t.values[k] - switch v.(type) { - case *Tree, []*Tree: - node = sortNode{key: k, complexity: valueComplex} - compVals = append(compVals, node.key) - default: - node = sortNode{key: k, complexity: valueSimple} - simpVals = append(simpVals, node.key) - } - vals = append(vals, node) - m[node.key] = node - } - - // Simples first to match previous implementation - sort.Strings(simpVals) - i := 0 - for _, key := range simpVals { - vals[i] = m[key] - i++ - } - - sort.Strings(compVals) - for _, key := range compVals { - vals[i] = m[key] - i++ - } - - return vals -} - -func (t *Tree) writeTo(w io.Writer, indent, keyspace string, bytesCount int64, arraysOneElementPerLine bool) (int64, error) { - return t.writeToOrdered(w, indent, keyspace, bytesCount, arraysOneElementPerLine, OrderAlphabetical, " ", false) -} - -func (t *Tree) writeToOrdered(w io.Writer, indent, keyspace string, bytesCount int64, arraysOneElementPerLine bool, ord marshalOrder, indentString string, parentCommented bool) (int64, error) { - var orderedVals []sortNode - - switch ord { - case OrderPreserve: - orderedVals = sortByLines(t) - default: - orderedVals = sortAlphabetical(t) - } - - for _, node := range orderedVals { - switch node.complexity { - case valueComplex: - k := node.key - v := t.values[k] - - combinedKey := quoteKeyIfNeeded(k) - if keyspace != "" { - combinedKey = keyspace + "." + combinedKey - } - - switch node := v.(type) { - // node has to be of those two types given how keys are sorted above - case *Tree: - tv, ok := t.values[k].(*Tree) - if !ok { - return bytesCount, fmt.Errorf("invalid value type at %s: %T", k, t.values[k]) - } - if tv.comment != "" { - comment := strings.Replace(tv.comment, "\n", "\n"+indent+"#", -1) - start := "# " - if strings.HasPrefix(comment, "#") { - start = "" - } - writtenBytesCountComment, errc := writeStrings(w, "\n", indent, start, comment) - bytesCount += int64(writtenBytesCountComment) - if errc != nil { - return bytesCount, errc - } - } - - var commented string - if parentCommented || t.commented || tv.commented { - commented = "# " - } - writtenBytesCount, err := writeStrings(w, "\n", indent, commented, "[", combinedKey, "]\n") - bytesCount += int64(writtenBytesCount) - if err != nil { - return bytesCount, err - } - bytesCount, err = node.writeToOrdered(w, indent+indentString, combinedKey, bytesCount, arraysOneElementPerLine, ord, indentString, parentCommented || t.commented || tv.commented) - if err != nil { - return bytesCount, err - } - case []*Tree: - for _, subTree := range node { - var commented string - if parentCommented || t.commented || subTree.commented { - commented = "# " - } - writtenBytesCount, err := writeStrings(w, "\n", indent, commented, "[[", combinedKey, "]]\n") - bytesCount += int64(writtenBytesCount) - if err != nil { - return bytesCount, err - } - - bytesCount, err = subTree.writeToOrdered(w, indent+indentString, combinedKey, bytesCount, arraysOneElementPerLine, ord, indentString, parentCommented || t.commented || subTree.commented) - if err != nil { - return bytesCount, err - } - } - } - default: // Simple - k := node.key - v, ok := t.values[k].(*tomlValue) - if !ok { - return bytesCount, fmt.Errorf("invalid value type at %s: %T", k, t.values[k]) - } - - var commented string - if parentCommented || t.commented || v.commented { - commented = "# " - } - repr, err := tomlValueStringRepresentation(v, commented, indent, ord, arraysOneElementPerLine) - if err != nil { - return bytesCount, err - } - - if v.comment != "" { - comment := strings.Replace(v.comment, "\n", "\n"+indent+"#", -1) - start := "# " - if strings.HasPrefix(comment, "#") { - start = "" - } - writtenBytesCountComment, errc := writeStrings(w, "\n", indent, start, comment, "\n") - bytesCount += int64(writtenBytesCountComment) - if errc != nil { - return bytesCount, errc - } - } - - quotedKey := quoteKeyIfNeeded(k) - writtenBytesCount, err := writeStrings(w, indent, commented, quotedKey, " = ", repr, "\n") - bytesCount += int64(writtenBytesCount) - if err != nil { - return bytesCount, err - } - } - } - - return bytesCount, nil -} - -// quote a key if it does not fit the bare key format (A-Za-z0-9_-) -// quoted keys use the same rules as strings -func quoteKeyIfNeeded(k string) string { - // when encoding a map with the 'quoteMapKeys' option enabled, the tree will contain - // keys that have already been quoted. - // not an ideal situation, but good enough of a stop gap. - if len(k) >= 2 && k[0] == '"' && k[len(k)-1] == '"' { - return k - } - isBare := true - for _, r := range k { - if !isValidBareChar(r) { - isBare = false - break - } - } - if isBare { - return k - } - return quoteKey(k) -} - -func quoteKey(k string) string { - return "\"" + encodeTomlString(k) + "\"" -} - -func writeStrings(w io.Writer, s ...string) (int, error) { - var n int - for i := range s { - b, err := io.WriteString(w, s[i]) - n += b - if err != nil { - return n, err - } - } - return n, nil -} - -// WriteTo encode the Tree as Toml and writes it to the writer w. -// Returns the number of bytes written in case of success, or an error if anything happened. -func (t *Tree) WriteTo(w io.Writer) (int64, error) { - return t.writeTo(w, "", "", 0, false) -} - -// ToTomlString generates a human-readable representation of the current tree. -// Output spans multiple lines, and is suitable for ingest by a TOML parser. -// If the conversion cannot be performed, ToString returns a non-nil error. -func (t *Tree) ToTomlString() (string, error) { - b, err := t.Marshal() - if err != nil { - return "", err - } - return string(b), nil -} - -// String generates a human-readable representation of the current tree. -// Alias of ToString. Present to implement the fmt.Stringer interface. -func (t *Tree) String() string { - result, _ := t.ToTomlString() - return result -} - -// ToMap recursively generates a representation of the tree using Go built-in structures. -// The following types are used: -// -// * bool -// * float64 -// * int64 -// * string -// * uint64 -// * time.Time -// * map[string]interface{} (where interface{} is any of this list) -// * []interface{} (where interface{} is any of this list) -func (t *Tree) ToMap() map[string]interface{} { - result := map[string]interface{}{} - - for k, v := range t.values { - switch node := v.(type) { - case []*Tree: - var array []interface{} - for _, item := range node { - array = append(array, item.ToMap()) - } - result[k] = array - case *Tree: - result[k] = node.ToMap() - case *tomlValue: - result[k] = tomlValueToGo(node.value) - } - } - return result -} - -func tomlValueToGo(v interface{}) interface{} { - if tree, ok := v.(*Tree); ok { - return tree.ToMap() - } - - rv := reflect.ValueOf(v) - - if rv.Kind() != reflect.Slice { - return v - } - values := make([]interface{}, rv.Len()) - for i := 0; i < rv.Len(); i++ { - item := rv.Index(i).Interface() - values[i] = tomlValueToGo(item) - } - return values -} diff --git a/tomltree_write_test.go b/tomltree_write_test.go deleted file mode 100644 index b254d76a..00000000 --- a/tomltree_write_test.go +++ /dev/null @@ -1,437 +0,0 @@ -package toml - -import ( - "bytes" - "errors" - "fmt" - "reflect" - "strings" - "testing" - "time" -) - -type failingWriter struct { - failAt int - written int - buffer bytes.Buffer -} - -func (f *failingWriter) Write(p []byte) (n int, err error) { - count := len(p) - toWrite := f.failAt - (count + f.written) - if toWrite < 0 { - toWrite = 0 - } - if toWrite > count { - f.written += count - f.buffer.Write(p) - return count, nil - } - - f.buffer.Write(p[:toWrite]) - f.written = f.failAt - return toWrite, fmt.Errorf("failingWriter failed after writing %d bytes", f.written) -} - -func assertErrorString(t *testing.T, expected string, err error) { - expectedErr := errors.New(expected) - if err == nil || err.Error() != expectedErr.Error() { - t.Errorf("expecting error %s, but got %s instead", expected, err) - } -} - -func TestTreeWriteToEmptyTable(t *testing.T) { - doc := `[[empty-tables]] -[[empty-tables]]` - - toml, err := Load(doc) - if err != nil { - t.Fatal("Unexpected Load error:", err) - } - tomlString, err := toml.ToTomlString() - if err != nil { - t.Fatal("Unexpected ToTomlString error:", err) - } - - expected := ` -[[empty-tables]] - -[[empty-tables]] -` - - if tomlString != expected { - t.Fatalf("Expected:\n%s\nGot:\n%s", expected, tomlString) - } -} - -func TestTreeWriteToTomlString(t *testing.T) { - toml, err := Load(`name = { first = "Tom", last = "Preston-Werner" } -points = { x = 1, y = 2 }`) - - if err != nil { - t.Fatal("Unexpected error:", err) - } - - tomlString, _ := toml.ToTomlString() - reparsedTree, err := Load(tomlString) - - assertTree(t, reparsedTree, err, map[string]interface{}{ - "name": map[string]interface{}{ - "first": "Tom", - "last": "Preston-Werner", - }, - "points": map[string]interface{}{ - "x": int64(1), - "y": int64(2), - }, - }) -} - -func TestTreeWriteToTomlStringSimple(t *testing.T) { - tree, err := Load("[foo]\n\n[[foo.bar]]\na = 42\n\n[[foo.bar]]\na = 69\n") - if err != nil { - t.Errorf("Test failed to parse: %v", err) - return - } - result, err := tree.ToTomlString() - if err != nil { - t.Errorf("Unexpected error: %s", err) - } - expected := "\n[foo]\n\n [[foo.bar]]\n a = 42\n\n [[foo.bar]]\n a = 69\n" - if result != expected { - t.Errorf("Expected got '%s', expected '%s'", result, expected) - } -} - -func TestTreeWriteToTomlStringKeysOrders(t *testing.T) { - for i := 0; i < 100; i++ { - tree, _ := Load(` - foobar = true - bar = "baz" - foo = 1 - [qux] - foo = 1 - bar = "baz2"`) - - stringRepr, _ := tree.ToTomlString() - - t.Log("Intermediate string representation:") - t.Log(stringRepr) - - r := strings.NewReader(stringRepr) - toml, err := LoadReader(r) - - if err != nil { - t.Fatal("Unexpected error:", err) - } - - assertTree(t, toml, err, map[string]interface{}{ - "foobar": true, - "bar": "baz", - "foo": 1, - "qux": map[string]interface{}{ - "foo": 1, - "bar": "baz2", - }, - }) - } -} - -func testMaps(t *testing.T, actual, expected map[string]interface{}) { - if !reflect.DeepEqual(actual, expected) { - t.Fatal("trees aren't equal.\n", "Expected:\n", expected, "\nActual:\n", actual) - } -} - -func TestTreeWriteToMapSimple(t *testing.T) { - tree, _ := Load("a = 42\nb = 17") - - expected := map[string]interface{}{ - "a": int64(42), - "b": int64(17), - } - - testMaps(t, tree.ToMap(), expected) -} - -func TestTreeWriteToInvalidTreeSimpleValue(t *testing.T) { - tree := Tree{values: map[string]interface{}{"foo": int8(1)}} - _, err := tree.ToTomlString() - assertErrorString(t, "invalid value type at foo: int8", err) -} - -func TestTreeWriteToInvalidTreeTomlValue(t *testing.T) { - tree := Tree{values: map[string]interface{}{"foo": &tomlValue{value: int8(1), comment: "", position: Position{}}}} - _, err := tree.ToTomlString() - assertErrorString(t, "unsupported value type int8: 1", err) -} - -func TestTreeWriteToInvalidTreeTomlValueArray(t *testing.T) { - tree := Tree{values: map[string]interface{}{"foo": &tomlValue{value: int8(1), comment: "", position: Position{}}}} - _, err := tree.ToTomlString() - assertErrorString(t, "unsupported value type int8: 1", err) -} - -func TestTreeWriteToFailingWriterInSimpleValue(t *testing.T) { - toml, _ := Load(`a = 2`) - writer := failingWriter{failAt: 0, written: 0} - _, err := toml.WriteTo(&writer) - assertErrorString(t, "failingWriter failed after writing 0 bytes", err) -} - -func TestTreeWriteToFailingWriterInTable(t *testing.T) { - toml, _ := Load(` -[b] -a = 2`) - writer := failingWriter{failAt: 2, written: 0} - _, err := toml.WriteTo(&writer) - assertErrorString(t, "failingWriter failed after writing 2 bytes", err) - - writer = failingWriter{failAt: 13, written: 0} - _, err = toml.WriteTo(&writer) - assertErrorString(t, "failingWriter failed after writing 13 bytes", err) -} - -func TestTreeWriteToFailingWriterInArray(t *testing.T) { - toml, _ := Load(` -[[b]] -a = 2`) - writer := failingWriter{failAt: 2, written: 0} - _, err := toml.WriteTo(&writer) - assertErrorString(t, "failingWriter failed after writing 2 bytes", err) - - writer = failingWriter{failAt: 15, written: 0} - _, err = toml.WriteTo(&writer) - assertErrorString(t, "failingWriter failed after writing 15 bytes", err) -} - -func TestTreeWriteToMapExampleFile(t *testing.T) { - tree, _ := LoadFile("example.toml") - expected := map[string]interface{}{ - "title": "TOML Example", - "owner": map[string]interface{}{ - "name": "Tom Preston-Werner", - "organization": "GitHub", - "bio": "GitHub Cofounder & CEO\nLikes tater tots and beer.", - "dob": time.Date(1979, time.May, 27, 7, 32, 0, 0, time.UTC), - }, - "database": map[string]interface{}{ - "server": "192.168.1.1", - "ports": []interface{}{int64(8001), int64(8001), int64(8002)}, - "connection_max": int64(5000), - "enabled": true, - }, - "servers": map[string]interface{}{ - "alpha": map[string]interface{}{ - "ip": "10.0.0.1", - "dc": "eqdc10", - }, - "beta": map[string]interface{}{ - "ip": "10.0.0.2", - "dc": "eqdc10", - }, - }, - "clients": map[string]interface{}{ - "data": []interface{}{ - []interface{}{"gamma", "delta"}, - []interface{}{int64(1), int64(2)}, - }, - "score": 4e-08, - }, - } - testMaps(t, tree.ToMap(), expected) -} - -func TestTreeWriteToMapWithTablesInMultipleChunks(t *testing.T) { - tree, _ := Load(` - [[menu.main]] - a = "menu 1" - b = "menu 2" - [[menu.main]] - c = "menu 3" - d = "menu 4"`) - expected := map[string]interface{}{ - "menu": map[string]interface{}{ - "main": []interface{}{ - map[string]interface{}{"a": "menu 1", "b": "menu 2"}, - map[string]interface{}{"c": "menu 3", "d": "menu 4"}, - }, - }, - } - treeMap := tree.ToMap() - - testMaps(t, treeMap, expected) -} - -func TestTreeWriteToMapWithArrayOfInlineTables(t *testing.T) { - tree, _ := Load(` - [params] - language_tabs = [ - { key = "shell", name = "Shell" }, - { key = "ruby", name = "Ruby" }, - { key = "python", name = "Python" } - ]`) - - expected := map[string]interface{}{ - "params": map[string]interface{}{ - "language_tabs": []interface{}{ - map[string]interface{}{ - "key": "shell", - "name": "Shell", - }, - map[string]interface{}{ - "key": "ruby", - "name": "Ruby", - }, - map[string]interface{}{ - "key": "python", - "name": "Python", - }, - }, - }, - } - - treeMap := tree.ToMap() - testMaps(t, treeMap, expected) -} - -func TestTreeWriteToMapWithTableInMixedArray(t *testing.T) { - tree, _ := Load(`a = [ - "foo", - [ - "bar", - {baz = "quux"}, - ], - [ - {a = "b"}, - {c = "d"}, - ], - ]`) - expected := map[string]interface{}{ - "a": []interface{}{ - "foo", - []interface{}{ - "bar", - map[string]interface{}{ - "baz": "quux", - }, - }, - []interface{}{ - map[string]interface{}{ - "a": "b", - }, - map[string]interface{}{ - "c": "d", - }, - }, - }, - } - treeMap := tree.ToMap() - - testMaps(t, treeMap, expected) -} - -func TestTreeWriteToFloat(t *testing.T) { - tree, err := Load(`a = 3.0`) - if err != nil { - t.Fatal(err) - } - str, err := tree.ToTomlString() - if err != nil { - t.Fatal(err) - } - expected := `a = 3.0` - if strings.TrimSpace(str) != strings.TrimSpace(expected) { - t.Fatalf("Expected:\n%s\nGot:\n%s", expected, str) - } -} - -func TestTreeWriteToSpecialFloat(t *testing.T) { - expected := `a = +inf -b = -inf -c = nan` - - tree, err := Load(expected) - if err != nil { - t.Fatal(err) - } - str, err := tree.ToTomlString() - if err != nil { - t.Fatal(err) - } - if strings.TrimSpace(str) != strings.TrimSpace(expected) { - t.Fatalf("Expected:\n%s\nGot:\n%s", expected, str) - } -} - -func TestIssue290(t *testing.T) { - tomlString := - `[table] -"127.0.0.1" = "value" -"127.0.0.1:8028" = "value" -"character encoding" = "value" -"ʎǝʞ" = "value"` - - t1, err := Load(tomlString) - if err != nil { - t.Fatal("load err:", err) - } - - s, err := t1.ToTomlString() - if err != nil { - t.Fatal("ToTomlString err:", err) - } - - _, err = Load(s) - if err != nil { - t.Fatal("reload err:", err) - } -} - -func BenchmarkTreeToTomlString(b *testing.B) { - toml, err := Load(sampleHard) - if err != nil { - b.Fatal("Unexpected error:", err) - } - - for i := 0; i < b.N; i++ { - _, err := toml.ToTomlString() - if err != nil { - b.Fatal(err) - } - } -} - -var sampleHard = `# Test file for TOML -# Only this one tries to emulate a TOML file written by a user of the kind of parser writers probably hate -# This part you'll really hate - -[the] -test_string = "You'll hate me after this - #" # " Annoying, isn't it? - - [the.hard] - test_array = [ "] ", " # "] # ] There you go, parse this! - test_array2 = [ "Test #11 ]proved that", "Experiment #9 was a success" ] - # You didn't think it'd as easy as chucking out the last #, did you? - another_test_string = " Same thing, but with a string #" - harder_test_string = " And when \"'s are in the string, along with # \"" # "and comments are there too" - # Things will get harder - - [the.hard."bit#"] - "what?" = "You don't think some user won't do that?" - multi_line_array = [ - "]", - # ] Oh yes I did - ] - -# Each of the following keygroups/key value pairs should produce an error. Uncomment to them to test - -#[error] if you didn't catch this, your parser is broken -#string = "Anything other than tabs, spaces and newline after a keygroup or key value pair has ended should produce an error unless it is a comment" like this -#array = [ -# "This might most likely happen in multiline arrays", -# Like here, -# "or here, -# and here" -# ] End of array comment, forgot the # -#number = 3.14 pi <--again forgot the # ` From abe1005d7a4d91d10ee8e34e5d5497267603bd09 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Sat, 30 Jan 2021 20:31:14 -0500 Subject: [PATCH 002/228] wip: string parsing --- toml.go | 467 +++++++++++++++++++++++++++++++++++++++++++++++++++ toml_test.go | 34 ++++ 2 files changed, 501 insertions(+) create mode 100644 toml.go create mode 100644 toml_test.go diff --git a/toml.go b/toml.go new file mode 100644 index 00000000..115d073d --- /dev/null +++ b/toml.go @@ -0,0 +1,467 @@ +package toml + +import ( + "fmt" + "unicode/utf8" +) + +func Unmarshal(data []byte, v interface{}) error { + // TODO + return nil +} + +func Marshal(v interface{}) ([]byte, error) { + // TODO + return nil, nil +} + +type Document struct { +} + +type builder interface { + Whitespace(b []byte) + Comment(b []byte) + UnquotedKey(b []byte) + LiteralString(b []byte) + BasicString(b []byte) +} + +type position struct { + line int + column int +} + +type documentBuilder struct { + document Document +} + +func (d *documentBuilder) BasicString(b []byte) { + s := string(b) + fmt.Printf("BasicString: '%s'\n", s) +} + +func (d *documentBuilder) LiteralString(b []byte) { + s := string(b) + fmt.Printf("LiteralString: '%s'\n", s) +} + +func (d *documentBuilder) UnquotedKey(b []byte) { + s := string(b) + fmt.Printf("UnquotedKey: '%s'\n", s) +} + +func (d *documentBuilder) Comment(b []byte) { + s := string(b) + fmt.Printf("Comment: '%s'\n", s) +} + +func (d *documentBuilder) Whitespace(b []byte) { + s := string(b) + fmt.Printf("Whitespace: '%s'\n", s) +} + +func Parse(b []byte) (Document, error) { + builder := documentBuilder{} + p := parser{builder: &builder} + err := p.parse(b) + if err != nil { + return Document{}, err + } + return builder.document, nil +} + +// eof is a rune value indicating end-of-file. +const eof = -1 + +type parser struct { + builder builder +} + +type InvalidCharacter struct { + r rune +} + +func (e *InvalidCharacter) Error() string { + return fmt.Sprintf("unexpected character '%#U'", e.r) +} + +type UnexpectedCharacter struct { + r rune + expected rune +} + +func (e *UnexpectedCharacter) Error() string { + return fmt.Sprintf("expected character '%#U' but got '%#U'", e.expected, e.r) +} + +func (p *parser) parse(b []byte) error { + next := b + var err error + for { + next, err = p.parseExpression(next) + if err != nil { + return err + } + if len(next) == 0 { + return nil + } + + // new lines between expressions + r, size, err := readRune(next) + if err != nil { + return err + } + if r == '\n' { + next = next[size:] + continue + } + if r == '\r' { + r, size2, err := readRune(next) + if err != nil { + return err + } + if r == '\n' { + next = next[size+size2:] + continue + } + } + return &InvalidCharacter{r: r} + } +} + +func (p *parser) parseExpression(b []byte) ([]byte, error) { + next, err := p.parseWhitespace(b) + if err != nil { + return nil, err + } + r, _, err := readRune(next) + if err != nil { + return nil, err + } + // Line with just whitespace and a comment. We can exit early. + if r == '#' { + return p.parseComment(next) + } + + // or line with something? + + if r == '[' { + // parse table. could be either a standard table or an array table + } + + // it has to be a keyval + + if isUnquotedKeyRune(r) || r == '\'' || r == '"' { + next, err = p.parseKeyval(next) + if err != nil { + return nil, err + } + } + + // parse trailing whitespace and comment + + next, err = p.parseWhitespace(next) + if err != nil { + return nil, err + } + + r, _, err = readRune(next) + if err != nil { + return nil, err + } + + if r == '#' { + return p.parseComment(next) + } + + return next, nil +} + +func (p *parser) parseKeyval(b []byte) ([]byte, error) { + // key keyval-sep val + next, err := p.parseKey(b) + if err != nil { + return nil, err + } + return next, nil +} + +func (p *parser) parseKey(b []byte) ([]byte, error) { + // simple-key / dotted-key + // dotted-key = simple-key 1*( dot-sep simple-key ) + + return p.parseSimpleKey(b) + // TODO: dotted key +} + +func isUnquotedKeyRune(r rune) bool { + return (r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '-' || r == '_' +} + +func (p *parser) parseSimpleKey(b []byte) ([]byte, error) { + // simple-key = quoted-key / unquoted-key + // quoted-key = basic-string / literal-string + // unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _ + // basic-string = quotation-mark *basic-char quotation-mark + // literal-string = apostrophe *literal-char apostrophe + + r, _, err := readRune(b) + if err != nil { + return nil, err + } + + if r == '\'' { + return p.parseLiteralString(b) + } + if r == '"' { + return p.parseBasicString(b) + } + + return p.parseUnquotedKey(b) +} + +func (p *parser) parseUnquotedKey(b []byte) ([]byte, error) { + length := 0 + r, size, err := readRune(b) + if err != nil { + return nil, err + } + + if !isUnquotedKeyRune(r) { + return nil, &InvalidCharacter{r: r} + } + length += size + + for { + r, size, err := readRune(b[length:]) + if err != nil { + return nil, err + } + if !isUnquotedKeyRune(r) { + break + } + length += size + } + p.builder.UnquotedKey(b[:length]) + return b[length:], nil +} + +func expectRune(b []byte, expected rune) (int, error) { + r, size, err := readRune(b) + if err != nil { + return 0, err + } + if r != expected { + return 0, &UnexpectedCharacter{ + r: r, + expected: expected, + } + } + return size, nil +} + +func (p *parser) parseComment(b []byte) ([]byte, error) { + length := 0 + + size, err := expectRune(b, '#') + if err != nil { + return b, err + } + length += size + + for { + r, size, err := readRune(b[length:]) + if err != nil { + return nil, err + } + if r == eof || r == '\n' { + if length > 0 { + p.builder.Comment(b[:length]) + } + return b[length:], nil + } + length += size + } +} + +func isWhitespace(r rune) bool { + switch r { + case 0x20, 0x09: + return true + default: + return false + } +} + +type InvalidUnicodeError struct { + r rune +} + +func (e *InvalidUnicodeError) Error() string { + return fmt.Sprintf("invalid unicode: %#U", e.r) +} + +func readRune(b []byte) (rune, int, error) { + r, size := utf8.DecodeRune(b) + if r == utf8.RuneError { + if size == 0 { // eof + return eof, 0, nil + } + if size == 1 { // invalid rune + return utf8.RuneError, 1, &InvalidUnicodeError{r: r} + } + } + return r, size, nil +} + +func (p *parser) parseWhitespace(b []byte) ([]byte, error) { + length := 0 + for { + r, size, err := readRune(b[length:]) + if err != nil { + return nil, err + } + if isWhitespace(r) { + length += size + } else { + if length > 0 { + p.builder.Whitespace(b[:length]) + } + return b[length:], nil + } + } +} + +func isNonAsciiChar(r rune) bool { + return (r >= 0x80 && r <= 0xD7FF) || (r >= 0xE000 && r <= 0x10FFFF) +} + +func isLiteralChar(r rune) bool { + return r == 0x09 || (r >= 0x20 && r <= 0x26) || (r >= 0x28 && r <= 0x7E) || isNonAsciiChar(r) +} + +func (p *parser) parseLiteralString(b []byte) ([]byte, error) { + // literal-string = apostrophe *literal-char apostrophe + // literal-char = %x09 / %x20-26 / %x28-7E / non-ascii + // non-ascii = %x80-D7FF / %xE000-10FFFF + + length := 0 + + start, err := expectRune(b, '\'') + if err != nil { + return nil, err + } + + for { + r, size, err := readRune(b[start+length:]) + if err != nil { + return nil, err + } + if r == '\'' { + p.builder.LiteralString(b[start : start+length]) + return b[start+length+size:], nil + } + if !isLiteralChar(r) { + return nil, &InvalidCharacter{r: r} + } + length += size + } +} + +func isBasicStringChar(r rune) bool { + return r == ' ' || r == 0x21 || r >= 0x23 && r <= 0x5B || r >= 0x5D && r <= 0x7E || isNonAsciiChar(r) +} + +func isEscapeChar(r rune) bool { + return r == '"' || r == '\\' || r == 'b' || r == 'f' || r == 'n' || r == 'r' || r == 't' +} + +func isHex(r rune) bool { + return (r >= '0' && r <= '9') || (r >= 'A' && r <= 'F') +} + +func (p *parser) parseBasicString(b []byte) ([]byte, error) { + // basic-string = quotation-mark *basic-char quotation-mark + // basic-char = basic-unescaped / escaped + // basic-unescaped = wschar / %x21 / %x23-5B / %x5D-7E / non-ascii + // escaped = escape escape-seq-char + //escape = %x5C ; \ + //escape-seq-char = %x22 ; " quotation mark U+0022 + //escape-seq-char =/ %x5C ; \ reverse solidus U+005C + //escape-seq-char =/ %x62 ; b backspace U+0008 + //escape-seq-char =/ %x66 ; f form feed U+000C + //escape-seq-char =/ %x6E ; n line feed U+000A + //escape-seq-char =/ %x72 ; r carriage return U+000D + //escape-seq-char =/ %x74 ; t tab U+0009 + //escape-seq-char =/ %x75 4HEXDIG ; uXXXX U+XXXX + //escape-seq-char =/ %x55 8HEXDIG ; UXXXXXXXX U+XXXXXXXX + // HEXDIG = DIGIT / "A" / "B" / "C" / "D" / "E" / "F" + length := 0 + + start, err := expectRune(b, '"') + if err != nil { + return nil, err + } + + for { + r, size, err := readRune(b[start+length:]) + if err != nil { + return nil, err + } + if r == '"' { + p.builder.BasicString(b[start : start+length]) + return b[start+length+size:], nil + } + + if r == '\\' { + length += size + r, size, err := readRune(b[start+length:]) + if err != nil { + return nil, err + } + + if isEscapeChar(r) { + length += size + continue + } + + if r == 'u' { + length += size + for i := 0; i < 4; i++ { + r, size, err := readRune(b[start+length:]) + if err != nil { + return nil, err + } + if !isHex(r) { + return nil, &InvalidCharacter{r: r} + } + length += size + } + continue + } + + if r == 'U' { + length += size + for i := 0; i < 8; i++ { + r, size, err := readRune(b[start+length:]) + if err != nil { + return nil, err + } + if !isHex(r) { + return nil, &InvalidCharacter{r: r} + } + length += size + } + continue + } + + return nil, &InvalidCharacter{r: r} + + } + + if isBasicStringChar(r) { + length += size + continue + } + } +} diff --git a/toml_test.go b/toml_test.go new file mode 100644 index 00000000..1aae1eb5 --- /dev/null +++ b/toml_test.go @@ -0,0 +1,34 @@ +package toml + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestParse(t *testing.T) { + + inputs := []string{ + ` #foo`, + `#foo`, + `#`, + "\n\n\n", + "#one\n # two \n", + `a`, + `abc`, + ` abc # foo`, + `'abc'`, + `"foo bar"`, + `"hello\tworld"`, + `"hello \u1234 foo"`, + } + + for i, data := range inputs { + t.Run(fmt.Sprintf("example %d", i), func(t *testing.T) { + doc, err := Parse([]byte(data)) + assert.NoError(t, err) + fmt.Println(doc) + }) + } +} From d54ad15d16ccef5e994708ac71798bd3eda45b7f Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Mon, 1 Feb 2021 09:00:21 -0500 Subject: [PATCH 003/228] Track ABNF file --- toml.abnf | 243 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 243 insertions(+) create mode 100644 toml.abnf diff --git a/toml.abnf b/toml.abnf new file mode 100644 index 00000000..473f3749 --- /dev/null +++ b/toml.abnf @@ -0,0 +1,243 @@ +;; This document describes TOML's syntax, using the ABNF format (defined in +;; RFC 5234 -- https://www.ietf.org/rfc/rfc5234.txt). +;; +;; All valid TOML documents will match this description, however certain +;; invalid documents would need to be rejected as per the semantics described +;; in the supporting text description. + +;; It is possible to try this grammar interactively, using instaparse. +;; http://instaparse.mojombo.com/ +;; +;; To do so, in the lower right, click on Options and change `:input-format` to +;; ':abnf'. Then paste this entire ABNF document into the grammar entry box +;; (above the options). Then you can type or paste a sample TOML document into +;; the beige box on the left. Tada! + +;; Overall Structure + +toml = expression *( newline expression ) + +expression = ws [ comment ] +expression =/ ws keyval ws [ comment ] +expression =/ ws table ws [ comment ] + +;; Whitespace + +ws = *wschar +wschar = %x20 ; Space +wschar =/ %x09 ; Horizontal tab + +;; Newline + +newline = %x0A ; LF +newline =/ %x0D.0A ; CRLF + +;; Comment + +comment-start-symbol = %x23 ; # +non-ascii = %x80-D7FF / %xE000-10FFFF +non-eol = %x09 / %x20-7F / non-ascii + +comment = comment-start-symbol *non-eol + +;; Key-Value pairs + +keyval = key keyval-sep val + +key = simple-key / dotted-key +simple-key = quoted-key / unquoted-key + +unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _ +quoted-key = basic-string / literal-string +dotted-key = simple-key 1*( dot-sep simple-key ) + +dot-sep = ws %x2E ws ; . Period +keyval-sep = ws %x3D ws ; = + +val = string / boolean / array / inline-table / date-time / float / integer + +;; String + +string = ml-basic-string / basic-string / ml-literal-string / literal-string + +;; Basic String + +basic-string = quotation-mark *basic-char quotation-mark + +quotation-mark = %x22 ; " + +basic-char = basic-unescaped / escaped +basic-unescaped = wschar / %x21 / %x23-5B / %x5D-7E / non-ascii +escaped = escape escape-seq-char + +escape = %x5C ; \ +escape-seq-char = %x22 ; " quotation mark U+0022 +escape-seq-char =/ %x5C ; \ reverse solidus U+005C +escape-seq-char =/ %x62 ; b backspace U+0008 +escape-seq-char =/ %x66 ; f form feed U+000C +escape-seq-char =/ %x6E ; n line feed U+000A +escape-seq-char =/ %x72 ; r carriage return U+000D +escape-seq-char =/ %x74 ; t tab U+0009 +escape-seq-char =/ %x75 4HEXDIG ; uXXXX U+XXXX +escape-seq-char =/ %x55 8HEXDIG ; UXXXXXXXX U+XXXXXXXX + +;; Multiline Basic String + +ml-basic-string = ml-basic-string-delim [ newline ] ml-basic-body + ml-basic-string-delim +ml-basic-string-delim = 3quotation-mark +ml-basic-body = *mlb-content *( mlb-quotes 1*mlb-content ) [ mlb-quotes ] + +mlb-content = mlb-char / newline / mlb-escaped-nl +mlb-char = mlb-unescaped / escaped +mlb-quotes = 1*2quotation-mark +mlb-unescaped = wschar / %x21 / %x23-5B / %x5D-7E / non-ascii +mlb-escaped-nl = escape ws newline *( wschar / newline ) + +;; Literal String + +literal-string = apostrophe *literal-char apostrophe + +apostrophe = %x27 ; ' apostrophe + +literal-char = %x09 / %x20-26 / %x28-7E / non-ascii + +;; Multiline Literal String + +ml-literal-string = ml-literal-string-delim [ newline ] ml-literal-body + ml-literal-string-delim +ml-literal-string-delim = 3apostrophe +ml-literal-body = *mll-content *( mll-quotes 1*mll-content ) [ mll-quotes ] + +mll-content = mll-char / newline +mll-char = %x09 / %x20-26 / %x28-7E / non-ascii +mll-quotes = 1*2apostrophe + +;; Integer + +integer = dec-int / hex-int / oct-int / bin-int + +minus = %x2D ; - +plus = %x2B ; + +underscore = %x5F ; _ +digit1-9 = %x31-39 ; 1-9 +digit0-7 = %x30-37 ; 0-7 +digit0-1 = %x30-31 ; 0-1 + +hex-prefix = %x30.78 ; 0x +oct-prefix = %x30.6F ; 0o +bin-prefix = %x30.62 ; 0b + +dec-int = [ minus / plus ] unsigned-dec-int +unsigned-dec-int = DIGIT / digit1-9 1*( DIGIT / underscore DIGIT ) + +hex-int = hex-prefix HEXDIG *( HEXDIG / underscore HEXDIG ) +oct-int = oct-prefix digit0-7 *( digit0-7 / underscore digit0-7 ) +bin-int = bin-prefix digit0-1 *( digit0-1 / underscore digit0-1 ) + +;; Float + +float = float-int-part ( exp / frac [ exp ] ) +float =/ special-float + +float-int-part = dec-int +frac = decimal-point zero-prefixable-int +decimal-point = %x2E ; . +zero-prefixable-int = DIGIT *( DIGIT / underscore DIGIT ) + +exp = "e" float-exp-part +float-exp-part = [ minus / plus ] zero-prefixable-int + +special-float = [ minus / plus ] ( inf / nan ) +inf = %x69.6e.66 ; inf +nan = %x6e.61.6e ; nan + +;; Boolean + +boolean = true / false + +true = %x74.72.75.65 ; true +false = %x66.61.6C.73.65 ; false + +;; Date and Time (as defined in RFC 3339) + +date-time = offset-date-time / local-date-time / local-date / local-time + +date-fullyear = 4DIGIT +date-month = 2DIGIT ; 01-12 +date-mday = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on month/year +time-delim = "T" / %x20 ; T, t, or space +time-hour = 2DIGIT ; 00-23 +time-minute = 2DIGIT ; 00-59 +time-second = 2DIGIT ; 00-58, 00-59, 00-60 based on leap second rules +time-secfrac = "." 1*DIGIT +time-numoffset = ( "+" / "-" ) time-hour ":" time-minute +time-offset = "Z" / time-numoffset + +partial-time = time-hour ":" time-minute ":" time-second [ time-secfrac ] +full-date = date-fullyear "-" date-month "-" date-mday +full-time = partial-time time-offset + +;; Offset Date-Time + +offset-date-time = full-date time-delim full-time + +;; Local Date-Time + +local-date-time = full-date time-delim partial-time + +;; Local Date + +local-date = full-date + +;; Local Time + +local-time = partial-time + +;; Array + +array = array-open [ array-values ] ws-comment-newline array-close + +array-open = %x5B ; [ +array-close = %x5D ; ] + +array-values = ws-comment-newline val ws-comment-newline array-sep array-values +array-values =/ ws-comment-newline val ws-comment-newline [ array-sep ] + +array-sep = %x2C ; , Comma + +ws-comment-newline = *( wschar / [ comment ] newline ) + +;; Table + +table = std-table / array-table + +;; Standard Table + +std-table = std-table-open key std-table-close + +std-table-open = %x5B ws ; [ Left square bracket +std-table-close = ws %x5D ; ] Right square bracket + +;; Inline Table + +inline-table = inline-table-open [ inline-table-keyvals ] inline-table-close + +inline-table-open = %x7B ws ; { +inline-table-close = ws %x7D ; } +inline-table-sep = ws %x2C ws ; , Comma + +inline-table-keyvals = keyval [ inline-table-sep inline-table-keyvals ] + +;; Array Table + +array-table = array-table-open key array-table-close + +array-table-open = %x5B.5B ws ; [[ Double left square bracket +array-table-close = ws %x5D.5D ; ]] Double right square bracket + +;; Built-in ABNF terms, reproduced here for clarity + +ALPHA = %x41-5A / %x61-7A ; A-Z / a-z +DIGIT = %x30-39 ; 0-9 +HEXDIG = DIGIT / "A" / "B" / "C" / "D" / "E" / "F" From 07aa85ea0b36d3be489b8be6663472617c65a6b7 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Mon, 1 Feb 2021 09:00:36 -0500 Subject: [PATCH 004/228] Refactor to use parser state --- toml.go | 336 +++++++++++++++++++++++++++++---------------------- toml_test.go | 1 + 2 files changed, 192 insertions(+), 145 deletions(-) diff --git a/toml.go b/toml.go index 115d073d..85146711 100644 --- a/toml.go +++ b/toml.go @@ -62,8 +62,8 @@ func (d *documentBuilder) Whitespace(b []byte) { func Parse(b []byte) (Document, error) { builder := documentBuilder{} - p := parser{builder: &builder} - err := p.parse(b) + p := parser{builder: &builder, data: b} + err := p.parse() if err != nil { return Document{}, err } @@ -73,8 +73,93 @@ func Parse(b []byte) (Document, error) { // eof is a rune value indicating end-of-file. const eof = -1 +type lookahead struct { + r rune + size int +} + +func (l lookahead) empty() bool { + return l.r == 0 +} + type parser struct { builder builder + + data []byte + start int + end int + + lookahead lookahead +} + +func (p *parser) peek() (rune, error) { + if p.lookahead.empty() { + p.lookahead.r, p.lookahead.size = utf8.DecodeRune(p.data[p.end:]) + if p.lookahead.r == utf8.RuneError { + + switch p.lookahead.size { + case 0: + p.lookahead.r = eof + case 1: + p.lookahead.r = utf8.RuneError + return utf8.RuneError, &InvalidUnicodeError{r: p.lookahead.r} + default: + panic("unhandled rune error case") + } + } + } + return p.lookahead.r, nil +} + +func (p *parser) next() (rune, error) { + r, err := p.peek() + if err == nil { + p.end += p.lookahead.size + p.lookahead.r = 0 + p.lookahead.size = 0 + } + return r, err +} + +func (p *parser) sureNext() { + _, err := p.next() + if err != nil { + panic(err) + } +} + +func (p *parser) ignore() { + if p.empty() { + panic("cannot ignore empty token") + } + p.start = p.end +} + +func (p *parser) accept() []byte { + if p.empty() { + panic("cannot accept empty token") + } + x := p.data[p.start:p.end] + p.start = p.end + return x +} + +func (p *parser) expect(expected rune) error { + r, err := p.next() + if err != nil { + return err + } + if r != expected { + return &UnexpectedCharacter{ + r: r, + expected: expected, + } + } + return nil +} + +func (p *parser) empty() bool { + return p.start == p.end } type InvalidCharacter struct { @@ -94,34 +179,31 @@ func (e *UnexpectedCharacter) Error() string { return fmt.Sprintf("expected character '%#U' but got '%#U'", e.expected, e.r) } -func (p *parser) parse(b []byte) error { - next := b - var err error +func (p *parser) parse() error { for { - next, err = p.parseExpression(next) + err := p.parseExpression() if err != nil { return err } - if len(next) == 0 { - return nil - } // new lines between expressions - r, size, err := readRune(next) + r, err := p.next() if err != nil { return err } - if r == '\n' { - next = next[size:] + switch r { + case eof: + return nil + case '\n': + p.ignore() continue - } - if r == '\r' { - r, size2, err := readRune(next) + case '\r': + r, err = p.next() if err != nil { return err } if r == '\n' { - next = next[size+size2:] + p.ignore() continue } } @@ -129,22 +211,23 @@ func (p *parser) parse(b []byte) error { } } -func (p *parser) parseExpression(b []byte) ([]byte, error) { - next, err := p.parseWhitespace(b) +func (p *parser) parseExpression() error { + err := p.parseWhitespace() if err != nil { - return nil, err + return err } - r, _, err := readRune(next) + + r, err := p.peek() if err != nil { - return nil, err + return err } + // Line with just whitespace and a comment. We can exit early. if r == '#' { - return p.parseComment(next) + return p.parseComment() } // or line with something? - if r == '[' { // parse table. could be either a standard table or an array table } @@ -152,45 +235,44 @@ func (p *parser) parseExpression(b []byte) ([]byte, error) { // it has to be a keyval if isUnquotedKeyRune(r) || r == '\'' || r == '"' { - next, err = p.parseKeyval(next) + err := p.parseKeyval() if err != nil { - return nil, err + return err } } // parse trailing whitespace and comment - next, err = p.parseWhitespace(next) + err = p.parseWhitespace() if err != nil { - return nil, err + return err } - r, _, err = readRune(next) + r, err = p.peek() if err != nil { - return nil, err + return err } - if r == '#' { - return p.parseComment(next) + return p.parseComment() } - return next, nil + return nil } -func (p *parser) parseKeyval(b []byte) ([]byte, error) { +func (p *parser) parseKeyval() error { // key keyval-sep val - next, err := p.parseKey(b) + err := p.parseKey() if err != nil { - return nil, err + return err } - return next, nil + return nil } -func (p *parser) parseKey(b []byte) ([]byte, error) { +func (p *parser) parseKey() error { // simple-key / dotted-key // dotted-key = simple-key 1*( dot-sep simple-key ) - return p.parseSimpleKey(b) + return p.parseSimpleKey() // TODO: dotted key } @@ -198,89 +280,67 @@ func isUnquotedKeyRune(r rune) bool { return (r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '-' || r == '_' } -func (p *parser) parseSimpleKey(b []byte) ([]byte, error) { +func (p *parser) parseSimpleKey() error { // simple-key = quoted-key / unquoted-key // quoted-key = basic-string / literal-string // unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _ // basic-string = quotation-mark *basic-char quotation-mark // literal-string = apostrophe *literal-char apostrophe - r, _, err := readRune(b) + r, err := p.peek() if err != nil { - return nil, err + return err } - if r == '\'' { - return p.parseLiteralString(b) - } - if r == '"' { - return p.parseBasicString(b) + switch r { + case '\'': + return p.parseLiteralString() + case '"': + return p.parseBasicString() + default: + return p.parseUnquotedKey() } - - return p.parseUnquotedKey(b) } -func (p *parser) parseUnquotedKey(b []byte) ([]byte, error) { - length := 0 - r, size, err := readRune(b) +func (p *parser) parseUnquotedKey() error { + r, err := p.next() if err != nil { - return nil, err + return err } if !isUnquotedKeyRune(r) { - return nil, &InvalidCharacter{r: r} + return &InvalidCharacter{r: r} } - length += size for { - r, size, err := readRune(b[length:]) + r, err := p.peek() if err != nil { - return nil, err + return err } if !isUnquotedKeyRune(r) { break } - length += size + p.sureNext() } - p.builder.UnquotedKey(b[:length]) - return b[length:], nil -} - -func expectRune(b []byte, expected rune) (int, error) { - r, size, err := readRune(b) - if err != nil { - return 0, err - } - if r != expected { - return 0, &UnexpectedCharacter{ - r: r, - expected: expected, - } - } - return size, nil + p.builder.UnquotedKey(p.accept()) + return nil } -func (p *parser) parseComment(b []byte) ([]byte, error) { - length := 0 - - size, err := expectRune(b, '#') - if err != nil { - return b, err +func (p *parser) parseComment() error { + if err := p.expect('#'); err != nil { + return err } - length += size for { - r, size, err := readRune(b[length:]) + r, err := p.peek() if err != nil { - return nil, err + return err } if r == eof || r == '\n' { - if length > 0 { - p.builder.Comment(b[:length]) - } - return b[length:], nil + p.builder.Comment(p.accept()) + return nil } - length += size + p.sureNext() } } @@ -301,33 +361,19 @@ func (e *InvalidUnicodeError) Error() string { return fmt.Sprintf("invalid unicode: %#U", e.r) } -func readRune(b []byte) (rune, int, error) { - r, size := utf8.DecodeRune(b) - if r == utf8.RuneError { - if size == 0 { // eof - return eof, 0, nil - } - if size == 1 { // invalid rune - return utf8.RuneError, 1, &InvalidUnicodeError{r: r} - } - } - return r, size, nil -} - -func (p *parser) parseWhitespace(b []byte) ([]byte, error) { - length := 0 +func (p *parser) parseWhitespace() error { for { - r, size, err := readRune(b[length:]) + r, err := p.peek() if err != nil { - return nil, err + return err } if isWhitespace(r) { - length += size + p.sureNext() } else { - if length > 0 { - p.builder.Whitespace(b[:length]) + if !p.empty() { + p.builder.Whitespace(p.accept()) } - return b[length:], nil + return nil } } } @@ -340,31 +386,32 @@ func isLiteralChar(r rune) bool { return r == 0x09 || (r >= 0x20 && r <= 0x26) || (r >= 0x28 && r <= 0x7E) || isNonAsciiChar(r) } -func (p *parser) parseLiteralString(b []byte) ([]byte, error) { +func (p *parser) parseLiteralString() error { // literal-string = apostrophe *literal-char apostrophe // literal-char = %x09 / %x20-26 / %x28-7E / non-ascii // non-ascii = %x80-D7FF / %xE000-10FFFF - length := 0 - - start, err := expectRune(b, '\'') + err := p.expect('\'') if err != nil { - return nil, err + return err } + p.ignore() for { - r, size, err := readRune(b[start+length:]) + r, err := p.peek() if err != nil { - return nil, err + return err } if r == '\'' { - p.builder.LiteralString(b[start : start+length]) - return b[start+length+size:], nil + p.builder.LiteralString(p.accept()) + p.sureNext() + p.ignore() + return nil } if !isLiteralChar(r) { - return nil, &InvalidCharacter{r: r} + return &InvalidCharacter{r: r} } - length += size + p.sureNext() } } @@ -380,7 +427,7 @@ func isHex(r rune) bool { return (r >= '0' && r <= '9') || (r >= 'A' && r <= 'F') } -func (p *parser) parseBasicString(b []byte) ([]byte, error) { +func (p *parser) parseBasicString() error { // basic-string = quotation-mark *basic-char quotation-mark // basic-char = basic-unescaped / escaped // basic-unescaped = wschar / %x21 / %x23-5B / %x5D-7E / non-ascii @@ -396,71 +443,70 @@ func (p *parser) parseBasicString(b []byte) ([]byte, error) { //escape-seq-char =/ %x75 4HEXDIG ; uXXXX U+XXXX //escape-seq-char =/ %x55 8HEXDIG ; UXXXXXXXX U+XXXXXXXX // HEXDIG = DIGIT / "A" / "B" / "C" / "D" / "E" / "F" - length := 0 - start, err := expectRune(b, '"') + err := p.expect('"') if err != nil { - return nil, err + return err } + p.ignore() for { - r, size, err := readRune(b[start+length:]) + r, err := p.peek() if err != nil { - return nil, err + return err } + if r == '"' { - p.builder.BasicString(b[start : start+length]) - return b[start+length+size:], nil + p.builder.BasicString(p.accept()) + p.sureNext() + p.ignore() + return nil } if r == '\\' { - length += size - r, size, err := readRune(b[start+length:]) + p.sureNext() + r, err := p.peek() if err != nil { - return nil, err + return err } - if isEscapeChar(r) { - length += size + p.sureNext() continue } if r == 'u' { - length += size + p.sureNext() for i := 0; i < 4; i++ { - r, size, err := readRune(b[start+length:]) + r, err := p.next() if err != nil { - return nil, err + return err } if !isHex(r) { - return nil, &InvalidCharacter{r: r} + return &InvalidCharacter{r: r} } - length += size } continue } if r == 'U' { - length += size + p.sureNext() for i := 0; i < 8; i++ { - r, size, err := readRune(b[start+length:]) + r, err := p.next() if err != nil { - return nil, err + return err } if !isHex(r) { - return nil, &InvalidCharacter{r: r} + return &InvalidCharacter{r: r} } - length += size } continue } - return nil, &InvalidCharacter{r: r} - + return &InvalidCharacter{r: r} } if isBasicStringChar(r) { - length += size + p.sureNext() continue } } diff --git a/toml_test.go b/toml_test.go index 1aae1eb5..d1288440 100644 --- a/toml_test.go +++ b/toml_test.go @@ -26,6 +26,7 @@ func TestParse(t *testing.T) { for i, data := range inputs { t.Run(fmt.Sprintf("example %d", i), func(t *testing.T) { + fmt.Printf("input:\n\t`%s`\n", data) doc, err := Parse([]byte(data)) assert.NoError(t, err) fmt.Println(doc) From 1c7e9fe3af3de09dc6fca3ea7177e337addb6da8 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Mon, 1 Feb 2021 19:07:51 -0500 Subject: [PATCH 005/228] Dotted keys --- toml.go | 45 +++++++++++++++++++++++++++++++++++++++++++-- toml_test.go | 2 ++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/toml.go b/toml.go index 85146711..db13a94c 100644 --- a/toml.go +++ b/toml.go @@ -24,6 +24,7 @@ type builder interface { UnquotedKey(b []byte) LiteralString(b []byte) BasicString(b []byte) + Dot(b []byte) } type position struct { @@ -35,6 +36,11 @@ type documentBuilder struct { document Document } +func (d *documentBuilder) Dot(b []byte) { + s := string(b) + fmt.Printf("DOT: '%s'\n", s) +} + func (d *documentBuilder) BasicString(b []byte) { s := string(b) fmt.Printf("BasicString: '%s'\n", s) @@ -230,6 +236,7 @@ func (p *parser) parseExpression() error { // or line with something? if r == '[' { // parse table. could be either a standard table or an array table + // TODO } // it has to be a keyval @@ -271,9 +278,43 @@ func (p *parser) parseKeyval() error { func (p *parser) parseKey() error { // simple-key / dotted-key // dotted-key = simple-key 1*( dot-sep simple-key ) + // dot-sep = ws %x2E ws + + for { + err := p.parseSimpleKey() + if err != nil { + return err + } + + err = p.parseWhitespace() + if err != nil { + return err + } + + r, err := p.peek() + if err != nil { + return err + } - return p.parseSimpleKey() - // TODO: dotted key + if r != '.' { + break + } + + p.sureNext() + p.builder.Dot(p.accept()) + + err = p.parseWhitespace() + if err != nil { + return err + } + } + + err := p.parseWhitespace() + if err != nil { + return err + } + + return nil } func isUnquotedKeyRune(r rune) bool { diff --git a/toml_test.go b/toml_test.go index d1288440..0dd35ec4 100644 --- a/toml_test.go +++ b/toml_test.go @@ -22,6 +22,8 @@ func TestParse(t *testing.T) { `"foo bar"`, `"hello\tworld"`, `"hello \u1234 foo"`, + `a.b.c`, + `a."b".c`, } for i, data := range inputs { From fd961100c12adb85b6783f5a4f9dd947a5dd8992 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Mon, 1 Feb 2021 19:19:40 -0500 Subject: [PATCH 006/228] Boolean values --- toml.go | 95 ++++++++++++++++++++++++++++++++++++++++++++++++++++ toml_test.go | 18 +++++----- 2 files changed, 104 insertions(+), 9 deletions(-) diff --git a/toml.go b/toml.go index db13a94c..6078d916 100644 --- a/toml.go +++ b/toml.go @@ -25,6 +25,8 @@ type builder interface { LiteralString(b []byte) BasicString(b []byte) Dot(b []byte) + Boolean(b []byte) + Equal(b []byte) } type position struct { @@ -36,6 +38,16 @@ type documentBuilder struct { document Document } +func (d *documentBuilder) Equal(b []byte) { + s := string(b) + fmt.Printf("EQUAL: '%s'\n", s) +} + +func (d *documentBuilder) Boolean(b []byte) { + s := string(b) + fmt.Printf("Boolean: '%s'\n", s) +} + func (d *documentBuilder) Dot(b []byte) { s := string(b) fmt.Printf("DOT: '%s'\n", s) @@ -268,10 +280,93 @@ func (p *parser) parseExpression() error { func (p *parser) parseKeyval() error { // key keyval-sep val + //keyval-sep = ws %x3D ws ; = + err := p.parseKey() if err != nil { return err } + + err = p.parseWhitespace() + if err != nil { + return err + } + + err = p.expect('=') + if err != nil { + return err + } + p.builder.Equal(p.accept()) + + err = p.parseWhitespace() + if err != nil { + return err + } + + return p.parseVal() +} + +func (p *parser) parseVal() error { + //val = string / boolean / array / inline-table / date-time / float / integer + // string = ml-basic-string / basic-string / ml-literal-string / literal-string + + r, err := p.peek() + if err != nil { + return err + } + + switch r { + case 't', 'f': + return p.parseBool() + // TODO + default: + return &InvalidCharacter{r: r} + } +} + +func (p *parser) parseBool() error { + r, err := p.peek() + if err != nil { + return err + } + + if r == 't' { + p.sureNext() + err = p.expect('r') + if err != nil { + return err + } + err = p.expect('u') + if err != nil { + return err + } + err = p.expect('e') + if err != nil { + return err + } + } else if r == 'f' { + p.sureNext() + err = p.expect('a') + if err != nil { + return err + } + err = p.expect('l') + if err != nil { + return err + } + err = p.expect('s') + if err != nil { + return err + } + err = p.expect('e') + if err != nil { + return err + } + } else { + return &InvalidCharacter{r: r} + } + + p.builder.Boolean(p.accept()) return nil } diff --git a/toml_test.go b/toml_test.go index 0dd35ec4..7b2e30bb 100644 --- a/toml_test.go +++ b/toml_test.go @@ -15,15 +15,15 @@ func TestParse(t *testing.T) { `#`, "\n\n\n", "#one\n # two \n", - `a`, - `abc`, - ` abc # foo`, - `'abc'`, - `"foo bar"`, - `"hello\tworld"`, - `"hello \u1234 foo"`, - `a.b.c`, - `a."b".c`, + `a = false`, + `abc = false`, + ` abc = false # foo`, + `'abc' = false`, + `"foo bar" = false`, + `"hello\tworld" = false`, + `"hello \u1234 foo" = false`, + `a.b.c = false`, + `a."b".c = true`, } for i, data := range inputs { From b96c535061065ab90c9356c5c8fbf0095fa4eac0 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Mon, 1 Feb 2021 19:25:07 -0500 Subject: [PATCH 007/228] Check for allocs --- toml_test.go | 77 ++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 59 insertions(+), 18 deletions(-) diff --git a/toml_test.go b/toml_test.go index 7b2e30bb..bb26178f 100644 --- a/toml_test.go +++ b/toml_test.go @@ -7,25 +7,24 @@ import ( "github.com/stretchr/testify/assert" ) -func TestParse(t *testing.T) { - - inputs := []string{ - ` #foo`, - `#foo`, - `#`, - "\n\n\n", - "#one\n # two \n", - `a = false`, - `abc = false`, - ` abc = false # foo`, - `'abc' = false`, - `"foo bar" = false`, - `"hello\tworld" = false`, - `"hello \u1234 foo" = false`, - `a.b.c = false`, - `a."b".c = true`, - } +var inputs = []string{ + ` #foo`, + `#foo`, + `#`, + "\n\n\n", + "#one\n # two \n", + `a = false`, + `abc = false`, + ` abc = false # foo`, + `'abc' = false`, + `"foo bar" = false`, + `"hello\tworld" = false`, + `"hello \u1234 foo" = false`, + `a.b.c = false`, + `a."b".c = true`, +} +func TestParse(t *testing.T) { for i, data := range inputs { t.Run(fmt.Sprintf("example %d", i), func(t *testing.T) { fmt.Printf("input:\n\t`%s`\n", data) @@ -35,3 +34,45 @@ func TestParse(t *testing.T) { }) } } + +type noopBuilder struct { +} + +func (n noopBuilder) Whitespace(b []byte) { +} + +func (n noopBuilder) Comment(b []byte) { +} + +func (n noopBuilder) UnquotedKey(b []byte) { +} + +func (n noopBuilder) LiteralString(b []byte) { +} + +func (n noopBuilder) BasicString(b []byte) { +} + +func (n noopBuilder) Dot(b []byte) { +} + +func (n noopBuilder) Boolean(b []byte) { +} + +func (n noopBuilder) Equal(b []byte) { +} + +func BenchmarkParseAll(b *testing.B) { + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + for _, data := range inputs { + builder := noopBuilder{} + p := parser{builder: &builder, data: []byte(data)} + err := p.parse() + if err != nil { + b.Fatalf("error: %s", err) + } + } + } +} From 2ab0f8c733646150719dba25023fe550131f6e52 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Mon, 1 Feb 2021 20:20:24 -0500 Subject: [PATCH 008/228] Default to use bytes instead of runes benchmark old ns/op new ns/op delta BenchmarkParseAll-8 3238 1941 -40.06% --- toml.go | 152 +++++++++++++++++++++++++------------------------------- 1 file changed, 69 insertions(+), 83 deletions(-) diff --git a/toml.go b/toml.go index 6078d916..893cdeae 100644 --- a/toml.go +++ b/toml.go @@ -110,7 +110,33 @@ type parser struct { lookahead lookahead } -func (p *parser) peek() (rune, error) { +func (p *parser) peek() rune { + if p.end >= len(p.data) { + return eof + } + return rune(p.data[p.end]) +} + +func (p *parser) next() rune { + x := p.peek() + if x != eof { + p.end++ + } + return x +} + +func (p *parser) expect(expected rune) error { + r := p.next() + if r != expected { + return &UnexpectedCharacter{ + r: r, + expected: expected, + } + } + return nil +} + +func (p *parser) peekRune() (rune, error) { if p.lookahead.empty() { p.lookahead.r, p.lookahead.size = utf8.DecodeRune(p.data[p.end:]) if p.lookahead.r == utf8.RuneError { @@ -129,8 +155,8 @@ func (p *parser) peek() (rune, error) { return p.lookahead.r, nil } -func (p *parser) next() (rune, error) { - r, err := p.peek() +func (p *parser) nextRune() (rune, error) { + r, err := p.peekRune() if err == nil { p.end += p.lookahead.size p.lookahead.r = 0 @@ -139,8 +165,8 @@ func (p *parser) next() (rune, error) { return r, err } -func (p *parser) sureNext() { - _, err := p.next() +func (p *parser) sureNextRune() { + _, err := p.nextRune() if err != nil { panic(err) } @@ -162,8 +188,8 @@ func (p *parser) accept() []byte { return x } -func (p *parser) expect(expected rune) error { - r, err := p.next() +func (p *parser) expectRune(expected rune) error { + r, err := p.nextRune() if err != nil { return err } @@ -205,10 +231,7 @@ func (p *parser) parse() error { } // new lines between expressions - r, err := p.next() - if err != nil { - return err - } + r := p.next() switch r { case eof: return nil @@ -216,10 +239,7 @@ func (p *parser) parse() error { p.ignore() continue case '\r': - r, err = p.next() - if err != nil { - return err - } + r = p.next() if r == '\n' { p.ignore() continue @@ -235,10 +255,7 @@ func (p *parser) parseExpression() error { return err } - r, err := p.peek() - if err != nil { - return err - } + r := p.peek() // Line with just whitespace and a comment. We can exit early. if r == '#' { @@ -267,10 +284,7 @@ func (p *parser) parseExpression() error { return err } - r, err = p.peek() - if err != nil { - return err - } + r = p.peek() if r == '#' { return p.parseComment() } @@ -310,10 +324,7 @@ func (p *parser) parseVal() error { //val = string / boolean / array / inline-table / date-time / float / integer // string = ml-basic-string / basic-string / ml-literal-string / literal-string - r, err := p.peek() - if err != nil { - return err - } + r := p.peek() switch r { case 't', 'f': @@ -325,14 +336,11 @@ func (p *parser) parseVal() error { } func (p *parser) parseBool() error { - r, err := p.peek() - if err != nil { - return err - } + r := p.peek() if r == 't' { - p.sureNext() - err = p.expect('r') + p.next() + err := p.expect('r') if err != nil { return err } @@ -345,8 +353,8 @@ func (p *parser) parseBool() error { return err } } else if r == 'f' { - p.sureNext() - err = p.expect('a') + p.next() + err := p.expect('a') if err != nil { return err } @@ -386,16 +394,12 @@ func (p *parser) parseKey() error { return err } - r, err := p.peek() - if err != nil { - return err - } - + r := p.peek() if r != '.' { break } - p.sureNext() + p.next() p.builder.Dot(p.accept()) err = p.parseWhitespace() @@ -423,10 +427,7 @@ func (p *parser) parseSimpleKey() error { // basic-string = quotation-mark *basic-char quotation-mark // literal-string = apostrophe *literal-char apostrophe - r, err := p.peek() - if err != nil { - return err - } + r := p.peek() switch r { case '\'': @@ -439,24 +440,20 @@ func (p *parser) parseSimpleKey() error { } func (p *parser) parseUnquotedKey() error { - r, err := p.next() - if err != nil { - return err - } + // unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _ + + r := p.next() if !isUnquotedKeyRune(r) { return &InvalidCharacter{r: r} } for { - r, err := p.peek() - if err != nil { - return err - } + r := p.peek() if !isUnquotedKeyRune(r) { break } - p.sureNext() + p.next() } p.builder.UnquotedKey(p.accept()) return nil @@ -468,25 +465,17 @@ func (p *parser) parseComment() error { } for { - r, err := p.peek() - if err != nil { - return err - } + r := p.peek() if r == eof || r == '\n' { p.builder.Comment(p.accept()) return nil } - p.sureNext() + p.next() } } func isWhitespace(r rune) bool { - switch r { - case 0x20, 0x09: - return true - default: - return false - } + return r == 0x20 || r == 0x09 } type InvalidUnicodeError struct { @@ -499,12 +488,9 @@ func (e *InvalidUnicodeError) Error() string { func (p *parser) parseWhitespace() error { for { - r, err := p.peek() - if err != nil { - return err - } + r := p.peek() if isWhitespace(r) { - p.sureNext() + p.next() } else { if !p.empty() { p.builder.Whitespace(p.accept()) @@ -534,20 +520,20 @@ func (p *parser) parseLiteralString() error { p.ignore() for { - r, err := p.peek() + r, err := p.peekRune() if err != nil { return err } if r == '\'' { p.builder.LiteralString(p.accept()) - p.sureNext() + p.sureNextRune() p.ignore() return nil } if !isLiteralChar(r) { return &InvalidCharacter{r: r} } - p.sureNext() + p.sureNextRune() } } @@ -587,33 +573,33 @@ func (p *parser) parseBasicString() error { p.ignore() for { - r, err := p.peek() + r, err := p.peekRune() if err != nil { return err } if r == '"' { p.builder.BasicString(p.accept()) - p.sureNext() + p.sureNextRune() p.ignore() return nil } if r == '\\' { - p.sureNext() - r, err := p.peek() + p.sureNextRune() + r, err := p.peekRune() if err != nil { return err } if isEscapeChar(r) { - p.sureNext() + p.sureNextRune() continue } if r == 'u' { - p.sureNext() + p.sureNextRune() for i := 0; i < 4; i++ { - r, err := p.next() + r, err := p.nextRune() if err != nil { return err } @@ -625,9 +611,9 @@ func (p *parser) parseBasicString() error { } if r == 'U' { - p.sureNext() + p.sureNextRune() for i := 0; i < 8; i++ { - r, err := p.next() + r, err := p.nextRune() if err != nil { return err } @@ -642,7 +628,7 @@ func (p *parser) parseBasicString() error { } if isBasicStringChar(r) { - p.sureNext() + p.sureNextRune() continue } } From 7b4d82a939622220f18181f2fa076b8a3c219668 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Mon, 1 Feb 2021 20:25:31 -0500 Subject: [PATCH 009/228] Remove error handling for rune --- toml.go | 78 ++++++++++++++++----------------------------------------- 1 file changed, 22 insertions(+), 56 deletions(-) diff --git a/toml.go b/toml.go index 893cdeae..a03fa713 100644 --- a/toml.go +++ b/toml.go @@ -136,40 +136,24 @@ func (p *parser) expect(expected rune) error { return nil } -func (p *parser) peekRune() (rune, error) { +func (p *parser) peekRune() rune { if p.lookahead.empty() { p.lookahead.r, p.lookahead.size = utf8.DecodeRune(p.data[p.end:]) - if p.lookahead.r == utf8.RuneError { - - switch p.lookahead.size { - case 0: - p.lookahead.r = eof - case 1: - p.lookahead.r = utf8.RuneError - return utf8.RuneError, &InvalidUnicodeError{r: p.lookahead.r} - default: - panic("unhandled rune error case") - } + if p.lookahead.r == utf8.RuneError && p.lookahead.size == 0 { + p.lookahead.r = eof } } - return p.lookahead.r, nil + return p.lookahead.r } -func (p *parser) nextRune() (rune, error) { - r, err := p.peekRune() - if err == nil { +func (p *parser) nextRune() rune { + r := p.peekRune() + if r != eof { p.end += p.lookahead.size p.lookahead.r = 0 p.lookahead.size = 0 } - return r, err -} - -func (p *parser) sureNextRune() { - _, err := p.nextRune() - if err != nil { - panic(err) - } + return r } func (p *parser) ignore() { @@ -189,10 +173,7 @@ func (p *parser) accept() []byte { } func (p *parser) expectRune(expected rune) error { - r, err := p.nextRune() - if err != nil { - return err - } + r := p.nextRune() if r != expected { return &UnexpectedCharacter{ r: r, @@ -520,20 +501,17 @@ func (p *parser) parseLiteralString() error { p.ignore() for { - r, err := p.peekRune() - if err != nil { - return err - } + r := p.peekRune() if r == '\'' { p.builder.LiteralString(p.accept()) - p.sureNextRune() + p.nextRune() p.ignore() return nil } if !isLiteralChar(r) { return &InvalidCharacter{r: r} } - p.sureNextRune() + p.nextRune() } } @@ -573,36 +551,27 @@ func (p *parser) parseBasicString() error { p.ignore() for { - r, err := p.peekRune() - if err != nil { - return err - } + r := p.peekRune() if r == '"' { p.builder.BasicString(p.accept()) - p.sureNextRune() + p.nextRune() p.ignore() return nil } if r == '\\' { - p.sureNextRune() - r, err := p.peekRune() - if err != nil { - return err - } + p.nextRune() + r := p.peekRune() if isEscapeChar(r) { - p.sureNextRune() + p.nextRune() continue } if r == 'u' { - p.sureNextRune() + p.nextRune() for i := 0; i < 4; i++ { - r, err := p.nextRune() - if err != nil { - return err - } + r := p.nextRune() if !isHex(r) { return &InvalidCharacter{r: r} } @@ -611,12 +580,9 @@ func (p *parser) parseBasicString() error { } if r == 'U' { - p.sureNextRune() + p.nextRune() for i := 0; i < 8; i++ { - r, err := p.nextRune() - if err != nil { - return err - } + r := p.nextRune() if !isHex(r) { return &InvalidCharacter{r: r} } @@ -628,7 +594,7 @@ func (p *parser) parseBasicString() error { } if isBasicStringChar(r) { - p.sureNextRune() + p.nextRune() continue } } From 91d7afbc0a1a79f786707ecaf18c8c8e456a64b8 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Mon, 1 Feb 2021 20:54:04 -0500 Subject: [PATCH 010/228] Parse rvalue string --- toml.go | 42 +++++++++++++++++++++++++++++++++++++++--- toml_test.go | 2 ++ 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/toml.go b/toml.go index a03fa713..177e111e 100644 --- a/toml.go +++ b/toml.go @@ -110,11 +110,24 @@ type parser struct { lookahead lookahead } -func (p *parser) peek() rune { - if p.end >= len(p.data) { +func (p *parser) at(i int) rune { + if p.end+i >= len(p.data) { return eof } - return rune(p.data[p.end]) + return rune(p.data[p.end+i]) +} + +func (p *parser) follows(s string) bool { + for i := 0; i < len(s); i++ { + if rune(s[i]) != p.at(i) { + return false + } + } + return true +} + +func (p *parser) peek() rune { + return p.at(0) } func (p *parser) next() rune { @@ -310,11 +323,34 @@ func (p *parser) parseVal() error { switch r { case 't', 'f': return p.parseBool() + case '\'', '"': + return p.parseString() // TODO default: return &InvalidCharacter{r: r} } } +func (p *parser) parseString() error { + r := p.peek() + + if r == '\'' { + if p.follows("'''") { + // TODO ml-literal-string + panic("TODO") + } else { + return p.parseLiteralString() + } + } else if r == '"' { + if p.follows("\"\"\"") { + // TODO ml-basic-string + panic("TODO") + } else { + return p.parseBasicString() + } + } else { + panic("string should start with ' or \"") + } +} func (p *parser) parseBool() error { r := p.peek() diff --git a/toml_test.go b/toml_test.go index bb26178f..3491e6ae 100644 --- a/toml_test.go +++ b/toml_test.go @@ -22,6 +22,8 @@ var inputs = []string{ `"hello \u1234 foo" = false`, `a.b.c = false`, `a."b".c = true`, + `a = "foo"`, + `b = 'sample thingy'`, } func TestParse(t *testing.T) { From bac65cc5307206a2c32541be1628733851833f9b Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Mon, 1 Feb 2021 21:25:20 -0500 Subject: [PATCH 011/228] Array implementation --- toml.go | 124 +++++++++++++++++++++++++++++++++++++++++++++++++++ toml_test.go | 39 +++++++--------- 2 files changed, 140 insertions(+), 23 deletions(-) diff --git a/toml.go b/toml.go index 177e111e..1e9aec2d 100644 --- a/toml.go +++ b/toml.go @@ -27,6 +27,9 @@ type builder interface { Dot(b []byte) Boolean(b []byte) Equal(b []byte) + ArrayBegin() + ArrayEnd() + ArraySeparator() } type position struct { @@ -38,6 +41,18 @@ type documentBuilder struct { document Document } +func (d *documentBuilder) ArraySeparator() { + fmt.Println(", ARRAY SEPARATOR") +} + +func (d *documentBuilder) ArrayBegin() { + fmt.Println("[ ARRAY BEGIN") +} + +func (d *documentBuilder) ArrayEnd() { + fmt.Println("] ARRAY END") +} + func (d *documentBuilder) Equal(b []byte) { s := string(b) fmt.Printf("EQUAL: '%s'\n", s) @@ -243,6 +258,22 @@ func (p *parser) parse() error { } } +func (p *parser) parseRequiredNewline() error { + r := p.next() + switch r { + case '\n': + p.ignore() + return nil + case '\r': + r = p.next() + if r == '\n' { + p.ignore() + return nil + } + } + return &InvalidCharacter{r: r} +} + func (p *parser) parseExpression() error { err := p.parseWhitespace() if err != nil { @@ -325,11 +356,104 @@ func (p *parser) parseVal() error { return p.parseBool() case '\'', '"': return p.parseString() + case '[': + return p.parseArray() // TODO default: return &InvalidCharacter{r: r} } } + +func (p *parser) parseArray() error { + //array = array-open [ array-values ] ws-comment-newline array-close + + err := p.expect('[') + if err != nil { + panic("arrays should start with [") + } + + p.builder.ArrayBegin() + + err = p.parseWhitespaceCommentNewline() + if err != nil { + return err + } + + r := p.peek() + + if r == ']' { + p.next() + p.ignore() + p.builder.ArrayEnd() + return nil + } + + err = p.parseVal() + if err != nil { + return err + } + + for { + err = p.parseWhitespaceCommentNewline() + if err != nil { + return err + } + + r := p.peek() + + if r == ']' { + p.next() + p.ignore() + p.builder.ArrayEnd() + return nil + } + + err := p.expect(',') + if err != nil { + return err + } + p.builder.ArraySeparator() + p.ignore() + + err = p.parseWhitespaceCommentNewline() + if err != nil { + return err + } + + err = p.parseVal() + if err != nil { + return err + } + } +} + +func (p *parser) parseWhitespaceCommentNewline() error { + // ws-comment-newline = *( wschar / ([ comment ] newline) ) + + for { + if isWhitespace(p.peek()) { + err := p.parseWhitespace() + if err != nil { + return err + } + } + if p.peek() == '#' { + err := p.parseComment() + if err != nil { + return err + } + } + r := p.peek() + if r != '\n' && r != '\r' { + return nil + } + err := p.parseRequiredNewline() + if err != nil { + return err + } + } +} + func (p *parser) parseString() error { r := p.peek() diff --git a/toml_test.go b/toml_test.go index 3491e6ae..1563fcee 100644 --- a/toml_test.go +++ b/toml_test.go @@ -24,6 +24,11 @@ var inputs = []string{ `a."b".c = true`, `a = "foo"`, `b = 'sample thingy'`, + `a = []`, + `b = ["foo"]`, + `c = [[[]]]`, + `d = ["foo","bar"]`, + `d = ["foo", "test"]`, } func TestParse(t *testing.T) { @@ -40,29 +45,17 @@ func TestParse(t *testing.T) { type noopBuilder struct { } -func (n noopBuilder) Whitespace(b []byte) { -} - -func (n noopBuilder) Comment(b []byte) { -} - -func (n noopBuilder) UnquotedKey(b []byte) { -} - -func (n noopBuilder) LiteralString(b []byte) { -} - -func (n noopBuilder) BasicString(b []byte) { -} - -func (n noopBuilder) Dot(b []byte) { -} - -func (n noopBuilder) Boolean(b []byte) { -} - -func (n noopBuilder) Equal(b []byte) { -} +func (n noopBuilder) ArraySeparator() {} +func (n noopBuilder) ArrayBegin() {} +func (n noopBuilder) ArrayEnd() {} +func (n noopBuilder) Whitespace(b []byte) {} +func (n noopBuilder) Comment(b []byte) {} +func (n noopBuilder) UnquotedKey(b []byte) {} +func (n noopBuilder) LiteralString(b []byte) {} +func (n noopBuilder) BasicString(b []byte) {} +func (n noopBuilder) Dot(b []byte) {} +func (n noopBuilder) Boolean(b []byte) {} +func (n noopBuilder) Equal(b []byte) {} func BenchmarkParseAll(b *testing.B) { b.ReportAllocs() From 44f7a7aead83e3d5e0701c77d13432e2be9b0e7a Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Mon, 1 Feb 2021 21:41:34 -0500 Subject: [PATCH 012/228] Inline tables --- toml.go | 85 ++++++++++++++++++++++++++++++++++++++++++++++++++++ toml_test.go | 5 ++++ 2 files changed, 90 insertions(+) diff --git a/toml.go b/toml.go index 1e9aec2d..ea6db529 100644 --- a/toml.go +++ b/toml.go @@ -30,6 +30,9 @@ type builder interface { ArrayBegin() ArrayEnd() ArraySeparator() + InlineTableBegin() + InlineTableEnd() + InlineTableSeparator() } type position struct { @@ -41,6 +44,18 @@ type documentBuilder struct { document Document } +func (d *documentBuilder) InlineTableSeparator() { + fmt.Println(", InlineTable SEPARATOR") +} + +func (d *documentBuilder) InlineTableBegin() { + fmt.Println("{ InlineTable BEGIN") +} + +func (d *documentBuilder) InlineTableEnd() { + fmt.Println("} InlineTable END") +} + func (d *documentBuilder) ArraySeparator() { fmt.Println(", ARRAY SEPARATOR") } @@ -358,12 +373,81 @@ func (p *parser) parseVal() error { return p.parseString() case '[': return p.parseArray() + case '{': + return p.parseInlineTable() // TODO default: return &InvalidCharacter{r: r} } } +func (p *parser) parseInlineTable() error { + //inline-table = inline-table-open [ inline-table-keyvals ] inline-table-close + // + //inline-table-open = %x7B ws ; { + // inline-table-close = ws %x7D ; } + //inline-table-sep = ws %x2C ws ; , Comma + // + //inline-table-keyvals = keyval [ inline-table-sep inline-table-keyvals ] + + err := p.expect('{') + if err != nil { + panic("inline tables should start with {") + } + p.ignore() + p.builder.InlineTableBegin() + + err = p.parseWhitespace() + if err != nil { + return err + } + + r := p.peek() + if r == '}' { + p.next() + p.ignore() + p.builder.InlineTableEnd() + return nil + } + + err = p.parseKeyval() + if err != nil { + return err + } + + for { + err = p.parseWhitespace() + if err != nil { + return err + } + + r := p.peek() + if r == '}' { + p.next() + p.ignore() + p.builder.InlineTableEnd() + return nil + } + + err := p.expect(',') + if err != nil { + return err + } + p.builder.InlineTableSeparator() + p.ignore() + + err = p.parseWhitespace() + if err != nil { + return err + } + + err = p.parseKeyval() + if err != nil { + return err + } + } +} + func (p *parser) parseArray() error { //array = array-open [ array-values ] ws-comment-newline array-close @@ -371,6 +455,7 @@ func (p *parser) parseArray() error { if err != nil { panic("arrays should start with [") } + p.ignore() p.builder.ArrayBegin() diff --git a/toml_test.go b/toml_test.go index 1563fcee..3bddd1ef 100644 --- a/toml_test.go +++ b/toml_test.go @@ -29,6 +29,8 @@ var inputs = []string{ `c = [[[]]]`, `d = ["foo","bar"]`, `d = ["foo", "test"]`, + `d = {}`, + `e = {f = "bar"}`, } func TestParse(t *testing.T) { @@ -45,6 +47,9 @@ func TestParse(t *testing.T) { type noopBuilder struct { } +func (n noopBuilder) InlineTableSeparator() {} +func (n noopBuilder) InlineTableBegin() {} +func (n noopBuilder) InlineTableEnd() {} func (n noopBuilder) ArraySeparator() {} func (n noopBuilder) ArrayBegin() {} func (n noopBuilder) ArrayEnd() {} From aae4656c6462e877e940561e9f5704d6b97bf7d5 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Mon, 1 Feb 2021 22:03:53 -0500 Subject: [PATCH 013/228] Standard Table --- toml.go | 89 ++++++++++++++++++++++++++++++++++++++++++++++++---- toml_test.go | 7 +++++ 2 files changed, 90 insertions(+), 6 deletions(-) diff --git a/toml.go b/toml.go index ea6db529..07b89f52 100644 --- a/toml.go +++ b/toml.go @@ -33,6 +33,8 @@ type builder interface { InlineTableBegin() InlineTableEnd() InlineTableSeparator() + StandardTableBegin() + StandardTableEnd() } type position struct { @@ -44,6 +46,14 @@ type documentBuilder struct { document Document } +func (d *documentBuilder) StandardTableBegin() { + fmt.Println("STD-TABLE[") +} + +func (d *documentBuilder) StandardTableEnd() { + fmt.Println("STD-TABLE]") +} + func (d *documentBuilder) InlineTableSeparator() { fmt.Println(", InlineTable SEPARATOR") } @@ -290,6 +300,10 @@ func (p *parser) parseRequiredNewline() error { } func (p *parser) parseExpression() error { + //expression = ws [ comment ] + //expression =/ ws keyval ws [ comment ] + //expression =/ ws table ws [ comment ] + err := p.parseWhitespace() if err != nil { return err @@ -305,12 +319,11 @@ func (p *parser) parseExpression() error { // or line with something? if r == '[' { // parse table. could be either a standard table or an array table - // TODO - } - - // it has to be a keyval - - if isUnquotedKeyRune(r) || r == '\'' || r == '"' { + err := p.parseTable() + if err != nil { + return err + } + } else if isUnquotedKeyRune(r) || r == '\'' || r == '"' { err := p.parseKeyval() if err != nil { return err @@ -844,3 +857,67 @@ func (p *parser) parseBasicString() error { } } } + +func (p *parser) parseTable() error { + //;; Table + // + //table = std-table / array-table + // + //;; Standard Table + // + //std-table = std-table-open key std-table-close + // + //std-table-open = %x5B ws ; [ Left square bracket + //std-table-close = ws %x5D ; ] Right square bracket + // + //;; Array Table + // + //array-table = array-table-open key array-table-close + // + //array-table-open = %x5B.5B ws ; [[ Double left square bracket + //array-table-close = ws %x5D.5D ; ]] Double right square bracket + + if p.follows("[[") { + panic("TODO") // TODO: array-table + } + + return p.parseStandardTable() +} + +func (p *parser) parseStandardTable() error { + //;; Standard Table + // + //std-table = std-table-open key std-table-close + // + //std-table-open = %x5B ws ; [ Left square bracket + //std-table-close = ws %x5D ; ] Right square bracket + + err := p.expect('[') + if err != nil { + panic("std-table should start with [") + } + p.ignore() + p.builder.StandardTableBegin() + + err = p.parseWhitespace() + if err != nil { + return err + } + + err = p.parseKey() + if err != nil { + return err + } + + err = p.parseWhitespace() + if err != nil { + return err + } + err = p.expect(']') + if err != nil { + return err + } + p.ignore() + p.builder.StandardTableEnd() + return nil +} diff --git a/toml_test.go b/toml_test.go index 3bddd1ef..168573b9 100644 --- a/toml_test.go +++ b/toml_test.go @@ -31,6 +31,11 @@ var inputs = []string{ `d = ["foo", "test"]`, `d = {}`, `e = {f = "bar"}`, + `[foo]`, + `[ test ]`, + `[ "hello".world ]`, + `[test] +a = false`, } func TestParse(t *testing.T) { @@ -47,6 +52,8 @@ func TestParse(t *testing.T) { type noopBuilder struct { } +func (n noopBuilder) StandardTableBegin() {} +func (n noopBuilder) StandardTableEnd() {} func (n noopBuilder) InlineTableSeparator() {} func (n noopBuilder) InlineTableBegin() {} func (n noopBuilder) InlineTableEnd() {} From 7300b6a97b2194b47f60473c0cce0b544a28095f Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Tue, 2 Feb 2021 08:19:04 -0500 Subject: [PATCH 014/228] Array tables --- toml.go | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++- toml_test.go | 3 +++ 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/toml.go b/toml.go index 07b89f52..30f11a6c 100644 --- a/toml.go +++ b/toml.go @@ -35,6 +35,8 @@ type builder interface { InlineTableSeparator() StandardTableBegin() StandardTableEnd() + ArrayTableBegin() + ArrayTableEnd() } type position struct { @@ -46,6 +48,14 @@ type documentBuilder struct { document Document } +func (d *documentBuilder) ArrayTableBegin() { + fmt.Println("ARRAY-TABLE[[") +} + +func (d *documentBuilder) ArrayTableEnd() { + fmt.Println("ARRAY-TABLE]]") +} + func (d *documentBuilder) StandardTableBegin() { fmt.Println("STD-TABLE[") } @@ -878,12 +888,57 @@ func (p *parser) parseTable() error { //array-table-close = ws %x5D.5D ; ]] Double right square bracket if p.follows("[[") { - panic("TODO") // TODO: array-table + return p.parseArrayTable() } return p.parseStandardTable() } +func (p *parser) parseArrayTable() error { + //;; Array Table + // + //array-table = array-table-open key array-table-close + // + //array-table-open = %x5B.5B ws ; [[ Double left square bracket + //array-table-close = ws %x5D.5D ; ]] Double right square bracket + err := p.expect('[') + if err != nil { + return err + } + err = p.expect('[') + if err != nil { + return err + } + p.ignore() + p.builder.ArrayTableBegin() + + err = p.parseWhitespace() + if err != nil { + return err + } + + err = p.parseKey() + if err != nil { + return err + } + + err = p.parseWhitespace() + if err != nil { + return err + } + err = p.expect(']') + if err != nil { + return err + } + err = p.expect(']') + if err != nil { + return err + } + p.ignore() + p.builder.ArrayTableEnd() + return nil +} + func (p *parser) parseStandardTable() error { //;; Standard Table // diff --git a/toml_test.go b/toml_test.go index 168573b9..b27ebf07 100644 --- a/toml_test.go +++ b/toml_test.go @@ -36,6 +36,7 @@ var inputs = []string{ `[ "hello".world ]`, `[test] a = false`, + `[[foo]]`, } func TestParse(t *testing.T) { @@ -52,6 +53,8 @@ func TestParse(t *testing.T) { type noopBuilder struct { } +func (n noopBuilder) ArrayTableBegin() {} +func (n noopBuilder) ArrayTableEnd() {} func (n noopBuilder) StandardTableBegin() {} func (n noopBuilder) StandardTableEnd() {} func (n noopBuilder) InlineTableSeparator() {} From 1e8b0dc3c9afcf878820ea0f790a9fad1a0cedec Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Tue, 2 Feb 2021 08:28:30 -0500 Subject: [PATCH 015/228] Rename to lexer and split in files --- document.go | 100 ++++++++++ encoding.go | 11 ++ lexer.go | 1 + parser.go | 22 +++ toml.abnf | 1 - toml.go | 550 ++++++++++++++++++++------------------------------- toml_test.go | 44 ++--- 7 files changed, 367 insertions(+), 362 deletions(-) create mode 100644 document.go create mode 100644 encoding.go create mode 100644 lexer.go create mode 100644 parser.go diff --git a/document.go b/document.go new file mode 100644 index 00000000..0d4522c9 --- /dev/null +++ b/document.go @@ -0,0 +1,100 @@ +package toml + +import "fmt" + +type Document struct { +} + +type docParser struct { + document Document +} + +func (d *docParser) ArrayTableBegin() { + fmt.Println("ARRAY-TABLE[[") +} + +func (d *docParser) ArrayTableEnd() { + fmt.Println("ARRAY-TABLE]]") +} + +func (d *docParser) StandardTableBegin() { + fmt.Println("STD-TABLE[") +} + +func (d *docParser) StandardTableEnd() { + fmt.Println("STD-TABLE]") +} + +func (d *docParser) InlineTableSeparator() { + fmt.Println(", InlineTable SEPARATOR") +} + +func (d *docParser) InlineTableBegin() { + fmt.Println("{ InlineTable BEGIN") +} + +func (d *docParser) InlineTableEnd() { + fmt.Println("} InlineTable END") +} + +func (d *docParser) ArraySeparator() { + fmt.Println(", ARRAY SEPARATOR") +} + +func (d *docParser) ArrayBegin() { + fmt.Println("[ ARRAY BEGIN") +} + +func (d *docParser) ArrayEnd() { + fmt.Println("] ARRAY END") +} + +func (d *docParser) Equal(b []byte) { + s := string(b) + fmt.Printf("EQUAL: '%s'\n", s) +} + +func (d *docParser) Boolean(b []byte) { + s := string(b) + fmt.Printf("Boolean: '%s'\n", s) +} + +func (d *docParser) Dot(b []byte) { + s := string(b) + fmt.Printf("DOT: '%s'\n", s) +} + +func (d *docParser) BasicString(b []byte) { + s := string(b) + fmt.Printf("BasicString: '%s'\n", s) +} + +func (d *docParser) LiteralString(b []byte) { + s := string(b) + fmt.Printf("LiteralString: '%s'\n", s) +} + +func (d *docParser) UnquotedKey(b []byte) { + s := string(b) + fmt.Printf("UnquotedKey: '%s'\n", s) +} + +func (d *docParser) Comment(b []byte) { + s := string(b) + fmt.Printf("Comment: '%s'\n", s) +} + +func (d *docParser) Whitespace(b []byte) { + s := string(b) + fmt.Printf("Whitespace: '%s'\n", s) +} + +func Parse(b []byte) (Document, error) { + p := docParser{} + l := lexer{parser: &p, data: b} + err := l.run() + if err != nil { + return Document{}, err + } + return p.document, nil +} diff --git a/encoding.go b/encoding.go new file mode 100644 index 00000000..5b9b969f --- /dev/null +++ b/encoding.go @@ -0,0 +1,11 @@ +package toml + +func Unmarshal(data []byte, v interface{}) error { + // TODO + return nil +} + +func Marshal(v interface{}) ([]byte, error) { + // TODO + return nil, nil +} diff --git a/lexer.go b/lexer.go new file mode 100644 index 00000000..f9fa173c --- /dev/null +++ b/lexer.go @@ -0,0 +1 @@ +package toml diff --git a/parser.go b/parser.go new file mode 100644 index 00000000..3f1fed74 --- /dev/null +++ b/parser.go @@ -0,0 +1,22 @@ +package toml + +type parser interface { + Whitespace(b []byte) + Comment(b []byte) + UnquotedKey(b []byte) + LiteralString(b []byte) + BasicString(b []byte) + Dot(b []byte) + Boolean(b []byte) + Equal(b []byte) + ArrayBegin() + ArrayEnd() + ArraySeparator() + InlineTableBegin() + InlineTableEnd() + InlineTableSeparator() + StandardTableBegin() + StandardTableEnd() + ArrayTableBegin() + ArrayTableEnd() +} diff --git a/toml.abnf b/toml.abnf index 473f3749..5655ed55 100644 --- a/toml.abnf +++ b/toml.abnf @@ -59,7 +59,6 @@ val = string / boolean / array / inline-table / date-time / float / integer ;; String string = ml-basic-string / basic-string / ml-literal-string / literal-string - ;; Basic String basic-string = quotation-mark *basic-char quotation-mark diff --git a/toml.go b/toml.go index 30f11a6c..e66c9ee4 100644 --- a/toml.go +++ b/toml.go @@ -5,139 +5,11 @@ import ( "unicode/utf8" ) -func Unmarshal(data []byte, v interface{}) error { - // TODO - return nil -} - -func Marshal(v interface{}) ([]byte, error) { - // TODO - return nil, nil -} - -type Document struct { -} - -type builder interface { - Whitespace(b []byte) - Comment(b []byte) - UnquotedKey(b []byte) - LiteralString(b []byte) - BasicString(b []byte) - Dot(b []byte) - Boolean(b []byte) - Equal(b []byte) - ArrayBegin() - ArrayEnd() - ArraySeparator() - InlineTableBegin() - InlineTableEnd() - InlineTableSeparator() - StandardTableBegin() - StandardTableEnd() - ArrayTableBegin() - ArrayTableEnd() -} - type position struct { line int column int } -type documentBuilder struct { - document Document -} - -func (d *documentBuilder) ArrayTableBegin() { - fmt.Println("ARRAY-TABLE[[") -} - -func (d *documentBuilder) ArrayTableEnd() { - fmt.Println("ARRAY-TABLE]]") -} - -func (d *documentBuilder) StandardTableBegin() { - fmt.Println("STD-TABLE[") -} - -func (d *documentBuilder) StandardTableEnd() { - fmt.Println("STD-TABLE]") -} - -func (d *documentBuilder) InlineTableSeparator() { - fmt.Println(", InlineTable SEPARATOR") -} - -func (d *documentBuilder) InlineTableBegin() { - fmt.Println("{ InlineTable BEGIN") -} - -func (d *documentBuilder) InlineTableEnd() { - fmt.Println("} InlineTable END") -} - -func (d *documentBuilder) ArraySeparator() { - fmt.Println(", ARRAY SEPARATOR") -} - -func (d *documentBuilder) ArrayBegin() { - fmt.Println("[ ARRAY BEGIN") -} - -func (d *documentBuilder) ArrayEnd() { - fmt.Println("] ARRAY END") -} - -func (d *documentBuilder) Equal(b []byte) { - s := string(b) - fmt.Printf("EQUAL: '%s'\n", s) -} - -func (d *documentBuilder) Boolean(b []byte) { - s := string(b) - fmt.Printf("Boolean: '%s'\n", s) -} - -func (d *documentBuilder) Dot(b []byte) { - s := string(b) - fmt.Printf("DOT: '%s'\n", s) -} - -func (d *documentBuilder) BasicString(b []byte) { - s := string(b) - fmt.Printf("BasicString: '%s'\n", s) -} - -func (d *documentBuilder) LiteralString(b []byte) { - s := string(b) - fmt.Printf("LiteralString: '%s'\n", s) -} - -func (d *documentBuilder) UnquotedKey(b []byte) { - s := string(b) - fmt.Printf("UnquotedKey: '%s'\n", s) -} - -func (d *documentBuilder) Comment(b []byte) { - s := string(b) - fmt.Printf("Comment: '%s'\n", s) -} - -func (d *documentBuilder) Whitespace(b []byte) { - s := string(b) - fmt.Printf("Whitespace: '%s'\n", s) -} - -func Parse(b []byte) (Document, error) { - builder := documentBuilder{} - p := parser{builder: &builder, data: b} - err := p.parse() - if err != nil { - return Document{}, err - } - return builder.document, nil -} - // eof is a rune value indicating end-of-file. const eof = -1 @@ -150,8 +22,8 @@ func (l lookahead) empty() bool { return l.r == 0 } -type parser struct { - builder builder +type lexer struct { + parser parser data []byte start int @@ -160,36 +32,36 @@ type parser struct { lookahead lookahead } -func (p *parser) at(i int) rune { - if p.end+i >= len(p.data) { +func (l *lexer) at(i int) rune { + if l.end+i >= len(l.data) { return eof } - return rune(p.data[p.end+i]) + return rune(l.data[l.end+i]) } -func (p *parser) follows(s string) bool { +func (l *lexer) follows(s string) bool { for i := 0; i < len(s); i++ { - if rune(s[i]) != p.at(i) { + if rune(s[i]) != l.at(i) { return false } } return true } -func (p *parser) peek() rune { - return p.at(0) +func (l *lexer) peek() rune { + return l.at(0) } -func (p *parser) next() rune { - x := p.peek() +func (l *lexer) next() rune { + x := l.peek() if x != eof { - p.end++ + l.end++ } return x } -func (p *parser) expect(expected rune) error { - r := p.next() +func (l *lexer) expect(expected rune) error { + r := l.next() if r != expected { return &UnexpectedCharacter{ r: r, @@ -199,44 +71,44 @@ func (p *parser) expect(expected rune) error { return nil } -func (p *parser) peekRune() rune { - if p.lookahead.empty() { - p.lookahead.r, p.lookahead.size = utf8.DecodeRune(p.data[p.end:]) - if p.lookahead.r == utf8.RuneError && p.lookahead.size == 0 { - p.lookahead.r = eof +func (l *lexer) peekRune() rune { + if l.lookahead.empty() { + l.lookahead.r, l.lookahead.size = utf8.DecodeRune(l.data[l.end:]) + if l.lookahead.r == utf8.RuneError && l.lookahead.size == 0 { + l.lookahead.r = eof } } - return p.lookahead.r + return l.lookahead.r } -func (p *parser) nextRune() rune { - r := p.peekRune() +func (l *lexer) nextRune() rune { + r := l.peekRune() if r != eof { - p.end += p.lookahead.size - p.lookahead.r = 0 - p.lookahead.size = 0 + l.end += l.lookahead.size + l.lookahead.r = 0 + l.lookahead.size = 0 } return r } -func (p *parser) ignore() { - if p.empty() { +func (l *lexer) ignore() { + if l.empty() { panic("cannot ignore empty token") } - p.start = p.end + l.start = l.end } -func (p *parser) accept() []byte { - if p.empty() { +func (l *lexer) accept() []byte { + if l.empty() { panic("cannot accept empty token") } - x := p.data[p.start:p.end] - p.start = p.end + x := l.data[l.start:l.end] + l.start = l.end return x } -func (p *parser) expectRune(expected rune) error { - r := p.nextRune() +func (l *lexer) expectRune(expected rune) error { + r := l.nextRune() if r != expected { return &UnexpectedCharacter{ r: r, @@ -246,8 +118,8 @@ func (p *parser) expectRune(expected rune) error { return nil } -func (p *parser) empty() bool { - return p.start == p.end +func (l *lexer) empty() bool { + return l.start == l.end } type InvalidCharacter struct { @@ -267,25 +139,25 @@ func (e *UnexpectedCharacter) Error() string { return fmt.Sprintf("expected character '%#U' but got '%#U'", e.expected, e.r) } -func (p *parser) parse() error { +func (l *lexer) run() error { for { - err := p.parseExpression() + err := l.lexExpression() if err != nil { return err } // new lines between expressions - r := p.next() + r := l.next() switch r { case eof: return nil case '\n': - p.ignore() + l.ignore() continue case '\r': - r = p.next() + r = l.next() if r == '\n' { - p.ignore() + l.ignore() continue } } @@ -293,48 +165,48 @@ func (p *parser) parse() error { } } -func (p *parser) parseRequiredNewline() error { - r := p.next() +func (l *lexer) lexRequiredNewline() error { + r := l.next() switch r { case '\n': - p.ignore() + l.ignore() return nil case '\r': - r = p.next() + r = l.next() if r == '\n' { - p.ignore() + l.ignore() return nil } } return &InvalidCharacter{r: r} } -func (p *parser) parseExpression() error { +func (l *lexer) lexExpression() error { //expression = ws [ comment ] //expression =/ ws keyval ws [ comment ] //expression =/ ws table ws [ comment ] - err := p.parseWhitespace() + err := l.lexWhitespace() if err != nil { return err } - r := p.peek() + r := l.peek() // Line with just whitespace and a comment. We can exit early. if r == '#' { - return p.parseComment() + return l.lexComment() } // or line with something? if r == '[' { // parse table. could be either a standard table or an array table - err := p.parseTable() + err := l.lexTable() if err != nil { return err } } else if isUnquotedKeyRune(r) || r == '\'' || r == '"' { - err := p.parseKeyval() + err := l.lexKeyval() if err != nil { return err } @@ -342,69 +214,69 @@ func (p *parser) parseExpression() error { // parse trailing whitespace and comment - err = p.parseWhitespace() + err = l.lexWhitespace() if err != nil { return err } - r = p.peek() + r = l.peek() if r == '#' { - return p.parseComment() + return l.lexComment() } return nil } -func (p *parser) parseKeyval() error { +func (l *lexer) lexKeyval() error { // key keyval-sep val //keyval-sep = ws %x3D ws ; = - err := p.parseKey() + err := l.lexKey() if err != nil { return err } - err = p.parseWhitespace() + err = l.lexWhitespace() if err != nil { return err } - err = p.expect('=') + err = l.expect('=') if err != nil { return err } - p.builder.Equal(p.accept()) + l.parser.Equal(l.accept()) - err = p.parseWhitespace() + err = l.lexWhitespace() if err != nil { return err } - return p.parseVal() + return l.lexVal() } -func (p *parser) parseVal() error { +func (l *lexer) lexVal() error { //val = string / boolean / array / inline-table / date-time / float / integer // string = ml-basic-string / basic-string / ml-literal-string / literal-string - r := p.peek() + r := l.peek() switch r { case 't', 'f': - return p.parseBool() + return l.lexBool() case '\'', '"': - return p.parseString() + return l.lexString() case '[': - return p.parseArray() + return l.lexArray() case '{': - return p.parseInlineTable() + return l.lexInlineTable() // TODO default: return &InvalidCharacter{r: r} } } -func (p *parser) parseInlineTable() error { +func (l *lexer) lexInlineTable() error { //inline-table = inline-table-open [ inline-table-keyvals ] inline-table-close // //inline-table-open = %x7B ws ; { @@ -413,209 +285,209 @@ func (p *parser) parseInlineTable() error { // //inline-table-keyvals = keyval [ inline-table-sep inline-table-keyvals ] - err := p.expect('{') + err := l.expect('{') if err != nil { panic("inline tables should start with {") } - p.ignore() - p.builder.InlineTableBegin() + l.ignore() + l.parser.InlineTableBegin() - err = p.parseWhitespace() + err = l.lexWhitespace() if err != nil { return err } - r := p.peek() + r := l.peek() if r == '}' { - p.next() - p.ignore() - p.builder.InlineTableEnd() + l.next() + l.ignore() + l.parser.InlineTableEnd() return nil } - err = p.parseKeyval() + err = l.lexKeyval() if err != nil { return err } for { - err = p.parseWhitespace() + err = l.lexWhitespace() if err != nil { return err } - r := p.peek() + r := l.peek() if r == '}' { - p.next() - p.ignore() - p.builder.InlineTableEnd() + l.next() + l.ignore() + l.parser.InlineTableEnd() return nil } - err := p.expect(',') + err := l.expect(',') if err != nil { return err } - p.builder.InlineTableSeparator() - p.ignore() + l.parser.InlineTableSeparator() + l.ignore() - err = p.parseWhitespace() + err = l.lexWhitespace() if err != nil { return err } - err = p.parseKeyval() + err = l.lexKeyval() if err != nil { return err } } } -func (p *parser) parseArray() error { +func (l *lexer) lexArray() error { //array = array-open [ array-values ] ws-comment-newline array-close - err := p.expect('[') + err := l.expect('[') if err != nil { panic("arrays should start with [") } - p.ignore() + l.ignore() - p.builder.ArrayBegin() + l.parser.ArrayBegin() - err = p.parseWhitespaceCommentNewline() + err = l.lexWhitespaceCommentNewline() if err != nil { return err } - r := p.peek() + r := l.peek() if r == ']' { - p.next() - p.ignore() - p.builder.ArrayEnd() + l.next() + l.ignore() + l.parser.ArrayEnd() return nil } - err = p.parseVal() + err = l.lexVal() if err != nil { return err } for { - err = p.parseWhitespaceCommentNewline() + err = l.lexWhitespaceCommentNewline() if err != nil { return err } - r := p.peek() + r := l.peek() if r == ']' { - p.next() - p.ignore() - p.builder.ArrayEnd() + l.next() + l.ignore() + l.parser.ArrayEnd() return nil } - err := p.expect(',') + err := l.expect(',') if err != nil { return err } - p.builder.ArraySeparator() - p.ignore() + l.parser.ArraySeparator() + l.ignore() - err = p.parseWhitespaceCommentNewline() + err = l.lexWhitespaceCommentNewline() if err != nil { return err } - err = p.parseVal() + err = l.lexVal() if err != nil { return err } } } -func (p *parser) parseWhitespaceCommentNewline() error { +func (l *lexer) lexWhitespaceCommentNewline() error { // ws-comment-newline = *( wschar / ([ comment ] newline) ) for { - if isWhitespace(p.peek()) { - err := p.parseWhitespace() + if isWhitespace(l.peek()) { + err := l.lexWhitespace() if err != nil { return err } } - if p.peek() == '#' { - err := p.parseComment() + if l.peek() == '#' { + err := l.lexComment() if err != nil { return err } } - r := p.peek() + r := l.peek() if r != '\n' && r != '\r' { return nil } - err := p.parseRequiredNewline() + err := l.lexRequiredNewline() if err != nil { return err } } } -func (p *parser) parseString() error { - r := p.peek() +func (l *lexer) lexString() error { + r := l.peek() if r == '\'' { - if p.follows("'''") { + if l.follows("'''") { // TODO ml-literal-string panic("TODO") } else { - return p.parseLiteralString() + return l.lexLiteralString() } } else if r == '"' { - if p.follows("\"\"\"") { + if l.follows("\"\"\"") { // TODO ml-basic-string panic("TODO") } else { - return p.parseBasicString() + return l.lexBasicString() } } else { panic("string should start with ' or \"") } } -func (p *parser) parseBool() error { - r := p.peek() +func (l *lexer) lexBool() error { + r := l.peek() if r == 't' { - p.next() - err := p.expect('r') + l.next() + err := l.expect('r') if err != nil { return err } - err = p.expect('u') + err = l.expect('u') if err != nil { return err } - err = p.expect('e') + err = l.expect('e') if err != nil { return err } } else if r == 'f' { - p.next() - err := p.expect('a') + l.next() + err := l.expect('a') if err != nil { return err } - err = p.expect('l') + err = l.expect('l') if err != nil { return err } - err = p.expect('s') + err = l.expect('s') if err != nil { return err } - err = p.expect('e') + err = l.expect('e') if err != nil { return err } @@ -623,41 +495,41 @@ func (p *parser) parseBool() error { return &InvalidCharacter{r: r} } - p.builder.Boolean(p.accept()) + l.parser.Boolean(l.accept()) return nil } -func (p *parser) parseKey() error { +func (l *lexer) lexKey() error { // simple-key / dotted-key // dotted-key = simple-key 1*( dot-sep simple-key ) // dot-sep = ws %x2E ws for { - err := p.parseSimpleKey() + err := l.lexSimpleKey() if err != nil { return err } - err = p.parseWhitespace() + err = l.lexWhitespace() if err != nil { return err } - r := p.peek() + r := l.peek() if r != '.' { break } - p.next() - p.builder.Dot(p.accept()) + l.next() + l.parser.Dot(l.accept()) - err = p.parseWhitespace() + err = l.lexWhitespace() if err != nil { return err } } - err := p.parseWhitespace() + err := l.lexWhitespace() if err != nil { return err } @@ -669,57 +541,57 @@ func isUnquotedKeyRune(r rune) bool { return (r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '-' || r == '_' } -func (p *parser) parseSimpleKey() error { +func (l *lexer) lexSimpleKey() error { // simple-key = quoted-key / unquoted-key // quoted-key = basic-string / literal-string // unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _ // basic-string = quotation-mark *basic-char quotation-mark // literal-string = apostrophe *literal-char apostrophe - r := p.peek() + r := l.peek() switch r { case '\'': - return p.parseLiteralString() + return l.lexLiteralString() case '"': - return p.parseBasicString() + return l.lexBasicString() default: - return p.parseUnquotedKey() + return l.lexUnquotedKey() } } -func (p *parser) parseUnquotedKey() error { +func (l *lexer) lexUnquotedKey() error { // unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _ - r := p.next() + r := l.next() if !isUnquotedKeyRune(r) { return &InvalidCharacter{r: r} } for { - r := p.peek() + r := l.peek() if !isUnquotedKeyRune(r) { break } - p.next() + l.next() } - p.builder.UnquotedKey(p.accept()) + l.parser.UnquotedKey(l.accept()) return nil } -func (p *parser) parseComment() error { - if err := p.expect('#'); err != nil { +func (l *lexer) lexComment() error { + if err := l.expect('#'); err != nil { return err } for { - r := p.peek() + r := l.peek() if r == eof || r == '\n' { - p.builder.Comment(p.accept()) + l.parser.Comment(l.accept()) return nil } - p.next() + l.next() } } @@ -735,14 +607,14 @@ func (e *InvalidUnicodeError) Error() string { return fmt.Sprintf("invalid unicode: %#U", e.r) } -func (p *parser) parseWhitespace() error { +func (l *lexer) lexWhitespace() error { for { - r := p.peek() + r := l.peek() if isWhitespace(r) { - p.next() + l.next() } else { - if !p.empty() { - p.builder.Whitespace(p.accept()) + if !l.empty() { + l.parser.Whitespace(l.accept()) } return nil } @@ -757,29 +629,29 @@ func isLiteralChar(r rune) bool { return r == 0x09 || (r >= 0x20 && r <= 0x26) || (r >= 0x28 && r <= 0x7E) || isNonAsciiChar(r) } -func (p *parser) parseLiteralString() error { +func (l *lexer) lexLiteralString() error { // literal-string = apostrophe *literal-char apostrophe // literal-char = %x09 / %x20-26 / %x28-7E / non-ascii // non-ascii = %x80-D7FF / %xE000-10FFFF - err := p.expect('\'') + err := l.expect('\'') if err != nil { return err } - p.ignore() + l.ignore() for { - r := p.peekRune() + r := l.peekRune() if r == '\'' { - p.builder.LiteralString(p.accept()) - p.nextRune() - p.ignore() + l.parser.LiteralString(l.accept()) + l.nextRune() + l.ignore() return nil } if !isLiteralChar(r) { return &InvalidCharacter{r: r} } - p.nextRune() + l.nextRune() } } @@ -795,7 +667,7 @@ func isHex(r rune) bool { return (r >= '0' && r <= '9') || (r >= 'A' && r <= 'F') } -func (p *parser) parseBasicString() error { +func (l *lexer) lexBasicString() error { // basic-string = quotation-mark *basic-char quotation-mark // basic-char = basic-unescaped / escaped // basic-unescaped = wschar / %x21 / %x23-5B / %x5D-7E / non-ascii @@ -812,34 +684,34 @@ func (p *parser) parseBasicString() error { //escape-seq-char =/ %x55 8HEXDIG ; UXXXXXXXX U+XXXXXXXX // HEXDIG = DIGIT / "A" / "B" / "C" / "D" / "E" / "F" - err := p.expect('"') + err := l.expect('"') if err != nil { return err } - p.ignore() + l.ignore() for { - r := p.peekRune() + r := l.peekRune() if r == '"' { - p.builder.BasicString(p.accept()) - p.nextRune() - p.ignore() + l.parser.BasicString(l.accept()) + l.nextRune() + l.ignore() return nil } if r == '\\' { - p.nextRune() - r := p.peekRune() + l.nextRune() + r := l.peekRune() if isEscapeChar(r) { - p.nextRune() + l.nextRune() continue } if r == 'u' { - p.nextRune() + l.nextRune() for i := 0; i < 4; i++ { - r := p.nextRune() + r := l.nextRune() if !isHex(r) { return &InvalidCharacter{r: r} } @@ -848,9 +720,9 @@ func (p *parser) parseBasicString() error { } if r == 'U' { - p.nextRune() + l.nextRune() for i := 0; i < 8; i++ { - r := p.nextRune() + r := l.nextRune() if !isHex(r) { return &InvalidCharacter{r: r} } @@ -862,13 +734,13 @@ func (p *parser) parseBasicString() error { } if isBasicStringChar(r) { - p.nextRune() + l.nextRune() continue } } } -func (p *parser) parseTable() error { +func (l *lexer) lexTable() error { //;; Table // //table = std-table / array-table @@ -887,59 +759,59 @@ func (p *parser) parseTable() error { //array-table-open = %x5B.5B ws ; [[ Double left square bracket //array-table-close = ws %x5D.5D ; ]] Double right square bracket - if p.follows("[[") { - return p.parseArrayTable() + if l.follows("[[") { + return l.lexArrayTable() } - return p.parseStandardTable() + return l.lexStandardTable() } -func (p *parser) parseArrayTable() error { +func (l *lexer) lexArrayTable() error { //;; Array Table // //array-table = array-table-open key array-table-close // //array-table-open = %x5B.5B ws ; [[ Double left square bracket //array-table-close = ws %x5D.5D ; ]] Double right square bracket - err := p.expect('[') + err := l.expect('[') if err != nil { return err } - err = p.expect('[') + err = l.expect('[') if err != nil { return err } - p.ignore() - p.builder.ArrayTableBegin() + l.ignore() + l.parser.ArrayTableBegin() - err = p.parseWhitespace() + err = l.lexWhitespace() if err != nil { return err } - err = p.parseKey() + err = l.lexKey() if err != nil { return err } - err = p.parseWhitespace() + err = l.lexWhitespace() if err != nil { return err } - err = p.expect(']') + err = l.expect(']') if err != nil { return err } - err = p.expect(']') + err = l.expect(']') if err != nil { return err } - p.ignore() - p.builder.ArrayTableEnd() + l.ignore() + l.parser.ArrayTableEnd() return nil } -func (p *parser) parseStandardTable() error { +func (l *lexer) lexStandardTable() error { //;; Standard Table // //std-table = std-table-open key std-table-close @@ -947,32 +819,32 @@ func (p *parser) parseStandardTable() error { //std-table-open = %x5B ws ; [ Left square bracket //std-table-close = ws %x5D ; ] Right square bracket - err := p.expect('[') + err := l.expect('[') if err != nil { panic("std-table should start with [") } - p.ignore() - p.builder.StandardTableBegin() + l.ignore() + l.parser.StandardTableBegin() - err = p.parseWhitespace() + err = l.lexWhitespace() if err != nil { return err } - err = p.parseKey() + err = l.lexKey() if err != nil { return err } - err = p.parseWhitespace() + err = l.lexWhitespace() if err != nil { return err } - err = p.expect(']') + err = l.expect(']') if err != nil { return err } - p.ignore() - p.builder.StandardTableEnd() + l.ignore() + l.parser.StandardTableEnd() return nil } diff --git a/toml_test.go b/toml_test.go index b27ebf07..66dc41aa 100644 --- a/toml_test.go +++ b/toml_test.go @@ -50,36 +50,36 @@ func TestParse(t *testing.T) { } } -type noopBuilder struct { +type noopParser struct { } -func (n noopBuilder) ArrayTableBegin() {} -func (n noopBuilder) ArrayTableEnd() {} -func (n noopBuilder) StandardTableBegin() {} -func (n noopBuilder) StandardTableEnd() {} -func (n noopBuilder) InlineTableSeparator() {} -func (n noopBuilder) InlineTableBegin() {} -func (n noopBuilder) InlineTableEnd() {} -func (n noopBuilder) ArraySeparator() {} -func (n noopBuilder) ArrayBegin() {} -func (n noopBuilder) ArrayEnd() {} -func (n noopBuilder) Whitespace(b []byte) {} -func (n noopBuilder) Comment(b []byte) {} -func (n noopBuilder) UnquotedKey(b []byte) {} -func (n noopBuilder) LiteralString(b []byte) {} -func (n noopBuilder) BasicString(b []byte) {} -func (n noopBuilder) Dot(b []byte) {} -func (n noopBuilder) Boolean(b []byte) {} -func (n noopBuilder) Equal(b []byte) {} +func (n noopParser) ArrayTableBegin() {} +func (n noopParser) ArrayTableEnd() {} +func (n noopParser) StandardTableBegin() {} +func (n noopParser) StandardTableEnd() {} +func (n noopParser) InlineTableSeparator() {} +func (n noopParser) InlineTableBegin() {} +func (n noopParser) InlineTableEnd() {} +func (n noopParser) ArraySeparator() {} +func (n noopParser) ArrayBegin() {} +func (n noopParser) ArrayEnd() {} +func (n noopParser) Whitespace(b []byte) {} +func (n noopParser) Comment(b []byte) {} +func (n noopParser) UnquotedKey(b []byte) {} +func (n noopParser) LiteralString(b []byte) {} +func (n noopParser) BasicString(b []byte) {} +func (n noopParser) Dot(b []byte) {} +func (n noopParser) Boolean(b []byte) {} +func (n noopParser) Equal(b []byte) {} func BenchmarkParseAll(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { for _, data := range inputs { - builder := noopBuilder{} - p := parser{builder: &builder, data: []byte(data)} - err := p.parse() + p := noopParser{} + l := lexer{parser: &p, data: []byte(data)} + err := l.run() if err != nil { b.Fatalf("error: %s", err) } From 94ad175728659bdfa1f03765873bc8d62fd470e0 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Tue, 2 Feb 2021 10:55:23 -0500 Subject: [PATCH 016/228] wip --- encoding.go | 75 ++++++++++++++++++++++++++++++++++++++++++++++-- encoding_test.go | 1 + 2 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 encoding_test.go diff --git a/encoding.go b/encoding.go index 5b9b969f..510a7a10 100644 --- a/encoding.go +++ b/encoding.go @@ -1,8 +1,79 @@ package toml +type unmarshaler struct { +} + +func (u unmarshaler) Whitespace(b []byte) {} +func (u unmarshaler) Comment(b []byte) {} + +func (u unmarshaler) UnquotedKey(b []byte) { + panic("implement me") +} + +func (u unmarshaler) LiteralString(b []byte) { + panic("implement me") +} + +func (u unmarshaler) BasicString(b []byte) { + panic("implement me") +} + +func (u unmarshaler) Dot(b []byte) { + panic("implement me") +} + +func (u unmarshaler) Boolean(b []byte) { + panic("implement me") +} + +func (u unmarshaler) Equal(b []byte) { + panic("implement me") +} + +func (u unmarshaler) ArrayBegin() { + panic("implement me") +} + +func (u unmarshaler) ArrayEnd() { + panic("implement me") +} + +func (u unmarshaler) ArraySeparator() { + panic("implement me") +} + +func (u unmarshaler) InlineTableBegin() { + panic("implement me") +} + +func (u unmarshaler) InlineTableEnd() { + panic("implement me") +} + +func (u unmarshaler) InlineTableSeparator() { + panic("implement me") +} + +func (u unmarshaler) StandardTableBegin() { + panic("implement me") +} + +func (u unmarshaler) StandardTableEnd() { + panic("implement me") +} + +func (u unmarshaler) ArrayTableBegin() { + panic("implement me") +} + +func (u unmarshaler) ArrayTableEnd() { + panic("implement me") +} + func Unmarshal(data []byte, v interface{}) error { - // TODO - return nil + p := unmarshaler{} + l := lexer{parser: &p, data: data} + return l.run() } func Marshal(v interface{}) ([]byte, error) { diff --git a/encoding_test.go b/encoding_test.go new file mode 100644 index 00000000..27aae5ad --- /dev/null +++ b/encoding_test.go @@ -0,0 +1 @@ +package toml_test From b123c357c5708bcb2b55916e11e2d84f21ca1380 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Tue, 2 Feb 2021 20:54:20 -0500 Subject: [PATCH 017/228] Add tokens to Document --- document.go | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/document.go b/document.go index 0d4522c9..6292a0a3 100644 --- a/document.go +++ b/document.go @@ -2,7 +2,40 @@ package toml import "fmt" +type tokenKind int + +const ( + whitespace tokenKind = iota + arrayTableBegin + arrayTableEnd + standardTableBegin + standardTableEnd + inlineTableSeparator + inlineTableBegin + inlineTableEnd + arraySeparator + arrayBegin + arrayEnd + equal + boolean + dot + basicString + literalString + unquotedKey + comment +) + +type token struct { + data []byte + kind tokenKind +} + type Document struct { + tokens []token +} + +func (d *Document) appendToken(kind tokenKind, data []byte) { + d.tokens = append(d.tokens, token{data: data, kind: kind}) } type docParser struct { @@ -11,82 +44,100 @@ type docParser struct { func (d *docParser) ArrayTableBegin() { fmt.Println("ARRAY-TABLE[[") + d.document.appendToken(arrayTableBegin, nil) } func (d *docParser) ArrayTableEnd() { fmt.Println("ARRAY-TABLE]]") + d.document.appendToken(arrayTableEnd, nil) } func (d *docParser) StandardTableBegin() { fmt.Println("STD-TABLE[") + d.document.appendToken(standardTableBegin, nil) } func (d *docParser) StandardTableEnd() { fmt.Println("STD-TABLE]") + d.document.appendToken(standardTableEnd, nil) } func (d *docParser) InlineTableSeparator() { fmt.Println(", InlineTable SEPARATOR") + d.document.appendToken(inlineTableSeparator, nil) } func (d *docParser) InlineTableBegin() { fmt.Println("{ InlineTable BEGIN") + d.document.appendToken(inlineTableBegin, nil) } func (d *docParser) InlineTableEnd() { fmt.Println("} InlineTable END") + d.document.appendToken(inlineTableEnd, nil) } func (d *docParser) ArraySeparator() { fmt.Println(", ARRAY SEPARATOR") + d.document.appendToken(arraySeparator, nil) } func (d *docParser) ArrayBegin() { fmt.Println("[ ARRAY BEGIN") + d.document.appendToken(arrayBegin, nil) } func (d *docParser) ArrayEnd() { fmt.Println("] ARRAY END") + d.document.appendToken(arrayEnd, nil) } func (d *docParser) Equal(b []byte) { s := string(b) fmt.Printf("EQUAL: '%s'\n", s) + d.document.appendToken(equal, b) } func (d *docParser) Boolean(b []byte) { s := string(b) fmt.Printf("Boolean: '%s'\n", s) + d.document.appendToken(boolean, b) } func (d *docParser) Dot(b []byte) { s := string(b) fmt.Printf("DOT: '%s'\n", s) + d.document.appendToken(dot, b) } func (d *docParser) BasicString(b []byte) { s := string(b) fmt.Printf("BasicString: '%s'\n", s) + d.document.appendToken(basicString, b) } func (d *docParser) LiteralString(b []byte) { s := string(b) fmt.Printf("LiteralString: '%s'\n", s) + d.document.appendToken(literalString, b) } func (d *docParser) UnquotedKey(b []byte) { s := string(b) fmt.Printf("UnquotedKey: '%s'\n", s) + d.document.appendToken(unquotedKey, b) } func (d *docParser) Comment(b []byte) { s := string(b) fmt.Printf("Comment: '%s'\n", s) + d.document.appendToken(comment, b) } func (d *docParser) Whitespace(b []byte) { s := string(b) fmt.Printf("Whitespace: '%s'\n", s) + d.document.appendToken(whitespace, b) } func Parse(b []byte) (Document, error) { From 0ee0fe7f7cbee764a35eb9d6f2088e35eda60db8 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Thu, 4 Feb 2021 10:14:11 -0500 Subject: [PATCH 018/228] Trying the scanner approach --- document.go | 151 --------- encoding.go | 82 ----- encoding_test.go | 1 - go.sum | 3 +- lexer.go | 1 - parser.go | 22 -- scanner.go | 236 +++++++++++++ toml.go | 849 ----------------------------------------------- toml_test.go | 42 ++- 9 files changed, 261 insertions(+), 1126 deletions(-) delete mode 100644 document.go delete mode 100644 encoding.go delete mode 100644 encoding_test.go delete mode 100644 lexer.go delete mode 100644 parser.go create mode 100644 scanner.go diff --git a/document.go b/document.go deleted file mode 100644 index 6292a0a3..00000000 --- a/document.go +++ /dev/null @@ -1,151 +0,0 @@ -package toml - -import "fmt" - -type tokenKind int - -const ( - whitespace tokenKind = iota - arrayTableBegin - arrayTableEnd - standardTableBegin - standardTableEnd - inlineTableSeparator - inlineTableBegin - inlineTableEnd - arraySeparator - arrayBegin - arrayEnd - equal - boolean - dot - basicString - literalString - unquotedKey - comment -) - -type token struct { - data []byte - kind tokenKind -} - -type Document struct { - tokens []token -} - -func (d *Document) appendToken(kind tokenKind, data []byte) { - d.tokens = append(d.tokens, token{data: data, kind: kind}) -} - -type docParser struct { - document Document -} - -func (d *docParser) ArrayTableBegin() { - fmt.Println("ARRAY-TABLE[[") - d.document.appendToken(arrayTableBegin, nil) -} - -func (d *docParser) ArrayTableEnd() { - fmt.Println("ARRAY-TABLE]]") - d.document.appendToken(arrayTableEnd, nil) -} - -func (d *docParser) StandardTableBegin() { - fmt.Println("STD-TABLE[") - d.document.appendToken(standardTableBegin, nil) -} - -func (d *docParser) StandardTableEnd() { - fmt.Println("STD-TABLE]") - d.document.appendToken(standardTableEnd, nil) -} - -func (d *docParser) InlineTableSeparator() { - fmt.Println(", InlineTable SEPARATOR") - d.document.appendToken(inlineTableSeparator, nil) -} - -func (d *docParser) InlineTableBegin() { - fmt.Println("{ InlineTable BEGIN") - d.document.appendToken(inlineTableBegin, nil) -} - -func (d *docParser) InlineTableEnd() { - fmt.Println("} InlineTable END") - d.document.appendToken(inlineTableEnd, nil) -} - -func (d *docParser) ArraySeparator() { - fmt.Println(", ARRAY SEPARATOR") - d.document.appendToken(arraySeparator, nil) -} - -func (d *docParser) ArrayBegin() { - fmt.Println("[ ARRAY BEGIN") - d.document.appendToken(arrayBegin, nil) -} - -func (d *docParser) ArrayEnd() { - fmt.Println("] ARRAY END") - d.document.appendToken(arrayEnd, nil) -} - -func (d *docParser) Equal(b []byte) { - s := string(b) - fmt.Printf("EQUAL: '%s'\n", s) - d.document.appendToken(equal, b) -} - -func (d *docParser) Boolean(b []byte) { - s := string(b) - fmt.Printf("Boolean: '%s'\n", s) - d.document.appendToken(boolean, b) -} - -func (d *docParser) Dot(b []byte) { - s := string(b) - fmt.Printf("DOT: '%s'\n", s) - d.document.appendToken(dot, b) -} - -func (d *docParser) BasicString(b []byte) { - s := string(b) - fmt.Printf("BasicString: '%s'\n", s) - d.document.appendToken(basicString, b) -} - -func (d *docParser) LiteralString(b []byte) { - s := string(b) - fmt.Printf("LiteralString: '%s'\n", s) - d.document.appendToken(literalString, b) -} - -func (d *docParser) UnquotedKey(b []byte) { - s := string(b) - fmt.Printf("UnquotedKey: '%s'\n", s) - d.document.appendToken(unquotedKey, b) -} - -func (d *docParser) Comment(b []byte) { - s := string(b) - fmt.Printf("Comment: '%s'\n", s) - d.document.appendToken(comment, b) -} - -func (d *docParser) Whitespace(b []byte) { - s := string(b) - fmt.Printf("Whitespace: '%s'\n", s) - d.document.appendToken(whitespace, b) -} - -func Parse(b []byte) (Document, error) { - p := docParser{} - l := lexer{parser: &p, data: b} - err := l.run() - if err != nil { - return Document{}, err - } - return p.document, nil -} diff --git a/encoding.go b/encoding.go deleted file mode 100644 index 510a7a10..00000000 --- a/encoding.go +++ /dev/null @@ -1,82 +0,0 @@ -package toml - -type unmarshaler struct { -} - -func (u unmarshaler) Whitespace(b []byte) {} -func (u unmarshaler) Comment(b []byte) {} - -func (u unmarshaler) UnquotedKey(b []byte) { - panic("implement me") -} - -func (u unmarshaler) LiteralString(b []byte) { - panic("implement me") -} - -func (u unmarshaler) BasicString(b []byte) { - panic("implement me") -} - -func (u unmarshaler) Dot(b []byte) { - panic("implement me") -} - -func (u unmarshaler) Boolean(b []byte) { - panic("implement me") -} - -func (u unmarshaler) Equal(b []byte) { - panic("implement me") -} - -func (u unmarshaler) ArrayBegin() { - panic("implement me") -} - -func (u unmarshaler) ArrayEnd() { - panic("implement me") -} - -func (u unmarshaler) ArraySeparator() { - panic("implement me") -} - -func (u unmarshaler) InlineTableBegin() { - panic("implement me") -} - -func (u unmarshaler) InlineTableEnd() { - panic("implement me") -} - -func (u unmarshaler) InlineTableSeparator() { - panic("implement me") -} - -func (u unmarshaler) StandardTableBegin() { - panic("implement me") -} - -func (u unmarshaler) StandardTableEnd() { - panic("implement me") -} - -func (u unmarshaler) ArrayTableBegin() { - panic("implement me") -} - -func (u unmarshaler) ArrayTableEnd() { - panic("implement me") -} - -func Unmarshal(data []byte, v interface{}) error { - p := unmarshaler{} - l := lexer{parser: &p, data: data} - return l.run() -} - -func Marshal(v interface{}) ([]byte, error) { - // TODO - return nil, nil -} diff --git a/encoding_test.go b/encoding_test.go deleted file mode 100644 index 27aae5ad..00000000 --- a/encoding_test.go +++ /dev/null @@ -1 +0,0 @@ -package toml_test diff --git a/go.sum b/go.sum index d021fdd5..acb88a48 100644 --- a/go.sum +++ b/go.sum @@ -1,12 +1,11 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/lexer.go b/lexer.go deleted file mode 100644 index f9fa173c..00000000 --- a/lexer.go +++ /dev/null @@ -1 +0,0 @@ -package toml diff --git a/parser.go b/parser.go deleted file mode 100644 index 3f1fed74..00000000 --- a/parser.go +++ /dev/null @@ -1,22 +0,0 @@ -package toml - -type parser interface { - Whitespace(b []byte) - Comment(b []byte) - UnquotedKey(b []byte) - LiteralString(b []byte) - BasicString(b []byte) - Dot(b []byte) - Boolean(b []byte) - Equal(b []byte) - ArrayBegin() - ArrayEnd() - ArraySeparator() - InlineTableBegin() - InlineTableEnd() - InlineTableSeparator() - StandardTableBegin() - StandardTableEnd() - ArrayTableBegin() - ArrayTableEnd() -} diff --git a/scanner.go b/scanner.go new file mode 100644 index 00000000..12a8d57b --- /dev/null +++ b/scanner.go @@ -0,0 +1,236 @@ +package toml + +import "fmt" + +func scanFollows(pattern []byte) func(b []byte) bool { + return func(b []byte) bool { + if len(b) < len(pattern) { + return false + } + for i, c := range pattern { + if b[i] != c { + return false + } + } + return true + } +} + +var scanFollowsMultilineBasicStringDelimiter = scanFollows([]byte{'"', '"', '"'}) +var scanFollowsMultilineLiteralStringDelimiter = scanFollows([]byte{'\'', '\'', '\''}) +var scanFollowsTrue = scanFollows([]byte{'t', 'r', 'u', 'e'}) +var scanFollowsFalse = scanFollows([]byte{'f', 'a', 'l', 's', 'e'}) +var scanFollowsArrayTableBegin = scanFollows([]byte{arrayOrTableBegin, arrayOrTableBegin}) +var scanFollowsArrayTableEnd = scanFollows([]byte{arrayOrTableEnd, arrayOrTableEnd}) + +const ( + dot = '.' + equal = '=' + comma = ',' + inlineTableBegin = '{' + inlineTableEnd = '}' + comment = '#' + arrayOrTableBegin = '[' + arrayOrTableEnd = ']' +) + +// scan returns a []byte containing the next lexical token, bytes left, and an error. +// +// eof is signaled by an empty token and nil error. +func scan(b []byte) ([]byte, []byte, error) { + if len(b) == 0 { + return b, b, nil + } + + switch b[0] { + case dot, equal, inlineTableBegin, inlineTableEnd, comma: + return b[:1], b[1:], nil + case '"': + if scanFollowsMultilineBasicStringDelimiter(b) { + return scanMultilineBasicString(b) + } + return scanBasicString(b) + case '\'': + if scanFollowsMultilineLiteralStringDelimiter(b) { + return scanMultilineLiteralString(b) + } + return scanLiteralString(b) + case comment: + return scanComment(b) + case ' ', '\t': + return scanWhitespace(b) + case '\r': + return scanWindowsNewline(b) + case '\n': + return b[:1], b[1:], nil + case 't': + if scanFollowsTrue(b) { + return b[:4], b[4:], nil + } + case 'f': + if scanFollowsFalse(b) { + return b[:5], b[5:], nil + } + case arrayOrTableBegin: + if scanFollowsArrayTableBegin(b) { + return b[:2], b[2:], nil + } + return b[:1], b[1:], nil + case arrayOrTableEnd: + if scanFollowsArrayTableEnd(b) { + return b[:2], b[2:], nil + } + return b[:1], b[1:], nil + } + + if isUnquotedKeyChar(b[0]) { + return scanUnquotedKey(b) + } + + // TODO: numbers, date-time + panic("unhandled scan") +} + +func scanUnquotedKey(b []byte) ([]byte, []byte, error) { + //unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _ + for i := 1; i < len(b); i++ { + if !isUnquotedKeyChar(b[i]) { + return b[:i], b[i:], nil + } + } + return b, nil, nil +} + +func isUnquotedKeyChar(r byte) bool { + return (r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '-' || r == '_' +} + +func scanLiteralString(b []byte) ([]byte, []byte, error) { + //literal-string = apostrophe *literal-char apostrophe + //apostrophe = %x27 ; ' apostrophe + //literal-char = %x09 / %x20-26 / %x28-7E / non-ascii + for i := 1; i < len(b); i++ { + switch b[i] { + case '\'': + return b[:i+1], b[i+1:], nil + case '\n': + return nil, nil, fmt.Errorf("literal strings cannot have new lines") + } + } + return nil, nil, fmt.Errorf("unterminated literal string") +} + +func scanMultilineLiteralString(b []byte) ([]byte, []byte, error) { + //ml-literal-string = ml-literal-string-delim [ newline ] ml-literal-body + //ml-literal-string-delim + //ml-literal-string-delim = 3apostrophe + //ml-literal-body = *mll-content *( mll-quotes 1*mll-content ) [ mll-quotes ] + // + //mll-content = mll-char / newline + //mll-char = %x09 / %x20-26 / %x28-7E / non-ascii + //mll-quotes = 1*2apostrophe + for i := 3; i < len(b); i++ { + switch b[i] { + case '\'': + if scanFollowsMultilineLiteralStringDelimiter(b[i:]) { + return b[:i+3], b[:i+3], nil + } + } + } + + return nil, nil, fmt.Errorf(`multiline literal string not terminated by '''`) +} + +func scanWindowsNewline(b []byte) ([]byte, []byte, error) { + if len(b) < 2 { + return nil, nil, fmt.Errorf(`windows new line missing \n`) + } + if b[1] != '\n' { + return nil, nil, fmt.Errorf(`windows new line should be \r\n`) + } + return b[:2], b[2:], nil +} + +func scanWhitespace(b []byte) ([]byte, []byte, error) { + for i := 1; i < len(b); i++ { + switch b[i] { + case ' ', '\t': + continue + default: + return b[:i], b[i:], nil + } + } + return b, nil, nil +} + +func scanComment(b []byte) ([]byte, []byte, error) { + //;; Comment + // + //comment-start-symbol = %x23 ; # + //non-ascii = %x80-D7FF / %xE000-10FFFF + //non-eol = %x09 / %x20-7F / non-ascii + // + //comment = comment-start-symbol *non-eol + + for i := 1; i < len(b); i++ { + switch b[i] { + case '\n': + return b[:i+1], b[i+1:], nil + } + } + return b, nil, nil +} + +// TODO perform validation on the string? +func scanBasicString(b []byte) ([]byte, []byte, error) { + //basic-string = quotation-mark *basic-char quotation-mark + //quotation-mark = %x22 ; " + //basic-char = basic-unescaped / escaped + //basic-unescaped = wschar / %x21 / %x23-5B / %x5D-7E / non-ascii + //escaped = escape escape-seq-char + for i := 1; i < len(b); i++ { + switch b[i] { + case '"': + return b[:i+1], b[i+1:], nil + case '\n': + return nil, nil, fmt.Errorf("basic strings cannot have new lines") + case '\\': + if len(b) < i+2 { + return nil, nil, fmt.Errorf("need a character after \\") + } + i++ // skip the next character + } + } + + return nil, nil, fmt.Errorf(`basic string not terminated by "`) +} + +// TODO perform validation on the string? +func scanMultilineBasicString(b []byte) ([]byte, []byte, error) { + //ml-basic-string = ml-basic-string-delim [ newline ] ml-basic-body + //ml-basic-string-delim + //ml-basic-string-delim = 3quotation-mark + //ml-basic-body = *mlb-content *( mlb-quotes 1*mlb-content ) [ mlb-quotes ] + // + //mlb-content = mlb-char / newline / mlb-escaped-nl + //mlb-char = mlb-unescaped / escaped + //mlb-quotes = 1*2quotation-mark + //mlb-unescaped = wschar / %x21 / %x23-5B / %x5D-7E / non-ascii + //mlb-escaped-nl = escape ws newline *( wschar / newline ) + + for i := 3; i < len(b); i++ { + switch b[i] { + case '"': + if scanFollowsMultilineBasicStringDelimiter(b[i:]) { + return b[:i+3], b[:i+3], nil + } + case '\\': + if len(b) < i+2 { + return nil, nil, fmt.Errorf("need a character after \\") + } + i++ // skip the next character + } + } + + return nil, nil, fmt.Errorf(`multiline basic string not terminated by """`) +} diff --git a/toml.go b/toml.go index e66c9ee4..f9fa173c 100644 --- a/toml.go +++ b/toml.go @@ -1,850 +1 @@ package toml - -import ( - "fmt" - "unicode/utf8" -) - -type position struct { - line int - column int -} - -// eof is a rune value indicating end-of-file. -const eof = -1 - -type lookahead struct { - r rune - size int -} - -func (l lookahead) empty() bool { - return l.r == 0 -} - -type lexer struct { - parser parser - - data []byte - start int - end int - - lookahead lookahead -} - -func (l *lexer) at(i int) rune { - if l.end+i >= len(l.data) { - return eof - } - return rune(l.data[l.end+i]) -} - -func (l *lexer) follows(s string) bool { - for i := 0; i < len(s); i++ { - if rune(s[i]) != l.at(i) { - return false - } - } - return true -} - -func (l *lexer) peek() rune { - return l.at(0) -} - -func (l *lexer) next() rune { - x := l.peek() - if x != eof { - l.end++ - } - return x -} - -func (l *lexer) expect(expected rune) error { - r := l.next() - if r != expected { - return &UnexpectedCharacter{ - r: r, - expected: expected, - } - } - return nil -} - -func (l *lexer) peekRune() rune { - if l.lookahead.empty() { - l.lookahead.r, l.lookahead.size = utf8.DecodeRune(l.data[l.end:]) - if l.lookahead.r == utf8.RuneError && l.lookahead.size == 0 { - l.lookahead.r = eof - } - } - return l.lookahead.r -} - -func (l *lexer) nextRune() rune { - r := l.peekRune() - if r != eof { - l.end += l.lookahead.size - l.lookahead.r = 0 - l.lookahead.size = 0 - } - return r -} - -func (l *lexer) ignore() { - if l.empty() { - panic("cannot ignore empty token") - } - l.start = l.end -} - -func (l *lexer) accept() []byte { - if l.empty() { - panic("cannot accept empty token") - } - x := l.data[l.start:l.end] - l.start = l.end - return x -} - -func (l *lexer) expectRune(expected rune) error { - r := l.nextRune() - if r != expected { - return &UnexpectedCharacter{ - r: r, - expected: expected, - } - } - return nil -} - -func (l *lexer) empty() bool { - return l.start == l.end -} - -type InvalidCharacter struct { - r rune -} - -func (e *InvalidCharacter) Error() string { - return fmt.Sprintf("unexpected character '%#U'", e.r) -} - -type UnexpectedCharacter struct { - r rune - expected rune -} - -func (e *UnexpectedCharacter) Error() string { - return fmt.Sprintf("expected character '%#U' but got '%#U'", e.expected, e.r) -} - -func (l *lexer) run() error { - for { - err := l.lexExpression() - if err != nil { - return err - } - - // new lines between expressions - r := l.next() - switch r { - case eof: - return nil - case '\n': - l.ignore() - continue - case '\r': - r = l.next() - if r == '\n' { - l.ignore() - continue - } - } - return &InvalidCharacter{r: r} - } -} - -func (l *lexer) lexRequiredNewline() error { - r := l.next() - switch r { - case '\n': - l.ignore() - return nil - case '\r': - r = l.next() - if r == '\n' { - l.ignore() - return nil - } - } - return &InvalidCharacter{r: r} -} - -func (l *lexer) lexExpression() error { - //expression = ws [ comment ] - //expression =/ ws keyval ws [ comment ] - //expression =/ ws table ws [ comment ] - - err := l.lexWhitespace() - if err != nil { - return err - } - - r := l.peek() - - // Line with just whitespace and a comment. We can exit early. - if r == '#' { - return l.lexComment() - } - - // or line with something? - if r == '[' { - // parse table. could be either a standard table or an array table - err := l.lexTable() - if err != nil { - return err - } - } else if isUnquotedKeyRune(r) || r == '\'' || r == '"' { - err := l.lexKeyval() - if err != nil { - return err - } - } - - // parse trailing whitespace and comment - - err = l.lexWhitespace() - if err != nil { - return err - } - - r = l.peek() - if r == '#' { - return l.lexComment() - } - - return nil -} - -func (l *lexer) lexKeyval() error { - // key keyval-sep val - //keyval-sep = ws %x3D ws ; = - - err := l.lexKey() - if err != nil { - return err - } - - err = l.lexWhitespace() - if err != nil { - return err - } - - err = l.expect('=') - if err != nil { - return err - } - l.parser.Equal(l.accept()) - - err = l.lexWhitespace() - if err != nil { - return err - } - - return l.lexVal() -} - -func (l *lexer) lexVal() error { - //val = string / boolean / array / inline-table / date-time / float / integer - // string = ml-basic-string / basic-string / ml-literal-string / literal-string - - r := l.peek() - - switch r { - case 't', 'f': - return l.lexBool() - case '\'', '"': - return l.lexString() - case '[': - return l.lexArray() - case '{': - return l.lexInlineTable() - // TODO - default: - return &InvalidCharacter{r: r} - } -} - -func (l *lexer) lexInlineTable() error { - //inline-table = inline-table-open [ inline-table-keyvals ] inline-table-close - // - //inline-table-open = %x7B ws ; { - // inline-table-close = ws %x7D ; } - //inline-table-sep = ws %x2C ws ; , Comma - // - //inline-table-keyvals = keyval [ inline-table-sep inline-table-keyvals ] - - err := l.expect('{') - if err != nil { - panic("inline tables should start with {") - } - l.ignore() - l.parser.InlineTableBegin() - - err = l.lexWhitespace() - if err != nil { - return err - } - - r := l.peek() - if r == '}' { - l.next() - l.ignore() - l.parser.InlineTableEnd() - return nil - } - - err = l.lexKeyval() - if err != nil { - return err - } - - for { - err = l.lexWhitespace() - if err != nil { - return err - } - - r := l.peek() - if r == '}' { - l.next() - l.ignore() - l.parser.InlineTableEnd() - return nil - } - - err := l.expect(',') - if err != nil { - return err - } - l.parser.InlineTableSeparator() - l.ignore() - - err = l.lexWhitespace() - if err != nil { - return err - } - - err = l.lexKeyval() - if err != nil { - return err - } - } -} - -func (l *lexer) lexArray() error { - //array = array-open [ array-values ] ws-comment-newline array-close - - err := l.expect('[') - if err != nil { - panic("arrays should start with [") - } - l.ignore() - - l.parser.ArrayBegin() - - err = l.lexWhitespaceCommentNewline() - if err != nil { - return err - } - - r := l.peek() - - if r == ']' { - l.next() - l.ignore() - l.parser.ArrayEnd() - return nil - } - - err = l.lexVal() - if err != nil { - return err - } - - for { - err = l.lexWhitespaceCommentNewline() - if err != nil { - return err - } - - r := l.peek() - - if r == ']' { - l.next() - l.ignore() - l.parser.ArrayEnd() - return nil - } - - err := l.expect(',') - if err != nil { - return err - } - l.parser.ArraySeparator() - l.ignore() - - err = l.lexWhitespaceCommentNewline() - if err != nil { - return err - } - - err = l.lexVal() - if err != nil { - return err - } - } -} - -func (l *lexer) lexWhitespaceCommentNewline() error { - // ws-comment-newline = *( wschar / ([ comment ] newline) ) - - for { - if isWhitespace(l.peek()) { - err := l.lexWhitespace() - if err != nil { - return err - } - } - if l.peek() == '#' { - err := l.lexComment() - if err != nil { - return err - } - } - r := l.peek() - if r != '\n' && r != '\r' { - return nil - } - err := l.lexRequiredNewline() - if err != nil { - return err - } - } -} - -func (l *lexer) lexString() error { - r := l.peek() - - if r == '\'' { - if l.follows("'''") { - // TODO ml-literal-string - panic("TODO") - } else { - return l.lexLiteralString() - } - } else if r == '"' { - if l.follows("\"\"\"") { - // TODO ml-basic-string - panic("TODO") - } else { - return l.lexBasicString() - } - } else { - panic("string should start with ' or \"") - } -} - -func (l *lexer) lexBool() error { - r := l.peek() - - if r == 't' { - l.next() - err := l.expect('r') - if err != nil { - return err - } - err = l.expect('u') - if err != nil { - return err - } - err = l.expect('e') - if err != nil { - return err - } - } else if r == 'f' { - l.next() - err := l.expect('a') - if err != nil { - return err - } - err = l.expect('l') - if err != nil { - return err - } - err = l.expect('s') - if err != nil { - return err - } - err = l.expect('e') - if err != nil { - return err - } - } else { - return &InvalidCharacter{r: r} - } - - l.parser.Boolean(l.accept()) - return nil -} - -func (l *lexer) lexKey() error { - // simple-key / dotted-key - // dotted-key = simple-key 1*( dot-sep simple-key ) - // dot-sep = ws %x2E ws - - for { - err := l.lexSimpleKey() - if err != nil { - return err - } - - err = l.lexWhitespace() - if err != nil { - return err - } - - r := l.peek() - if r != '.' { - break - } - - l.next() - l.parser.Dot(l.accept()) - - err = l.lexWhitespace() - if err != nil { - return err - } - } - - err := l.lexWhitespace() - if err != nil { - return err - } - - return nil -} - -func isUnquotedKeyRune(r rune) bool { - return (r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '-' || r == '_' -} - -func (l *lexer) lexSimpleKey() error { - // simple-key = quoted-key / unquoted-key - // quoted-key = basic-string / literal-string - // unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _ - // basic-string = quotation-mark *basic-char quotation-mark - // literal-string = apostrophe *literal-char apostrophe - - r := l.peek() - - switch r { - case '\'': - return l.lexLiteralString() - case '"': - return l.lexBasicString() - default: - return l.lexUnquotedKey() - } -} - -func (l *lexer) lexUnquotedKey() error { - // unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _ - - r := l.next() - - if !isUnquotedKeyRune(r) { - return &InvalidCharacter{r: r} - } - - for { - r := l.peek() - if !isUnquotedKeyRune(r) { - break - } - l.next() - } - l.parser.UnquotedKey(l.accept()) - return nil -} - -func (l *lexer) lexComment() error { - if err := l.expect('#'); err != nil { - return err - } - - for { - r := l.peek() - if r == eof || r == '\n' { - l.parser.Comment(l.accept()) - return nil - } - l.next() - } -} - -func isWhitespace(r rune) bool { - return r == 0x20 || r == 0x09 -} - -type InvalidUnicodeError struct { - r rune -} - -func (e *InvalidUnicodeError) Error() string { - return fmt.Sprintf("invalid unicode: %#U", e.r) -} - -func (l *lexer) lexWhitespace() error { - for { - r := l.peek() - if isWhitespace(r) { - l.next() - } else { - if !l.empty() { - l.parser.Whitespace(l.accept()) - } - return nil - } - } -} - -func isNonAsciiChar(r rune) bool { - return (r >= 0x80 && r <= 0xD7FF) || (r >= 0xE000 && r <= 0x10FFFF) -} - -func isLiteralChar(r rune) bool { - return r == 0x09 || (r >= 0x20 && r <= 0x26) || (r >= 0x28 && r <= 0x7E) || isNonAsciiChar(r) -} - -func (l *lexer) lexLiteralString() error { - // literal-string = apostrophe *literal-char apostrophe - // literal-char = %x09 / %x20-26 / %x28-7E / non-ascii - // non-ascii = %x80-D7FF / %xE000-10FFFF - - err := l.expect('\'') - if err != nil { - return err - } - l.ignore() - - for { - r := l.peekRune() - if r == '\'' { - l.parser.LiteralString(l.accept()) - l.nextRune() - l.ignore() - return nil - } - if !isLiteralChar(r) { - return &InvalidCharacter{r: r} - } - l.nextRune() - } -} - -func isBasicStringChar(r rune) bool { - return r == ' ' || r == 0x21 || r >= 0x23 && r <= 0x5B || r >= 0x5D && r <= 0x7E || isNonAsciiChar(r) -} - -func isEscapeChar(r rune) bool { - return r == '"' || r == '\\' || r == 'b' || r == 'f' || r == 'n' || r == 'r' || r == 't' -} - -func isHex(r rune) bool { - return (r >= '0' && r <= '9') || (r >= 'A' && r <= 'F') -} - -func (l *lexer) lexBasicString() error { - // basic-string = quotation-mark *basic-char quotation-mark - // basic-char = basic-unescaped / escaped - // basic-unescaped = wschar / %x21 / %x23-5B / %x5D-7E / non-ascii - // escaped = escape escape-seq-char - //escape = %x5C ; \ - //escape-seq-char = %x22 ; " quotation mark U+0022 - //escape-seq-char =/ %x5C ; \ reverse solidus U+005C - //escape-seq-char =/ %x62 ; b backspace U+0008 - //escape-seq-char =/ %x66 ; f form feed U+000C - //escape-seq-char =/ %x6E ; n line feed U+000A - //escape-seq-char =/ %x72 ; r carriage return U+000D - //escape-seq-char =/ %x74 ; t tab U+0009 - //escape-seq-char =/ %x75 4HEXDIG ; uXXXX U+XXXX - //escape-seq-char =/ %x55 8HEXDIG ; UXXXXXXXX U+XXXXXXXX - // HEXDIG = DIGIT / "A" / "B" / "C" / "D" / "E" / "F" - - err := l.expect('"') - if err != nil { - return err - } - l.ignore() - - for { - r := l.peekRune() - - if r == '"' { - l.parser.BasicString(l.accept()) - l.nextRune() - l.ignore() - return nil - } - - if r == '\\' { - l.nextRune() - r := l.peekRune() - if isEscapeChar(r) { - l.nextRune() - continue - } - - if r == 'u' { - l.nextRune() - for i := 0; i < 4; i++ { - r := l.nextRune() - if !isHex(r) { - return &InvalidCharacter{r: r} - } - } - continue - } - - if r == 'U' { - l.nextRune() - for i := 0; i < 8; i++ { - r := l.nextRune() - if !isHex(r) { - return &InvalidCharacter{r: r} - } - } - continue - } - - return &InvalidCharacter{r: r} - } - - if isBasicStringChar(r) { - l.nextRune() - continue - } - } -} - -func (l *lexer) lexTable() error { - //;; Table - // - //table = std-table / array-table - // - //;; Standard Table - // - //std-table = std-table-open key std-table-close - // - //std-table-open = %x5B ws ; [ Left square bracket - //std-table-close = ws %x5D ; ] Right square bracket - // - //;; Array Table - // - //array-table = array-table-open key array-table-close - // - //array-table-open = %x5B.5B ws ; [[ Double left square bracket - //array-table-close = ws %x5D.5D ; ]] Double right square bracket - - if l.follows("[[") { - return l.lexArrayTable() - } - - return l.lexStandardTable() -} - -func (l *lexer) lexArrayTable() error { - //;; Array Table - // - //array-table = array-table-open key array-table-close - // - //array-table-open = %x5B.5B ws ; [[ Double left square bracket - //array-table-close = ws %x5D.5D ; ]] Double right square bracket - err := l.expect('[') - if err != nil { - return err - } - err = l.expect('[') - if err != nil { - return err - } - l.ignore() - l.parser.ArrayTableBegin() - - err = l.lexWhitespace() - if err != nil { - return err - } - - err = l.lexKey() - if err != nil { - return err - } - - err = l.lexWhitespace() - if err != nil { - return err - } - err = l.expect(']') - if err != nil { - return err - } - err = l.expect(']') - if err != nil { - return err - } - l.ignore() - l.parser.ArrayTableEnd() - return nil -} - -func (l *lexer) lexStandardTable() error { - //;; Standard Table - // - //std-table = std-table-open key std-table-close - // - //std-table-open = %x5B ws ; [ Left square bracket - //std-table-close = ws %x5D ; ] Right square bracket - - err := l.expect('[') - if err != nil { - panic("std-table should start with [") - } - l.ignore() - l.parser.StandardTableBegin() - - err = l.lexWhitespace() - if err != nil { - return err - } - - err = l.lexKey() - if err != nil { - return err - } - - err = l.lexWhitespace() - if err != nil { - return err - } - err = l.expect(']') - if err != nil { - return err - } - l.ignore() - l.parser.StandardTableEnd() - return nil -} diff --git a/toml_test.go b/toml_test.go index 66dc41aa..446b60ae 100644 --- a/toml_test.go +++ b/toml_test.go @@ -4,7 +4,7 @@ import ( "fmt" "testing" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) var inputs = []string{ @@ -43,9 +43,14 @@ func TestParse(t *testing.T) { for i, data := range inputs { t.Run(fmt.Sprintf("example %d", i), func(t *testing.T) { fmt.Printf("input:\n\t`%s`\n", data) - doc, err := Parse([]byte(data)) - assert.NoError(t, err) - fmt.Println(doc) + b := []byte(data) + var token []byte + var err error + for len(b) > 0 { + token, b, err = scan(b) + require.NoError(t, err) + fmt.Printf("token => '%s'\n", string(token)) + } }) } } @@ -72,17 +77,18 @@ func (n noopParser) Dot(b []byte) {} func (n noopParser) Boolean(b []byte) {} func (n noopParser) Equal(b []byte) {} -func BenchmarkParseAll(b *testing.B) { - b.ReportAllocs() - - for i := 0; i < b.N; i++ { - for _, data := range inputs { - p := noopParser{} - l := lexer{parser: &p, data: []byte(data)} - err := l.run() - if err != nil { - b.Fatalf("error: %s", err) - } - } - } -} +// +//func BenchmarkParseAll(b *testing.B) { +// b.ReportAllocs() +// +// for i := 0; i < b.N; i++ { +// for _, data := range inputs { +// p := noopParser{} +// l := lexer{parser: &p, data: []byte(data)} +// err := l.run() +// if err != nil { +// b.Fatalf("error: %s", err) +// } +// } +// } +//} From ca12c0670d74e6b0529c01cb052fa962edf59f35 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Fri, 5 Feb 2021 14:47:24 -0500 Subject: [PATCH 019/228] wip parsing --- scanner.go | 6 +- toml.go | 267 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 270 insertions(+), 3 deletions(-) diff --git a/scanner.go b/scanner.go index 12a8d57b..4bd02915 100644 --- a/scanner.go +++ b/scanner.go @@ -151,16 +151,16 @@ func scanWindowsNewline(b []byte) ([]byte, []byte, error) { return b[:2], b[2:], nil } -func scanWhitespace(b []byte) ([]byte, []byte, error) { +func scanWhitespace(b []byte) ([]byte, []byte) { for i := 1; i < len(b); i++ { switch b[i] { case ' ', '\t': continue default: - return b[:i], b[i:], nil + return b[:i], b[i:] } } - return b, nil, nil + return b, nil } func scanComment(b []byte) ([]byte, []byte, error) { diff --git a/toml.go b/toml.go index f9fa173c..32c03ad1 100644 --- a/toml.go +++ b/toml.go @@ -1 +1,268 @@ package toml + +import ( + "encoding/hex" + "fmt" + "strings" +) + +func parse(b []byte) error { + b, err := parseExpression(b) + if err != nil { + return err + } + for len(b) > 0 { + b, err = parseNewline(b) + if err != nil { + return err + } + + b, err = parseExpression(b) + if err != nil { + return err + } + } + return nil +} + +func parseNewline(b []byte) ([]byte, error) { + if b[0] == '\n' { + return b[1:], nil + } + if b[0] == '\r' { + _, rest, err := scanWindowsNewline(b) + return rest, err + } + return nil, fmt.Errorf("expected newline but got %#U", b[0]) +} + +func parseExpression(b []byte) ([]byte, error) { + //expression = ws [ comment ] + //expression =/ ws keyval ws [ comment ] + //expression =/ ws table ws [ comment ] + + b = parseWhitespace(b) + + if len(b) == 0 { + return b, nil + } + + if b[0] == '#' { + _, rest, err := scanComment(b) + return rest, err + } + + if b[0] == '[' { + // TODO: parse 'table' + } else { + rest, err := parseKeyval(b) + return rest, err + } + + b = parseWhitespace(b) + + if len(b) > 0 && b[0] == '#' { + _, rest, err := scanComment(b) + return rest, err + } + + return b, nil +} + +func parseKeyval(b []byte) ([]byte, error) { + //keyval = key keyval-sep val + + b, err := parseKey(b) + if err != nil { + return nil, err + } + + //keyval-sep = ws %x3D ws ; = + + b = parseWhitespace(b) + b, err = expect('=', b) + if err != nil { + return nil, err + } + b = parseWhitespace(b) + + return parseVal(b) +} + +func parseVal(b []byte) ([]byte, error) { + // val = string / boolean / array / inline-table / date-time / float / integer + +} + +func parseKey(b []byte) ([]byte, error) { + //key = simple-key / dotted-key + //simple-key = quoted-key / unquoted-key + // + //unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _ + //quoted-key = basic-string / literal-string + //dotted-key = simple-key 1*( dot-sep simple-key ) + // + //dot-sep = ws %x2E ws ; . Period + + b, err := parseSimpleKey(b) + if err != nil { + return nil, err + } + + for { + if len(b) > 0 && (b[0] == '.' || isWhitespace(b[0])) { + b = parseWhitespace(b) + b, err = expect('.', b) + if err != nil { + return nil, err + } + b = parseWhitespace(b) + b, err = parseSimpleKey(b) + if err != nil { + return nil, err + } + } else { + break + } + } + + return b, nil +} + +func isWhitespace(b byte) bool { + return b == ' ' || b == '\t' +} + +func parseSimpleKey(b []byte) ([]byte, error) { + //simple-key = quoted-key / unquoted-key + //unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _ + //quoted-key = basic-string / literal-string + + if len(b) == 0 { + return nil, unexpectedCharacter{b: b} + } + + if b[0] == '\'' { + _, rest, err := scanLiteralString(b) + return rest, err + } + if b[0] == '"' { + _, rest, err := parseBasicString(b) + return rest, err + } + + if isUnquotedKeyChar(b[0]) { + _, rest, err := scanUnquotedKey(b) + return rest, err + } + + return nil, unexpectedCharacter{b: b} +} + +func parseBasicString(b []byte) (string, []byte, error) { + //basic-string = quotation-mark *basic-char quotation-mark + //quotation-mark = %x22 ; " + //basic-char = basic-unescaped / escaped + //basic-unescaped = wschar / %x21 / %x23-5B / %x5D-7E / non-ascii + //escaped = escape escape-seq-char + //escape-seq-char = %x22 ; " quotation mark U+0022 + //escape-seq-char =/ %x5C ; \ reverse solidus U+005C + //escape-seq-char =/ %x62 ; b backspace U+0008 + //escape-seq-char =/ %x66 ; f form feed U+000C + //escape-seq-char =/ %x6E ; n line feed U+000A + //escape-seq-char =/ %x72 ; r carriage return U+000D + //escape-seq-char =/ %x74 ; t tab U+0009 + //escape-seq-char =/ %x75 4HEXDIG ; uXXXX U+XXXX + //escape-seq-char =/ %x55 8HEXDIG ; UXXXXXXXX U+XXXXXXXX + + token, rest, err := scanBasicString(b) + if err != nil { + return "", nil, err + } + var builder strings.Builder + + // The scanner ensures that the token starts and ends with quotes and that + // escapes are balanced. + for i := 1; i < len(token)-1; i++ { + c := token[i] + if c == '\\' { + i++ + c = token[i] + switch c { + case '"', '\\': + builder.WriteByte(c) + case 'b': + builder.WriteByte('\b') + case 'f': + builder.WriteByte('\f') + case 'n': + builder.WriteByte('\n') + case 'r': + builder.WriteByte('\r') + case 't': + builder.WriteByte('\t') + case 'u': + x, err := hexToString(token[i+1:len(token)-1], 4) + if err != nil { + return "", nil, err + } + builder.WriteString(x) + i += 4 + case 'U': + x, err := hexToString(token[i+1:len(token)-1], 8) + if err != nil { + return "", nil, err + } + builder.WriteString(x) + i += 8 + default: + return "", nil, fmt.Errorf("invalid escaped character: %#U", c) + } + } else { + builder.WriteByte(c) + } + } + + return builder.String(), rest, nil +} + +func hexToString(b []byte, length int) (string, error) { + if len(b) < length { + return "", fmt.Errorf("unicode point needs %d hex characters", length) + } + // TODO: slow + b, err := hex.DecodeString(string(b[:length])) + if err != nil { + return "", err + } + return string(b), nil +} + +func parseWhitespace(b []byte) []byte { + //ws = *wschar + //wschar = %x20 ; Space + //wschar =/ %x09 ; Horizontal tab + + _, rest := scanWhitespace(b) + return rest +} + +func expect(x byte, b []byte) ([]byte, error) { + if len(b) == 0 || b[0] != x { + return nil, unexpectedCharacter{r: x, b: b} + } + return b[1:], nil +} + +type unexpectedCharacter struct { + r byte + b []byte +} + +func (u unexpectedCharacter) Error() string { + if len(u.b) == 0 { + return fmt.Sprintf("expected %#U, not EOF", u.r) + + } + return fmt.Sprintf("expected %#U, not %#U", u.r, u.b[0]) +} From 736a75748bbcebc77d708740921f199ddeabf832 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Fri, 5 Feb 2021 17:46:40 -0500 Subject: [PATCH 020/228] Multiline basic string parsing --- scanner.go | 3 +- toml.go | 120 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+), 1 deletion(-) diff --git a/scanner.go b/scanner.go index 4bd02915..5ffb7c46 100644 --- a/scanner.go +++ b/scanner.go @@ -58,7 +58,8 @@ func scan(b []byte) ([]byte, []byte, error) { case comment: return scanComment(b) case ' ', '\t': - return scanWhitespace(b) + data, rest := scanWhitespace(b) + return data, rest, nil case '\r': return scanWindowsNewline(b) case '\n': diff --git a/toml.go b/toml.go index 32c03ad1..bf6d290d 100644 --- a/toml.go +++ b/toml.go @@ -92,6 +92,126 @@ func parseKeyval(b []byte) ([]byte, error) { func parseVal(b []byte) ([]byte, error) { // val = string / boolean / array / inline-table / date-time / float / integer + c := b[0] + + switch c { + // strings + case '"': + var rest []byte + var err error + if scanFollowsMultilineBasicStringDelimiter(b) { + _, rest, err = parseMultilineBasicString(b) + } else { + _, rest, err = parseBasicString(b) + } + return rest, err + case '\'': + if scanFollowsMultilineLiteralStringDelimiter(b) { + return parseMultilineLiteralString(b) + } + _, rest, err := scanLiteralString(b) + return rest, err + // TODO boolean + + // TODO array + + // TODO inline-table + + // TODO date-time + + // TODO float + + // TODO integer + default: + return nil, fmt.Errorf("unexpected char") + } +} + +func parseMultilineBasicString(b []byte) (string, []byte, error) { + //ml-basic-string = ml-basic-string-delim [ newline ] ml-basic-body + //ml-basic-string-delim + //ml-basic-string-delim = 3quotation-mark + //ml-basic-body = *mlb-content *( mlb-quotes 1*mlb-content ) [ mlb-quotes ] + // + //mlb-content = mlb-char / newline / mlb-escaped-nl + //mlb-char = mlb-unescaped / escaped + //mlb-quotes = 1*2quotation-mark + //mlb-unescaped = wschar / %x21 / %x23-5B / %x5D-7E / non-ascii + //mlb-escaped-nl = escape ws newline *( wschar / newline ) + + token, rest, err := scanMultilineBasicString(b) + if err != nil { + return "", nil, err + } + var builder strings.Builder + + i := 3 + + // skip the immediate new line + if token[i] == '\n' { + i++ + } else if token[i] == '\r' && token[i+1] == '\n' { + i += 2 + } + + // The scanner ensures that the token starts and ends with quotes and that + // escapes are balanced. + for ; i < len(token)-3; i++ { + c := token[i] + if c == '\\' { + // When the last non-whitespace character on a line is an unescaped \, + // it will be trimmed along with all whitespace (including newlines) up + // to the next non-whitespace character or closing delimiter. + if token[i+1] == '\n' || (token[i+1] == '\r' && token[i+2] == '\n') { + i++ // skip the \ + for ; i < len(token)-3; i++ { + c := token[i] + if !(c == '\n' || c == '\r' || c == ' ' || c == '\t') { + break + } + } + continue + } + + // handle escaping + i++ + c = token[i] + switch c { + case '"', '\\': + builder.WriteByte(c) + case 'b': + builder.WriteByte('\b') + case 'f': + builder.WriteByte('\f') + case 'n': + builder.WriteByte('\n') + case 'r': + builder.WriteByte('\r') + case 't': + builder.WriteByte('\t') + case 'u': + x, err := hexToString(token[i+3:len(token)-3], 4) + if err != nil { + return "", nil, err + } + builder.WriteString(x) + i += 4 + case 'U': + x, err := hexToString(token[i+3:len(token)-3], 8) + if err != nil { + return "", nil, err + } + builder.WriteString(x) + i += 8 + default: + return "", nil, fmt.Errorf("invalid escaped character: %#U", c) + } + } else { + builder.WriteByte(c) + } + } + + return builder.String(), rest, nil } func parseKey(b []byte) ([]byte, error) { From a466f0ca79e63d1f26369430a50d014937e866b6 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Sat, 6 Feb 2021 08:01:38 -0500 Subject: [PATCH 021/228] Multiline literal strings --- toml.go | 33 +++++++++++++++++++++++++-------- toml_test.go | 13 ++++++++++++- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/toml.go b/toml.go index bf6d290d..353c8ad8 100644 --- a/toml.go +++ b/toml.go @@ -92,25 +92,24 @@ func parseKeyval(b []byte) ([]byte, error) { func parseVal(b []byte) ([]byte, error) { // val = string / boolean / array / inline-table / date-time / float / integer + var err error c := b[0] switch c { // strings case '"': - var rest []byte - var err error if scanFollowsMultilineBasicStringDelimiter(b) { - _, rest, err = parseMultilineBasicString(b) + _, b, err = parseMultilineBasicString(b) } else { - _, rest, err = parseBasicString(b) + _, b, err = parseBasicString(b) } - return rest, err + return b, err case '\'': if scanFollowsMultilineLiteralStringDelimiter(b) { - return parseMultilineLiteralString(b) + _, b, err = parseMultilineLiteralString(b) } - _, rest, err := scanLiteralString(b) - return rest, err + _, b, err = scanLiteralString(b) + return b, err // TODO boolean // TODO array @@ -127,6 +126,24 @@ func parseVal(b []byte) ([]byte, error) { } } +func parseMultilineLiteralString(b []byte) (string, []byte, error) { + token, rest, err := scanMultilineLiteralString(b) + if err != nil { + return "", nil, err + } + + i := 3 + + // skip the immediate new line + if token[i] == '\n' { + i++ + } else if token[i] == '\r' && token[i+1] == '\n' { + i += 2 + } + + return string(token[i : len(b)-3]), rest, err +} + func parseMultilineBasicString(b []byte) (string, []byte, error) { //ml-basic-string = ml-basic-string-delim [ newline ] ml-basic-body //ml-basic-string-delim diff --git a/toml_test.go b/toml_test.go index 446b60ae..f065c188 100644 --- a/toml_test.go +++ b/toml_test.go @@ -39,7 +39,7 @@ a = false`, `[[foo]]`, } -func TestParse(t *testing.T) { +func TestScan(t *testing.T) { for i, data := range inputs { t.Run(fmt.Sprintf("example %d", i), func(t *testing.T) { fmt.Printf("input:\n\t`%s`\n", data) @@ -55,6 +55,17 @@ func TestParse(t *testing.T) { } } +func TestParse(t *testing.T) { + for i, data := range inputs { + t.Run(fmt.Sprintf("example %d", i), func(t *testing.T) { + fmt.Printf("input:\n\t`%s`\n", data) + b := []byte(data) + err := parse(b) + require.NoError(t, err) + }) + } +} + type noopParser struct { } From 540c2a7b5983ffc635cb44b0bf9c989eaec3d073 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Sat, 6 Feb 2021 08:54:40 -0500 Subject: [PATCH 022/228] Fix parsing bugs + boolean impl --- scanner.go | 25 ++++++++++++++++++++++--- toml.go | 35 ++++++++++++++++++++++++----------- toml_test.go | 44 ++++++++++++++++++++++---------------------- 3 files changed, 68 insertions(+), 36 deletions(-) diff --git a/scanner.go b/scanner.go index 5ffb7c46..99d9f0a1 100644 --- a/scanner.go +++ b/scanner.go @@ -23,6 +23,25 @@ var scanFollowsFalse = scanFollows([]byte{'f', 'a', 'l', 's', 'e'}) var scanFollowsArrayTableBegin = scanFollows([]byte{arrayOrTableBegin, arrayOrTableBegin}) var scanFollowsArrayTableEnd = scanFollows([]byte{arrayOrTableEnd, arrayOrTableEnd}) +func scanNewline(b []byte) ([]byte, []byte, error) { + if len(b) == 0 { + return nil, nil, fmt.Errorf("not enough bytes for new line") + } + if b[0] == '\n' { + return b[:1], b[1:], nil + } + if b[0] == '\r' { + if len(b) < 2 { + return nil, nil, fmt.Errorf("not enough bytes for windows newline") + } + if b[1] == '\n' { + return b[:2], b[2:], nil + } + return nil, nil, unexpectedCharacter{r: '\n', b: b[2:]} + } + return nil, nil, unexpectedCharacter{b: b} +} + const ( dot = '.' equal = '=' @@ -94,7 +113,7 @@ func scan(b []byte) ([]byte, []byte, error) { func scanUnquotedKey(b []byte) ([]byte, []byte, error) { //unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _ - for i := 1; i < len(b); i++ { + for i := 0; i < len(b); i++ { if !isUnquotedKeyChar(b[i]) { return b[:i], b[i:], nil } @@ -153,7 +172,7 @@ func scanWindowsNewline(b []byte) ([]byte, []byte, error) { } func scanWhitespace(b []byte) ([]byte, []byte) { - for i := 1; i < len(b); i++ { + for i := 0; i < len(b); i++ { switch b[i] { case ' ', '\t': continue @@ -176,7 +195,7 @@ func scanComment(b []byte) ([]byte, []byte, error) { for i := 1; i < len(b); i++ { switch b[i] { case '\n': - return b[:i+1], b[i+1:], nil + return b[:i], b[i:], nil } } return b, nil, nil diff --git a/toml.go b/toml.go index 353c8ad8..6c6084d9 100644 --- a/toml.go +++ b/toml.go @@ -51,12 +51,20 @@ func parseExpression(b []byte) ([]byte, error) { _, rest, err := scanComment(b) return rest, err } + if b[0] == '\n' || b[0] == '\r' { + _, rest, err := scanNewline(b) + return rest, err + } + var err error if b[0] == '[' { // TODO: parse 'table' + panic("todo") } else { - rest, err := parseKeyval(b) - return rest, err + b, err = parseKeyval(b) + } + if err != nil { + return nil, err } b = parseWhitespace(b) @@ -107,11 +115,20 @@ func parseVal(b []byte) ([]byte, error) { case '\'': if scanFollowsMultilineLiteralStringDelimiter(b) { _, b, err = parseMultilineLiteralString(b) + } else { + _, b, err = scanLiteralString(b) } - _, b, err = scanLiteralString(b) return b, err - // TODO boolean - + case 't': + if !scanFollowsTrue(b) { + return nil, fmt.Errorf("expected 'true'") + } + return b[4:], nil + case 'f': + if !scanFollowsFalse(b) { + return nil, fmt.Errorf("expected 'false'") + } + return b[5:], nil // TODO array // TODO inline-table @@ -247,8 +264,8 @@ func parseKey(b []byte) ([]byte, error) { } for { - if len(b) > 0 && (b[0] == '.' || isWhitespace(b[0])) { - b = parseWhitespace(b) + b = parseWhitespace(b) + if len(b) > 0 && b[0] == '.' { b, err = expect('.', b) if err != nil { return nil, err @@ -266,10 +283,6 @@ func parseKey(b []byte) ([]byte, error) { return b, nil } -func isWhitespace(b byte) bool { - return b == ' ' || b == '\t' -} - func parseSimpleKey(b []byte) ([]byte, error) { //simple-key = quoted-key / unquoted-key //unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _ diff --git a/toml_test.go b/toml_test.go index f065c188..846e3809 100644 --- a/toml_test.go +++ b/toml_test.go @@ -35,7 +35,7 @@ var inputs = []string{ `[ test ]`, `[ "hello".world ]`, `[test] -a = false`, + a = false`, `[[foo]]`, } @@ -66,27 +66,27 @@ func TestParse(t *testing.T) { } } -type noopParser struct { -} - -func (n noopParser) ArrayTableBegin() {} -func (n noopParser) ArrayTableEnd() {} -func (n noopParser) StandardTableBegin() {} -func (n noopParser) StandardTableEnd() {} -func (n noopParser) InlineTableSeparator() {} -func (n noopParser) InlineTableBegin() {} -func (n noopParser) InlineTableEnd() {} -func (n noopParser) ArraySeparator() {} -func (n noopParser) ArrayBegin() {} -func (n noopParser) ArrayEnd() {} -func (n noopParser) Whitespace(b []byte) {} -func (n noopParser) Comment(b []byte) {} -func (n noopParser) UnquotedKey(b []byte) {} -func (n noopParser) LiteralString(b []byte) {} -func (n noopParser) BasicString(b []byte) {} -func (n noopParser) Dot(b []byte) {} -func (n noopParser) Boolean(b []byte) {} -func (n noopParser) Equal(b []byte) {} +//type noopParser struct { +//} +// +//func (n noopParser) ArrayTableBegin() {} +//func (n noopParser) ArrayTableEnd() {} +//func (n noopParser) StandardTableBegin() {} +//func (n noopParser) StandardTableEnd() {} +//func (n noopParser) InlineTableSeparator() {} +//func (n noopParser) InlineTableBegin() {} +//func (n noopParser) InlineTableEnd() {} +//func (n noopParser) ArraySeparator() {} +//func (n noopParser) ArrayBegin() {} +//func (n noopParser) ArrayEnd() {} +//func (n noopParser) Whitespace(b []byte) {} +//func (n noopParser) Comment(b []byte) {} +//func (n noopParser) UnquotedKey(b []byte) {} +//func (n noopParser) LiteralString(b []byte) {} +//func (n noopParser) BasicString(b []byte) {} +//func (n noopParser) Dot(b []byte) {} +//func (n noopParser) Boolean(b []byte) {} +//func (n noopParser) Equal(b []byte) {} // //func BenchmarkParseAll(b *testing.B) { From 165f65408d08405604fd0dabe034301d2da1b165 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Sat, 6 Feb 2021 09:09:41 -0500 Subject: [PATCH 023/228] Implement tables --- toml.go | 45 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/toml.go b/toml.go index 6c6084d9..e01aa3e6 100644 --- a/toml.go +++ b/toml.go @@ -58,8 +58,7 @@ func parseExpression(b []byte) ([]byte, error) { var err error if b[0] == '[' { - // TODO: parse 'table' - panic("todo") + b, err = parseTable(b) } else { b, err = parseKeyval(b) } @@ -77,6 +76,48 @@ func parseExpression(b []byte) ([]byte, error) { return b, nil } +func parseTable(b []byte) ([]byte, error) { + //table = std-table / array-table + if len(b) > 1 && b[1] == '[' { + return parseArrayTable(b) + } + return parseStdTable(b) +} + +func parseArrayTable(b []byte) ([]byte, error) { + //array-table = array-table-open key array-table-close + //array-table-open = %x5B.5B ws ; [[ Double left square bracket + //array-table-close = ws %x5D.5D ; ]] Double right square bracket + + b = b[2:] + b = parseWhitespace(b) + b, err := parseKey(b) + if err != nil { + return nil, err + } + b = parseWhitespace(b) + b, err = expect(']', b) + if err != nil { + return nil, err + } + return expect(']', b) +} + +func parseStdTable(b []byte) ([]byte, error) { + //std-table = std-table-open key std-table-close + //std-table-open = %x5B ws ; [ Left square bracket + //std-table-close = ws %x5D ; ] Right square bracket + + b = b[1:] + b = parseWhitespace(b) + b, err := parseKey(b) + if err != nil { + return nil, err + } + b = parseWhitespace(b) + return expect(']', b) +} + func parseKeyval(b []byte) ([]byte, error) { //keyval = key keyval-sep val From b1e11f82a99cd3ff9d613fa2e373fd7273b614f2 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Sat, 6 Feb 2021 09:27:24 -0500 Subject: [PATCH 024/228] Implement array values --- toml.go | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 74 insertions(+), 1 deletion(-) diff --git a/toml.go b/toml.go index e01aa3e6..0abbee79 100644 --- a/toml.go +++ b/toml.go @@ -140,6 +140,9 @@ func parseKeyval(b []byte) ([]byte, error) { func parseVal(b []byte) ([]byte, error) { // val = string / boolean / array / inline-table / date-time / float / integer + if len(b) == 0 { + return nil, fmt.Errorf("expected value, not eof") + } var err error c := b[0] @@ -170,7 +173,8 @@ func parseVal(b []byte) ([]byte, error) { return nil, fmt.Errorf("expected 'false'") } return b[5:], nil - // TODO array + case '[': + return parseValArray(b) // TODO inline-table @@ -184,6 +188,75 @@ func parseVal(b []byte) ([]byte, error) { } } +func parseValArray(b []byte) ([]byte, error) { + //array = array-open [ array-values ] ws-comment-newline array-close + //array-open = %x5B ; [ + //array-close = %x5D ; ] + //array-values = ws-comment-newline val ws-comment-newline array-sep array-values + //array-values =/ ws-comment-newline val ws-comment-newline [ array-sep ] + //array-sep = %x2C ; , Comma + //ws-comment-newline = *( wschar / [ comment ] newline ) + + b = b[1:] + + first := true + var err error + for len(b) > 0 { + b, err = parseOptionalWhitespaceCommentNewline(b) + if err != nil { + return nil, err + } + + if len(b) == 0 { + return nil, unexpectedCharacter{b: b} + } + + if b[0] == ']' { + break + } + if b[0] == ',' { + if first { + return nil, fmt.Errorf("array cannot start with comma") + } + b = b[1:] + b, err = parseOptionalWhitespaceCommentNewline(b) + if err != nil { + return nil, err + } + } + + b, err = parseVal(b) + if err != nil { + return nil, err + } + b, err = parseOptionalWhitespaceCommentNewline(b) + if err != nil { + return nil, err + } + first = false + } + + return expect(']', b) +} + +func parseOptionalWhitespaceCommentNewline(b []byte) ([]byte, error) { + var err error + b = parseWhitespace(b) + if len(b) > 0 && b[0] == '#' { + _, b, err = scanComment(b) + if err != nil { + return nil, err + } + } + if len(b) > 0 && (b[0] == '\n' || b[0] == '\r') { + b, err = parseNewline(b) + if err != nil { + return nil, err + } + } + return b, nil +} + func parseMultilineLiteralString(b []byte) (string, []byte, error) { token, rest, err := scanMultilineLiteralString(b) if err != nil { From 9fa2fd413d76491c861c174d1a8a2a1cf6be8695 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Sat, 6 Feb 2021 09:33:20 -0500 Subject: [PATCH 025/228] Implement inline tables --- toml.go | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/toml.go b/toml.go index 0abbee79..ffe1b1ab 100644 --- a/toml.go +++ b/toml.go @@ -175,8 +175,8 @@ func parseVal(b []byte) ([]byte, error) { return b[5:], nil case '[': return parseValArray(b) - - // TODO inline-table + case '{': + return parseInlineTable(b) // TODO date-time @@ -188,6 +188,37 @@ func parseVal(b []byte) ([]byte, error) { } } +func parseInlineTable(b []byte) ([]byte, error) { + //inline-table = inline-table-open [ inline-table-keyvals ] inline-table-close + //inline-table-open = %x7B ws ; { + //inline-table-close = ws %x7D ; } + //inline-table-sep = ws %x2C ws ; , Comma + //inline-table-keyvals = keyval [ inline-table-sep inline-table-keyvals ] + + b = b[1:] + + first := true + var err error + for len(b) > 0 { + b = parseWhitespace(b) + if b[0] == '}' { + break + } + + if !first { + b, err = expect(',', b) + if err != nil { + return nil, err + } + b = parseWhitespace(b) + } + b, err = parseKeyval(b) + + first = false + } + return expect('}', b) +} + func parseValArray(b []byte) ([]byte, error) { //array = array-open [ array-values ] ws-comment-newline array-close //array-open = %x5B ; [ From 89052d60b40f0482e1c2b505a095f39577a7af2f Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Sat, 6 Feb 2021 23:20:26 -0500 Subject: [PATCH 026/228] Very beginning of unmarshaler + builder interface --- parser.go | 1 + scanner.go | 71 ---------- toml_test.go | 105 -------------- toml.go => unmarshal.go | 295 +++++++++++++++++++++++++++++----------- unmarshal_test.go | 34 +++++ 5 files changed, 250 insertions(+), 256 deletions(-) create mode 100644 parser.go delete mode 100644 toml_test.go rename toml.go => unmarshal.go (63%) create mode 100644 unmarshal_test.go diff --git a/parser.go b/parser.go new file mode 100644 index 00000000..f9fa173c --- /dev/null +++ b/parser.go @@ -0,0 +1 @@ +package toml diff --git a/scanner.go b/scanner.go index 99d9f0a1..4786b260 100644 --- a/scanner.go +++ b/scanner.go @@ -20,8 +20,6 @@ var scanFollowsMultilineBasicStringDelimiter = scanFollows([]byte{'"', '"', '"'} var scanFollowsMultilineLiteralStringDelimiter = scanFollows([]byte{'\'', '\'', '\''}) var scanFollowsTrue = scanFollows([]byte{'t', 'r', 'u', 'e'}) var scanFollowsFalse = scanFollows([]byte{'f', 'a', 'l', 's', 'e'}) -var scanFollowsArrayTableBegin = scanFollows([]byte{arrayOrTableBegin, arrayOrTableBegin}) -var scanFollowsArrayTableEnd = scanFollows([]byte{arrayOrTableEnd, arrayOrTableEnd}) func scanNewline(b []byte) ([]byte, []byte, error) { if len(b) == 0 { @@ -42,75 +40,6 @@ func scanNewline(b []byte) ([]byte, []byte, error) { return nil, nil, unexpectedCharacter{b: b} } -const ( - dot = '.' - equal = '=' - comma = ',' - inlineTableBegin = '{' - inlineTableEnd = '}' - comment = '#' - arrayOrTableBegin = '[' - arrayOrTableEnd = ']' -) - -// scan returns a []byte containing the next lexical token, bytes left, and an error. -// -// eof is signaled by an empty token and nil error. -func scan(b []byte) ([]byte, []byte, error) { - if len(b) == 0 { - return b, b, nil - } - - switch b[0] { - case dot, equal, inlineTableBegin, inlineTableEnd, comma: - return b[:1], b[1:], nil - case '"': - if scanFollowsMultilineBasicStringDelimiter(b) { - return scanMultilineBasicString(b) - } - return scanBasicString(b) - case '\'': - if scanFollowsMultilineLiteralStringDelimiter(b) { - return scanMultilineLiteralString(b) - } - return scanLiteralString(b) - case comment: - return scanComment(b) - case ' ', '\t': - data, rest := scanWhitespace(b) - return data, rest, nil - case '\r': - return scanWindowsNewline(b) - case '\n': - return b[:1], b[1:], nil - case 't': - if scanFollowsTrue(b) { - return b[:4], b[4:], nil - } - case 'f': - if scanFollowsFalse(b) { - return b[:5], b[5:], nil - } - case arrayOrTableBegin: - if scanFollowsArrayTableBegin(b) { - return b[:2], b[2:], nil - } - return b[:1], b[1:], nil - case arrayOrTableEnd: - if scanFollowsArrayTableEnd(b) { - return b[:2], b[2:], nil - } - return b[:1], b[1:], nil - } - - if isUnquotedKeyChar(b[0]) { - return scanUnquotedKey(b) - } - - // TODO: numbers, date-time - panic("unhandled scan") -} - func scanUnquotedKey(b []byte) ([]byte, []byte, error) { //unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _ for i := 0; i < len(b); i++ { diff --git a/toml_test.go b/toml_test.go deleted file mode 100644 index 846e3809..00000000 --- a/toml_test.go +++ /dev/null @@ -1,105 +0,0 @@ -package toml - -import ( - "fmt" - "testing" - - "github.com/stretchr/testify/require" -) - -var inputs = []string{ - ` #foo`, - `#foo`, - `#`, - "\n\n\n", - "#one\n # two \n", - `a = false`, - `abc = false`, - ` abc = false # foo`, - `'abc' = false`, - `"foo bar" = false`, - `"hello\tworld" = false`, - `"hello \u1234 foo" = false`, - `a.b.c = false`, - `a."b".c = true`, - `a = "foo"`, - `b = 'sample thingy'`, - `a = []`, - `b = ["foo"]`, - `c = [[[]]]`, - `d = ["foo","bar"]`, - `d = ["foo", "test"]`, - `d = {}`, - `e = {f = "bar"}`, - `[foo]`, - `[ test ]`, - `[ "hello".world ]`, - `[test] - a = false`, - `[[foo]]`, -} - -func TestScan(t *testing.T) { - for i, data := range inputs { - t.Run(fmt.Sprintf("example %d", i), func(t *testing.T) { - fmt.Printf("input:\n\t`%s`\n", data) - b := []byte(data) - var token []byte - var err error - for len(b) > 0 { - token, b, err = scan(b) - require.NoError(t, err) - fmt.Printf("token => '%s'\n", string(token)) - } - }) - } -} - -func TestParse(t *testing.T) { - for i, data := range inputs { - t.Run(fmt.Sprintf("example %d", i), func(t *testing.T) { - fmt.Printf("input:\n\t`%s`\n", data) - b := []byte(data) - err := parse(b) - require.NoError(t, err) - }) - } -} - -//type noopParser struct { -//} -// -//func (n noopParser) ArrayTableBegin() {} -//func (n noopParser) ArrayTableEnd() {} -//func (n noopParser) StandardTableBegin() {} -//func (n noopParser) StandardTableEnd() {} -//func (n noopParser) InlineTableSeparator() {} -//func (n noopParser) InlineTableBegin() {} -//func (n noopParser) InlineTableEnd() {} -//func (n noopParser) ArraySeparator() {} -//func (n noopParser) ArrayBegin() {} -//func (n noopParser) ArrayEnd() {} -//func (n noopParser) Whitespace(b []byte) {} -//func (n noopParser) Comment(b []byte) {} -//func (n noopParser) UnquotedKey(b []byte) {} -//func (n noopParser) LiteralString(b []byte) {} -//func (n noopParser) BasicString(b []byte) {} -//func (n noopParser) Dot(b []byte) {} -//func (n noopParser) Boolean(b []byte) {} -//func (n noopParser) Equal(b []byte) {} - -// -//func BenchmarkParseAll(b *testing.B) { -// b.ReportAllocs() -// -// for i := 0; i < b.N; i++ { -// for _, data := range inputs { -// p := noopParser{} -// l := lexer{parser: &p, data: []byte(data)} -// err := l.run() -// if err != nil { -// b.Fatalf("error: %s", err) -// } -// } -// } -//} diff --git a/toml.go b/unmarshal.go similarity index 63% rename from toml.go rename to unmarshal.go index ffe1b1ab..fcaeb95e 100644 --- a/toml.go +++ b/unmarshal.go @@ -1,23 +1,147 @@ package toml import ( + "bytes" "encoding/hex" "fmt" - "strings" + "reflect" ) -func parse(b []byte) error { - b, err := parseExpression(b) +func Unmarshal(data []byte, v interface{}) error { + if v == nil { + return fmt.Errorf("cannot unmarshal to nil target") + } + rv := reflect.ValueOf(v) + if rv.Kind() != reflect.Ptr { + return fmt.Errorf("can only marshal to pointer, not %s", rv.Kind()) + } + + u := &unmarshaler{stack: []reflect.Value{rv.Elem()}} + parseErr := parser{builder: u}.parse(data) + if parseErr != nil { + return parseErr + } + return u.err +} + +type unmarshaler struct { + // Each stack frame is a pointer to the root object that should be + // considered when settings values. + // It at least contains the root object passed to Unmarshal. + stack []reflect.Value + + // First error that appeared during the construction of the object. + // When set all callbacks are no-ops. + err error + + // State that indicates the parser is processing a [table] name. If false + // keys are interpreted as part of a key-value. + parsingTable bool +} + +func (u *unmarshaler) KeyValBegin() { + u.push(u.top()) +} + +func (u *unmarshaler) KeyValEnd() { + u.pop() +} + +func getOrCreateChild(parent reflect.Value, key string) (reflect.Value, error) { + if parent.Type().Kind() != reflect.Struct { + return reflect.Value{}, fmt.Errorf("value of type '%s' cannot have children", parent) + } + f := parent.FieldByName(key) + if !f.IsValid() { + // TODO: implement alternative names + return reflect.Value{}, fmt.Errorf("field '%s' not found", key) + } + // TODO create things + return f, nil +} + +func (u *unmarshaler) top() reflect.Value { + return u.stack[len(u.stack)-1] +} + +func (u *unmarshaler) push(v reflect.Value) { + u.stack = append(u.stack, v) +} + +func (u *unmarshaler) pop() { + u.stack = u.stack[:len(u.stack)-1] +} + +func (u *unmarshaler) replace(v reflect.Value) { + u.stack[len(u.stack)-1] = v +} + +func (u *unmarshaler) StringValue(v []byte) { + if u.err != nil { + return + } + u.top().SetString(string(v)) +} + +func (u *unmarshaler) SimpleKey(v []byte) { + if u.err != nil { + return + } + + target, err := getOrCreateChild(u.top(), string(v)) + if err != nil { + u.err = err + return + } + + u.replace(target) +} + +func (u *unmarshaler) StandardTableBegin() { + if u.err != nil { + return + } + + // tables are only top-level + u.stack = u.stack[:1] +} + +func (u *unmarshaler) StandardTableEnd() { + if u.err != nil { + return + } + + panic("implement me") +} + +type builder interface { + SimpleKey(v []byte) + + StandardTableBegin() + StandardTableEnd() + + KeyValBegin() + KeyValEnd() + + StringValue(v []byte) +} + +type parser struct { + builder builder +} + +func (p parser) parse(b []byte) error { + b, err := p.parseExpression(b) if err != nil { return err } for len(b) > 0 { - b, err = parseNewline(b) + b, err = p.parseNewline(b) if err != nil { return err } - b, err = parseExpression(b) + b, err = p.parseExpression(b) if err != nil { return err } @@ -25,7 +149,7 @@ func parse(b []byte) error { return nil } -func parseNewline(b []byte) ([]byte, error) { +func (p parser) parseNewline(b []byte) ([]byte, error) { if b[0] == '\n' { return b[1:], nil } @@ -36,12 +160,12 @@ func parseNewline(b []byte) ([]byte, error) { return nil, fmt.Errorf("expected newline but got %#U", b[0]) } -func parseExpression(b []byte) ([]byte, error) { +func (p parser) parseExpression(b []byte) ([]byte, error) { //expression = ws [ comment ] //expression =/ ws keyval ws [ comment ] //expression =/ ws table ws [ comment ] - b = parseWhitespace(b) + b = p.parseWhitespace(b) if len(b) == 0 { return b, nil @@ -58,15 +182,15 @@ func parseExpression(b []byte) ([]byte, error) { var err error if b[0] == '[' { - b, err = parseTable(b) + b, err = p.parseTable(b) } else { - b, err = parseKeyval(b) + b, err = p.parseKeyval(b) } if err != nil { return nil, err } - b = parseWhitespace(b) + b = p.parseWhitespace(b) if len(b) > 0 && b[0] == '#' { _, rest, err := scanComment(b) @@ -76,26 +200,26 @@ func parseExpression(b []byte) ([]byte, error) { return b, nil } -func parseTable(b []byte) ([]byte, error) { +func (p parser) parseTable(b []byte) ([]byte, error) { //table = std-table / array-table if len(b) > 1 && b[1] == '[' { - return parseArrayTable(b) + return p.parseArrayTable(b) } - return parseStdTable(b) + return p.parseStdTable(b) } -func parseArrayTable(b []byte) ([]byte, error) { +func (p parser) parseArrayTable(b []byte) ([]byte, error) { //array-table = array-table-open key array-table-close //array-table-open = %x5B.5B ws ; [[ Double left square bracket //array-table-close = ws %x5D.5D ; ]] Double right square bracket b = b[2:] - b = parseWhitespace(b) - b, err := parseKey(b) + b = p.parseWhitespace(b) + b, err := p.parseKey(b) if err != nil { return nil, err } - b = parseWhitespace(b) + b = p.parseWhitespace(b) b, err = expect(']', b) if err != nil { return nil, err @@ -103,42 +227,49 @@ func parseArrayTable(b []byte) ([]byte, error) { return expect(']', b) } -func parseStdTable(b []byte) ([]byte, error) { +func (p parser) parseStdTable(b []byte) ([]byte, error) { //std-table = std-table-open key std-table-close //std-table-open = %x5B ws ; [ Left square bracket //std-table-close = ws %x5D ; ] Right square bracket + p.builder.StandardTableBegin() + defer p.builder.StandardTableEnd() + b = b[1:] - b = parseWhitespace(b) - b, err := parseKey(b) + b = p.parseWhitespace(b) + b, err := p.parseKey(b) if err != nil { return nil, err } - b = parseWhitespace(b) + b = p.parseWhitespace(b) + return expect(']', b) } -func parseKeyval(b []byte) ([]byte, error) { +func (p parser) parseKeyval(b []byte) ([]byte, error) { //keyval = key keyval-sep val - b, err := parseKey(b) + p.builder.KeyValBegin() + defer p.builder.KeyValEnd() + + b, err := p.parseKey(b) if err != nil { return nil, err } //keyval-sep = ws %x3D ws ; = - b = parseWhitespace(b) + b = p.parseWhitespace(b) b, err = expect('=', b) if err != nil { return nil, err } - b = parseWhitespace(b) + b = p.parseWhitespace(b) - return parseVal(b) + return p.parseVal(b) } -func parseVal(b []byte) ([]byte, error) { +func (p parser) parseVal(b []byte) ([]byte, error) { // val = string / boolean / array / inline-table / date-time / float / integer if len(b) == 0 { return nil, fmt.Errorf("expected value, not eof") @@ -150,15 +281,19 @@ func parseVal(b []byte) ([]byte, error) { switch c { // strings case '"': + var v []byte if scanFollowsMultilineBasicStringDelimiter(b) { - _, b, err = parseMultilineBasicString(b) + v, b, err = p.parseMultilineBasicString(b) } else { - _, b, err = parseBasicString(b) + v, b, err = p.parseBasicString(b) + } + if err == nil { + p.builder.StringValue(v) } return b, err case '\'': if scanFollowsMultilineLiteralStringDelimiter(b) { - _, b, err = parseMultilineLiteralString(b) + _, b, err = p.parseMultilineLiteralString(b) } else { _, b, err = scanLiteralString(b) } @@ -174,9 +309,9 @@ func parseVal(b []byte) ([]byte, error) { } return b[5:], nil case '[': - return parseValArray(b) + return p.parseValArray(b) case '{': - return parseInlineTable(b) + return p.parseInlineTable(b) // TODO date-time @@ -188,7 +323,7 @@ func parseVal(b []byte) ([]byte, error) { } } -func parseInlineTable(b []byte) ([]byte, error) { +func (p parser) parseInlineTable(b []byte) ([]byte, error) { //inline-table = inline-table-open [ inline-table-keyvals ] inline-table-close //inline-table-open = %x7B ws ; { //inline-table-close = ws %x7D ; } @@ -200,7 +335,7 @@ func parseInlineTable(b []byte) ([]byte, error) { first := true var err error for len(b) > 0 { - b = parseWhitespace(b) + b = p.parseWhitespace(b) if b[0] == '}' { break } @@ -210,16 +345,19 @@ func parseInlineTable(b []byte) ([]byte, error) { if err != nil { return nil, err } - b = parseWhitespace(b) + b = p.parseWhitespace(b) + } + b, err = p.parseKeyval(b) + if err != nil { + return nil, err } - b, err = parseKeyval(b) first = false } return expect('}', b) } -func parseValArray(b []byte) ([]byte, error) { +func (p parser) parseValArray(b []byte) ([]byte, error) { //array = array-open [ array-values ] ws-comment-newline array-close //array-open = %x5B ; [ //array-close = %x5D ; ] @@ -233,7 +371,7 @@ func parseValArray(b []byte) ([]byte, error) { first := true var err error for len(b) > 0 { - b, err = parseOptionalWhitespaceCommentNewline(b) + b, err = p.parseOptionalWhitespaceCommentNewline(b) if err != nil { return nil, err } @@ -250,17 +388,17 @@ func parseValArray(b []byte) ([]byte, error) { return nil, fmt.Errorf("array cannot start with comma") } b = b[1:] - b, err = parseOptionalWhitespaceCommentNewline(b) + b, err = p.parseOptionalWhitespaceCommentNewline(b) if err != nil { return nil, err } } - b, err = parseVal(b) + b, err = p.parseVal(b) if err != nil { return nil, err } - b, err = parseOptionalWhitespaceCommentNewline(b) + b, err = p.parseOptionalWhitespaceCommentNewline(b) if err != nil { return nil, err } @@ -270,9 +408,9 @@ func parseValArray(b []byte) ([]byte, error) { return expect(']', b) } -func parseOptionalWhitespaceCommentNewline(b []byte) ([]byte, error) { +func (p parser) parseOptionalWhitespaceCommentNewline(b []byte) ([]byte, error) { var err error - b = parseWhitespace(b) + b = p.parseWhitespace(b) if len(b) > 0 && b[0] == '#' { _, b, err = scanComment(b) if err != nil { @@ -280,7 +418,7 @@ func parseOptionalWhitespaceCommentNewline(b []byte) ([]byte, error) { } } if len(b) > 0 && (b[0] == '\n' || b[0] == '\r') { - b, err = parseNewline(b) + b, err = p.parseNewline(b) if err != nil { return nil, err } @@ -288,7 +426,7 @@ func parseOptionalWhitespaceCommentNewline(b []byte) ([]byte, error) { return b, nil } -func parseMultilineLiteralString(b []byte) (string, []byte, error) { +func (p parser) parseMultilineLiteralString(b []byte) (string, []byte, error) { token, rest, err := scanMultilineLiteralString(b) if err != nil { return "", nil, err @@ -306,7 +444,7 @@ func parseMultilineLiteralString(b []byte) (string, []byte, error) { return string(token[i : len(b)-3]), rest, err } -func parseMultilineBasicString(b []byte) (string, []byte, error) { +func (p parser) parseMultilineBasicString(b []byte) ([]byte, []byte, error) { //ml-basic-string = ml-basic-string-delim [ newline ] ml-basic-body //ml-basic-string-delim //ml-basic-string-delim = 3quotation-mark @@ -320,9 +458,9 @@ func parseMultilineBasicString(b []byte) (string, []byte, error) { token, rest, err := scanMultilineBasicString(b) if err != nil { - return "", nil, err + return nil, nil, err } - var builder strings.Builder + var builder bytes.Buffer i := 3 @@ -371,29 +509,29 @@ func parseMultilineBasicString(b []byte) (string, []byte, error) { case 'u': x, err := hexToString(token[i+3:len(token)-3], 4) if err != nil { - return "", nil, err + return nil, nil, err } builder.WriteString(x) i += 4 case 'U': x, err := hexToString(token[i+3:len(token)-3], 8) if err != nil { - return "", nil, err + return nil, nil, err } builder.WriteString(x) i += 8 default: - return "", nil, fmt.Errorf("invalid escaped character: %#U", c) + return nil, nil, fmt.Errorf("invalid escaped character: %#U", c) } } else { builder.WriteByte(c) } } - return builder.String(), rest, nil + return builder.Bytes(), rest, nil } -func parseKey(b []byte) ([]byte, error) { +func (p parser) parseKey(b []byte) ([]byte, error) { //key = simple-key / dotted-key //simple-key = quoted-key / unquoted-key // @@ -403,20 +541,20 @@ func parseKey(b []byte) ([]byte, error) { // //dot-sep = ws %x2E ws ; . Period - b, err := parseSimpleKey(b) + b, err := p.parseSimpleKey(b) if err != nil { return nil, err } for { - b = parseWhitespace(b) + b = p.parseWhitespace(b) if len(b) > 0 && b[0] == '.' { b, err = expect('.', b) if err != nil { return nil, err } - b = parseWhitespace(b) - b, err = parseSimpleKey(b) + b = p.parseWhitespace(b) + b, err = p.parseSimpleKey(b) if err != nil { return nil, err } @@ -428,7 +566,7 @@ func parseKey(b []byte) ([]byte, error) { return b, nil } -func parseSimpleKey(b []byte) ([]byte, error) { +func (p parser) parseSimpleKey(b []byte) (rest []byte, err error) { //simple-key = quoted-key / unquoted-key //unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _ //quoted-key = basic-string / literal-string @@ -437,24 +575,21 @@ func parseSimpleKey(b []byte) ([]byte, error) { return nil, unexpectedCharacter{b: b} } + var v []byte if b[0] == '\'' { - _, rest, err := scanLiteralString(b) - return rest, err - } - if b[0] == '"' { - _, rest, err := parseBasicString(b) - return rest, err - } - - if isUnquotedKeyChar(b[0]) { - _, rest, err := scanUnquotedKey(b) - return rest, err + v, rest, err = scanLiteralString(b) + } else if b[0] == '"' { + v, rest, err = p.parseBasicString(b) + } else if isUnquotedKeyChar(b[0]) { + v, rest, err = scanUnquotedKey(b) + } else { + return nil, unexpectedCharacter{b: b} } - - return nil, unexpectedCharacter{b: b} + p.builder.SimpleKey(v) + return } -func parseBasicString(b []byte) (string, []byte, error) { +func (p parser) parseBasicString(b []byte) ([]byte, []byte, error) { //basic-string = quotation-mark *basic-char quotation-mark //quotation-mark = %x22 ; " //basic-char = basic-unescaped / escaped @@ -472,9 +607,9 @@ func parseBasicString(b []byte) (string, []byte, error) { token, rest, err := scanBasicString(b) if err != nil { - return "", nil, err + return nil, nil, err } - var builder strings.Builder + var builder bytes.Buffer // The scanner ensures that the token starts and ends with quotes and that // escapes are balanced. @@ -499,26 +634,26 @@ func parseBasicString(b []byte) (string, []byte, error) { case 'u': x, err := hexToString(token[i+1:len(token)-1], 4) if err != nil { - return "", nil, err + return nil, nil, err } builder.WriteString(x) i += 4 case 'U': x, err := hexToString(token[i+1:len(token)-1], 8) if err != nil { - return "", nil, err + return nil, nil, err } builder.WriteString(x) i += 8 default: - return "", nil, fmt.Errorf("invalid escaped character: %#U", c) + return nil, nil, fmt.Errorf("invalid escaped character: %#U", c) } } else { builder.WriteByte(c) } } - return builder.String(), rest, nil + return builder.Bytes(), rest, nil } func hexToString(b []byte, length int) (string, error) { @@ -533,7 +668,7 @@ func hexToString(b []byte, length int) (string, error) { return string(b), nil } -func parseWhitespace(b []byte) []byte { +func (p parser) parseWhitespace(b []byte) []byte { //ws = *wschar //wschar = %x20 ; Space //wschar =/ %x09 ; Horizontal tab diff --git a/unmarshal_test.go b/unmarshal_test.go new file mode 100644 index 00000000..ad116ba8 --- /dev/null +++ b/unmarshal_test.go @@ -0,0 +1,34 @@ +package toml + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestUnmarshalSimple(t *testing.T) { + x := struct{ Foo string }{} + err := Unmarshal([]byte(`Foo = "hello"`), &x) + require.NoError(t, err) + assert.Equal(t, "hello", x.Foo) +} + +func TestUnmarshalNestedStructs(t *testing.T) { + x := struct{ Foo struct{ Bar string } }{} + err := Unmarshal([]byte(`Foo.Bar = "hello"`), &x) + require.NoError(t, err) + assert.Equal(t, "hello", x.Foo.Bar) +} + +func TestUnmarshalNestedStructsMultipleExpressions(t *testing.T) { + x := struct { + A struct{ B string } + C string + }{} + err := Unmarshal([]byte(`A.B = "hello" +C = "test"`), &x) + require.NoError(t, err) + assert.Equal(t, "hello", x.A.B) + assert.Equal(t, "test", x.C) +} From bd8df2464648fa99c00d177058e87542602486ed Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Sun, 7 Feb 2021 18:30:33 -0500 Subject: [PATCH 027/228] Parse tables --- unmarshal.go | 29 ++++++++++++++++++++--------- unmarshal_test.go | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 9 deletions(-) diff --git a/unmarshal.go b/unmarshal.go index fcaeb95e..04a4237d 100644 --- a/unmarshal.go +++ b/unmarshal.go @@ -104,14 +104,14 @@ func (u *unmarshaler) StandardTableBegin() { // tables are only top-level u.stack = u.stack[:1] + + u.push(u.top()) } func (u *unmarshaler) StandardTableEnd() { if u.err != nil { return } - - panic("implement me") } type builder interface { @@ -176,8 +176,7 @@ func (p parser) parseExpression(b []byte) ([]byte, error) { return rest, err } if b[0] == '\n' || b[0] == '\r' { - _, rest, err := scanNewline(b) - return rest, err + return b, nil } var err error @@ -292,10 +291,14 @@ func (p parser) parseVal(b []byte) ([]byte, error) { } return b, err case '\'': + var v []byte if scanFollowsMultilineLiteralStringDelimiter(b) { - _, b, err = p.parseMultilineLiteralString(b) + v, b, err = p.parseMultilineLiteralString(b) } else { - _, b, err = scanLiteralString(b) + v, b, err = p.parseLiteralString(b) + } + if err == nil { + p.builder.StringValue(v) } return b, err case 't': @@ -323,6 +326,14 @@ func (p parser) parseVal(b []byte) ([]byte, error) { } } +func (p parser) parseLiteralString(b []byte) ([]byte, []byte, error) { + v, rest, err := scanLiteralString(b) + if err != nil { + return nil, nil, err + } + return v[1 : len(v)-1], rest, nil +} + func (p parser) parseInlineTable(b []byte) ([]byte, error) { //inline-table = inline-table-open [ inline-table-keyvals ] inline-table-close //inline-table-open = %x7B ws ; { @@ -426,10 +437,10 @@ func (p parser) parseOptionalWhitespaceCommentNewline(b []byte) ([]byte, error) return b, nil } -func (p parser) parseMultilineLiteralString(b []byte) (string, []byte, error) { +func (p parser) parseMultilineLiteralString(b []byte) ([]byte, []byte, error) { token, rest, err := scanMultilineLiteralString(b) if err != nil { - return "", nil, err + return nil, nil, err } i := 3 @@ -441,7 +452,7 @@ func (p parser) parseMultilineLiteralString(b []byte) (string, []byte, error) { i += 2 } - return string(token[i : len(b)-3]), rest, err + return token[i : len(b)-3], rest, err } func (p parser) parseMultilineBasicString(b []byte) ([]byte, []byte, error) { diff --git a/unmarshal_test.go b/unmarshal_test.go index ad116ba8..3b1439f9 100644 --- a/unmarshal_test.go +++ b/unmarshal_test.go @@ -32,3 +32,36 @@ C = "test"`), &x) assert.Equal(t, "hello", x.A.B) assert.Equal(t, "test", x.C) } + +func TestUnmarshalTable(t *testing.T) { + x := struct { + Foo struct { + A string + B string + C string + } + Bar struct { + D string + } + E string + }{} + err := Unmarshal([]byte(` + +E = "E" +Foo.C = "C" + +[Foo] +A = "A" +B = 'B' + +[Bar] +D = "D" + +`), &x) + require.NoError(t, err) + assert.Equal(t, "A", x.Foo.A) + assert.Equal(t, "B", x.Foo.B) + assert.Equal(t, "C", x.Foo.C) + assert.Equal(t, "D", x.Bar.D) + assert.Equal(t, "E", x.E) +} From a197513ce7b35275ea67b81c122c8e6d05c3acb2 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Mon, 8 Feb 2021 09:08:42 -0500 Subject: [PATCH 028/228] Simple table array --- scanner.go | 19 ------- unmarshal.go | 92 +++++++++++++++++++++++++++++----- unmarshal_test.go | 123 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 203 insertions(+), 31 deletions(-) diff --git a/scanner.go b/scanner.go index 4786b260..8c92c00d 100644 --- a/scanner.go +++ b/scanner.go @@ -21,25 +21,6 @@ var scanFollowsMultilineLiteralStringDelimiter = scanFollows([]byte{'\'', '\'', var scanFollowsTrue = scanFollows([]byte{'t', 'r', 'u', 'e'}) var scanFollowsFalse = scanFollows([]byte{'f', 'a', 'l', 's', 'e'}) -func scanNewline(b []byte) ([]byte, []byte, error) { - if len(b) == 0 { - return nil, nil, fmt.Errorf("not enough bytes for new line") - } - if b[0] == '\n' { - return b[:1], b[1:], nil - } - if b[0] == '\r' { - if len(b) < 2 { - return nil, nil, fmt.Errorf("not enough bytes for windows newline") - } - if b[1] == '\n' { - return b[:2], b[2:], nil - } - return nil, nil, unexpectedCharacter{r: '\n', b: b[2:]} - } - return nil, nil, unexpectedCharacter{b: b} -} - func scanUnquotedKey(b []byte) ([]byte, []byte, error) { //unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _ for i := 0; i < len(b); i++ { diff --git a/unmarshal.go b/unmarshal.go index 04a4237d..310ef714 100644 --- a/unmarshal.go +++ b/unmarshal.go @@ -34,9 +34,63 @@ type unmarshaler struct { // When set all callbacks are no-ops. err error - // State that indicates the parser is processing a [table] name. If false - // keys are interpreted as part of a key-value. - parsingTable bool + // State that indicates the parser is processing a [[table-array]] name. + // If false keys are interpreted as part of a key-value or [table]. + parsingTableArray bool + + // Table Arrays need a buffer of keys because we need to know which one is + // the last one, as it may result in creating a new element in the array. + arrayTableKey [][]byte +} + +func (u *unmarshaler) ArrayTableBegin() { + if u.err != nil { + return + } + + u.parsingTableArray = true +} + +func (u *unmarshaler) ArrayTableEnd() { + if u.err != nil { + return + } + + u.parsingTableArray = false + + u.stack = u.stack[:1] + + parent := u.top() + for _, k := range u.arrayTableKey { + switch parent.Type().Kind() { + case reflect.Slice: + l := parent.Len() + parent = parent.Index(l - 1) + case reflect.Struct: + default: + u.err = fmt.Errorf("value of type '%s' cannot have children", parent) + return + } + + f := parent.FieldByName(string(k)) + if !f.IsValid() { + // TODO: implement alternative names + u.err = fmt.Errorf("field '%s' not found", string(k)) + return + } + parent = f + } + + if parent.Type().Kind() != reflect.Slice { + u.err = fmt.Errorf("array table key is not a slice") + return + } + + n := reflect.New(parent.Type().Elem()) + parent.Set(reflect.Append(parent, n.Elem())) + last := parent.Index(parent.Len() - 1) + u.push(last) + u.arrayTableKey = u.arrayTableKey[:0] } func (u *unmarshaler) KeyValBegin() { @@ -47,10 +101,17 @@ func (u *unmarshaler) KeyValEnd() { u.pop() } -func getOrCreateChild(parent reflect.Value, key string) (reflect.Value, error) { - if parent.Type().Kind() != reflect.Struct { +func (u *unmarshaler) getOrCreateChild(key string) (reflect.Value, error) { + parent := u.top() + switch parent.Type().Kind() { + case reflect.Slice: + l := parent.Len() + parent = parent.Index(l - 1) + case reflect.Struct: + default: return reflect.Value{}, fmt.Errorf("value of type '%s' cannot have children", parent) } + f := parent.FieldByName(key) if !f.IsValid() { // TODO: implement alternative names @@ -88,13 +149,16 @@ func (u *unmarshaler) SimpleKey(v []byte) { return } - target, err := getOrCreateChild(u.top(), string(v)) - if err != nil { - u.err = err - return + if u.parsingTableArray { + u.arrayTableKey = append(u.arrayTableKey, v) + } else { + target, err := u.getOrCreateChild(string(v)) + if err != nil { + u.err = err + return + } + u.replace(target) } - - u.replace(target) } func (u *unmarshaler) StandardTableBegin() { @@ -119,7 +183,8 @@ type builder interface { StandardTableBegin() StandardTableEnd() - + ArrayTableBegin() + ArrayTableEnd() KeyValBegin() KeyValEnd() @@ -212,6 +277,9 @@ func (p parser) parseArrayTable(b []byte) ([]byte, error) { //array-table-open = %x5B.5B ws ; [[ Double left square bracket //array-table-close = ws %x5D.5D ; ]] Double right square bracket + p.builder.ArrayTableBegin() + defer p.builder.ArrayTableEnd() + b = b[2:] b = p.parseWhitespace(b) b, err := p.parseKey(b) diff --git a/unmarshal_test.go b/unmarshal_test.go index 3b1439f9..13190274 100644 --- a/unmarshal_test.go +++ b/unmarshal_test.go @@ -65,3 +65,126 @@ D = "D" assert.Equal(t, "D", x.Bar.D) assert.Equal(t, "E", x.E) } + +func TestUnmarshalDoesNotEraseBaseStruct(t *testing.T) { + x := struct { + A string + B string + }{ + A: "preset", + } + err := Unmarshal([]byte(`B = "data"`), &x) + + require.NoError(t, err) + assert.Equal(t, "preset", x.A) + assert.Equal(t, "data", x.B) +} + +func TestArrayTableSimple(t *testing.T) { + doc := ` +[[Products]] +Name = "Hammer" + +[[Products]] +Name = "Nail" +` + + type Product struct { + Name string + } + + type Data struct { + Products []Product + } + + x := Data{} + err := Unmarshal([]byte(doc), &x) + + require.NoError(t, err) + + expected := Data{ + Products: []Product{ + { + Name: "Hammer", + }, + { + Name: "Nail", + }, + }, + } + + assert.Equal(t, expected, x) +} + +//func TestUnmarshalArrayTablesMultiple(t *testing.T) { +// doc := ` +//[[Products]] +//Name = "Hammer" +//Sku = "738594937" +// +//[[Products]] # empty table within the array +// +//[[Products]] +//Name = "Nail" +//Sku = "284758393" +// +//Color = "gray" +//` +// +// type Product struct { +// Name string +// Sku string +// Color string +// } +// +// type Data struct { +// Products []Product +// } +// +// x := Data{} +// err := Unmarshal([]byte(doc), &x) +// +// require.NoError(t, err) +// +// expected := Data{ +// Products: []Product{ +// { +// Name: "Hammer", +// Sku: "738594937", +// }, +// {}, +// { +// Name: "Nail", +// Sku: "284758393", +// Color: "gray", +// }, +// }, +// } +// +// assert.Equal(t, expected, x) +//} + +//func TestUnmarshalArrayTablesNested(t *testing.T) { +// doc := ` +//[[Fruits]] +//Name = "apple" +// +//[Fruits.Physical] # subtable +//Color = "red" +//Shape = "round" +// +//[[Fruits.Varieties]] # nested array of tables +//Name = "red delicious" +// +//[[fruits.varieties]] +//Name = "granny smith" +// +// +//[[fruits]] +//Name = "banana" +// +//[[fruits.varieties]] +//Name = "plantain" +//` +// +//} From 70d41bd75052b8b4ed8860b728f3dba2cdfc53e3 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Mon, 8 Feb 2021 09:16:26 -0500 Subject: [PATCH 029/228] Add more tests for unmarshal array tables --- unmarshal_test.go | 189 ++++++++++++++++++++++++++++------------------ 1 file changed, 117 insertions(+), 72 deletions(-) diff --git a/unmarshal_test.go b/unmarshal_test.go index 13190274..883a2e66 100644 --- a/unmarshal_test.go +++ b/unmarshal_test.go @@ -116,75 +116,120 @@ Name = "Nail" assert.Equal(t, expected, x) } -//func TestUnmarshalArrayTablesMultiple(t *testing.T) { -// doc := ` -//[[Products]] -//Name = "Hammer" -//Sku = "738594937" -// -//[[Products]] # empty table within the array -// -//[[Products]] -//Name = "Nail" -//Sku = "284758393" -// -//Color = "gray" -//` -// -// type Product struct { -// Name string -// Sku string -// Color string -// } -// -// type Data struct { -// Products []Product -// } -// -// x := Data{} -// err := Unmarshal([]byte(doc), &x) -// -// require.NoError(t, err) -// -// expected := Data{ -// Products: []Product{ -// { -// Name: "Hammer", -// Sku: "738594937", -// }, -// {}, -// { -// Name: "Nail", -// Sku: "284758393", -// Color: "gray", -// }, -// }, -// } -// -// assert.Equal(t, expected, x) -//} - -//func TestUnmarshalArrayTablesNested(t *testing.T) { -// doc := ` -//[[Fruits]] -//Name = "apple" -// -//[Fruits.Physical] # subtable -//Color = "red" -//Shape = "round" -// -//[[Fruits.Varieties]] # nested array of tables -//Name = "red delicious" -// -//[[fruits.varieties]] -//Name = "granny smith" -// -// -//[[fruits]] -//Name = "banana" -// -//[[fruits.varieties]] -//Name = "plantain" -//` -// -//} +func TestUnmarshalArrayTablesMultiple(t *testing.T) { + doc := ` +[[Products]] +Name = "Hammer" +Sku = "738594937" + +[[Products]] # empty table within the array + +[[Products]] +Name = "Nail" +Sku = "284758393" + +Color = "gray" +` + + type Product struct { + Name string + Sku string + Color string + } + + type Data struct { + Products []Product + } + + x := Data{} + err := Unmarshal([]byte(doc), &x) + + require.NoError(t, err) + + expected := Data{ + Products: []Product{ + { + Name: "Hammer", + Sku: "738594937", + }, + {}, + { + Name: "Nail", + Sku: "284758393", + Color: "gray", + }, + }, + } + + assert.Equal(t, expected, x) +} + +func TestUnmarshalArrayTablesNested(t *testing.T) { + doc := ` +[[Fruits]] +Name = "apple" + +[Fruits.Physical] # subtable +Color = "red" +Shape = "round" + +[[Fruits.Varieties]] # nested array of tables +Name = "red delicious" + +[[Fruits.Varieties]] +Name = "granny smith" + + +[[Fruits]] +Name = "banana" + +[[Fruits.Varieties]] +Name = "plantain" +` + type Variety struct { + Name string + } + + type Physical struct { + Color string + Shape string + } + + type Fruit struct { + Name string + Physical Physical + Varieties []Variety + } + + type Doc struct { + Fruits []Fruit + } + + x := Doc{} + err := Unmarshal([]byte(doc), &x) + require.NoError(t, err) + + expected := Doc{ + Fruits: []Fruit{ + { + Name: "apple", + Physical: Physical{ + Color: "red", + Shape: "round", + }, + Varieties: []Variety{ + {Name: "red delicious"}, + {Name: "granny smith"}, + }, + }, + { + Name: "banana", + Varieties: []Variety{ + {Name: "plantain"}, + }, + }, + }, + } + + assert.Equal(t, expected, x) +} From 0e8fd642039f913f55f690173dedc8a130a614b3 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Mon, 8 Feb 2021 09:18:42 -0500 Subject: [PATCH 030/228] Move tests out of the package --- unmarshal_test.go | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/unmarshal_test.go b/unmarshal_test.go index 883a2e66..6113d6b8 100644 --- a/unmarshal_test.go +++ b/unmarshal_test.go @@ -1,22 +1,23 @@ -package toml +package toml_test import ( "testing" + "github.com/pelletier/go-toml/v2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestUnmarshalSimple(t *testing.T) { x := struct{ Foo string }{} - err := Unmarshal([]byte(`Foo = "hello"`), &x) + err := toml.Unmarshal([]byte(`Foo = "hello"`), &x) require.NoError(t, err) assert.Equal(t, "hello", x.Foo) } func TestUnmarshalNestedStructs(t *testing.T) { x := struct{ Foo struct{ Bar string } }{} - err := Unmarshal([]byte(`Foo.Bar = "hello"`), &x) + err := toml.Unmarshal([]byte(`Foo.Bar = "hello"`), &x) require.NoError(t, err) assert.Equal(t, "hello", x.Foo.Bar) } @@ -26,7 +27,7 @@ func TestUnmarshalNestedStructsMultipleExpressions(t *testing.T) { A struct{ B string } C string }{} - err := Unmarshal([]byte(`A.B = "hello" + err := toml.Unmarshal([]byte(`A.B = "hello" C = "test"`), &x) require.NoError(t, err) assert.Equal(t, "hello", x.A.B) @@ -45,7 +46,7 @@ func TestUnmarshalTable(t *testing.T) { } E string }{} - err := Unmarshal([]byte(` + err := toml.Unmarshal([]byte(` E = "E" Foo.C = "C" @@ -73,7 +74,7 @@ func TestUnmarshalDoesNotEraseBaseStruct(t *testing.T) { }{ A: "preset", } - err := Unmarshal([]byte(`B = "data"`), &x) + err := toml.Unmarshal([]byte(`B = "data"`), &x) require.NoError(t, err) assert.Equal(t, "preset", x.A) @@ -98,7 +99,7 @@ Name = "Nail" } x := Data{} - err := Unmarshal([]byte(doc), &x) + err := toml.Unmarshal([]byte(doc), &x) require.NoError(t, err) @@ -142,7 +143,7 @@ Color = "gray" } x := Data{} - err := Unmarshal([]byte(doc), &x) + err := toml.Unmarshal([]byte(doc), &x) require.NoError(t, err) @@ -206,7 +207,7 @@ Name = "plantain" } x := Doc{} - err := Unmarshal([]byte(doc), &x) + err := toml.Unmarshal([]byte(doc), &x) require.NoError(t, err) expected := Doc{ From 3488a91effbc714621e45f0f0a1cc5a2ed69eedb Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Mon, 8 Feb 2021 20:38:53 -0500 Subject: [PATCH 031/228] Array values --- unmarshal.go | 10 +++++++++- unmarshal_test.go | 10 ++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/unmarshal.go b/unmarshal.go index 310ef714..b38cef72 100644 --- a/unmarshal.go +++ b/unmarshal.go @@ -141,7 +141,15 @@ func (u *unmarshaler) StringValue(v []byte) { if u.err != nil { return } - u.top().SetString(string(v)) + + t := u.top() + if t.Type().Kind() == reflect.Slice { + s := reflect.ValueOf(string(v)) + n := reflect.Append(t, s) + t.Set(n) + } else { + u.top().SetString(string(v)) + } } func (u *unmarshaler) SimpleKey(v []byte) { diff --git a/unmarshal_test.go b/unmarshal_test.go index 6113d6b8..5e6bc689 100644 --- a/unmarshal_test.go +++ b/unmarshal_test.go @@ -234,3 +234,13 @@ Name = "plantain" assert.Equal(t, expected, x) } + +func TestArraySimple(t *testing.T) { + x := struct { + Foo []string + }{} + doc := `Foo = ["hello", "world"]` + err := toml.Unmarshal([]byte(doc), &x) + require.NoError(t, err) + assert.Equal(t, []string{"hello", "world"}, x.Foo) +} From 279096427016fe4847d2dc12d8112e04f67fd08e Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Mon, 8 Feb 2021 20:44:23 -0500 Subject: [PATCH 032/228] Bool value --- unmarshal.go | 18 ++++++++++++++++++ unmarshal_test.go | 21 +++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/unmarshal.go b/unmarshal.go index b38cef72..6e83f029 100644 --- a/unmarshal.go +++ b/unmarshal.go @@ -152,6 +152,21 @@ func (u *unmarshaler) StringValue(v []byte) { } } +func (u *unmarshaler) BoolValue(b bool) { + if u.err != nil { + return + } + + t := u.top() + if t.Type().Kind() == reflect.Slice { + s := reflect.ValueOf(b) + n := reflect.Append(t, s) + t.Set(n) + } else { + u.top().SetBool(b) + } +} + func (u *unmarshaler) SimpleKey(v []byte) { if u.err != nil { return @@ -197,6 +212,7 @@ type builder interface { KeyValEnd() StringValue(v []byte) + BoolValue(b bool) } type parser struct { @@ -381,11 +397,13 @@ func (p parser) parseVal(b []byte) ([]byte, error) { if !scanFollowsTrue(b) { return nil, fmt.Errorf("expected 'true'") } + p.builder.BoolValue(true) return b[4:], nil case 'f': if !scanFollowsFalse(b) { return nil, fmt.Errorf("expected 'false'") } + p.builder.BoolValue(false) return b[5:], nil case '[': return p.parseValArray(b) diff --git a/unmarshal_test.go b/unmarshal_test.go index 5e6bc689..af7a8930 100644 --- a/unmarshal_test.go +++ b/unmarshal_test.go @@ -244,3 +244,24 @@ func TestArraySimple(t *testing.T) { require.NoError(t, err) assert.Equal(t, []string{"hello", "world"}, x.Foo) } + +func TestBool(t *testing.T) { + x := struct { + Truthy bool + Falsey bool + }{Falsey: true} + doc := `Truthy = true +Falsey = false` + err := toml.Unmarshal([]byte(doc), &x) + require.NoError(t, err) + assert.Equal(t, true, x.Truthy) + assert.Equal(t, false, x.Falsey) +} + +func TestBoolArray(t *testing.T) { + x := struct{ Bits []bool }{} + doc := `Bits = [true, false, true, true]` + err := toml.Unmarshal([]byte(doc), &x) + require.NoError(t, err) + assert.Equal(t, []bool{true, false, true, true}, x.Bits) +} From 7dbf7554c41cfcbedc0c9173a364a647f2fd0586 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Tue, 9 Feb 2021 19:22:54 -0500 Subject: [PATCH 033/228] Nested arrays --- internal/reflectbuild/reflectbuild.go | 239 +++++++++++++++++++++ internal/reflectbuild/reflectbuild_test.go | 167 ++++++++++++++ unmarshal.go | 183 +++++++--------- unmarshal_test.go | 36 ++++ 4 files changed, 523 insertions(+), 102 deletions(-) create mode 100644 internal/reflectbuild/reflectbuild.go create mode 100644 internal/reflectbuild/reflectbuild_test.go diff --git a/internal/reflectbuild/reflectbuild.go b/internal/reflectbuild/reflectbuild.go new file mode 100644 index 00000000..7199f9fd --- /dev/null +++ b/internal/reflectbuild/reflectbuild.go @@ -0,0 +1,239 @@ +// reflectbuild is a package that provides utility functions to build Go +// objects using reflection. +package reflectbuild + +import ( + "fmt" + "reflect" + "strings" +) + +// Builder wraps a value and provides method to modify its structure. +// It is a stateful object that keeps a cursor of what part of the object is +// being modified. +// Create a Builder with NewBuilder. +type Builder struct { + root reflect.Value + // Root is always a pointer to a non-nil value. + // Cursor is the top of the stack. + stack []reflect.Value +} + +// NewBuilder creates a Builder to construct v. +// If v is nil or not a pointer, an error will be returned. +func NewBuilder(v interface{}) (Builder, error) { + if v == nil { + return Builder{}, fmt.Errorf("cannot build a nil value") + } + + rv := reflect.ValueOf(v) + if rv.Type().Kind() != reflect.Ptr { + return Builder{}, fmt.Errorf("cannot build a %s: need a pointer", rv.Type().Kind()) + } + + return Builder{ + root: rv.Elem(), + stack: []reflect.Value{rv.Elem()}, + }, nil +} + +func (b *Builder) top() reflect.Value { + return b.stack[len(b.stack)-1] +} + +func (b *Builder) push(v reflect.Value) { + b.stack = append(b.stack, v) +} + +func (b *Builder) pop() { + b.stack = b.stack[:len(b.stack)-1] +} + +func (b *Builder) len() int { + return len(b.stack) +} + +func (b *Builder) Dump() string { + str := strings.Builder{} + str.WriteByte('[') + + for i, x := range b.stack { + if i > 0 { + str.WriteString(" | ") + } + fmt.Fprintf(&str, "%s (%s)", x.Type(), x) + } + + str.WriteByte(']') + return str.String() +} + +func (b *Builder) replace(v reflect.Value) { + b.stack[len(b.stack)-1] = v +} + +// DigField pushes the cursor into a field of the current struct. +// Errors if the current value is not a struct, or the field does not exist. +func (b *Builder) DigField(s string) error { + t := b.top() + + err := checkKind(t.Type(), reflect.Struct) + if err != nil { + return err + } + + f := t.FieldByName(s) + if !f.IsValid() { + return FieldNotFoundError{FieldName: s, Struct: t} + } + + b.replace(f) + + return nil +} + +// Save stores a copy of the current cursor position. +// It can be restored using Back(). +// Save points are stored as a stack. +func (b *Builder) Save() { + b.push(b.top()) +} + +// Reset brings the cursor back to the root object. +func (b *Builder) Reset() { + b.stack = b.stack[:1] + b.stack[0] = b.root +} + +// Load is the opposite of Save. It discards the current cursor and loads the +// last saved cursor. +// Panics if no cursor has been saved. +func (b *Builder) Load() { + if b.len() < 2 { + panic(fmt.Errorf("tried to Back() when cursor was already at root")) + } + b.pop() +} + +// Cursor returns the value pointed at by the cursor. +func (b *Builder) Cursor() reflect.Value { + return b.top() +} + +func (b *Builder) IsSlice() bool { + return b.top().Kind() == reflect.Slice +} + +// Last moves the cursor to the last value of the current value. +// For a slice or an array, it is the last element they contain, if any. +// For anything else, it's a no-op. +func (b *Builder) Last() { + switch b.Cursor().Kind() { + case reflect.Slice, reflect.Array: + length := b.Cursor().Len() + if length > 0 { + x := b.Cursor().Index(length - 1) + b.replace(x) + } + } +} + +// SliceLastOrCreate moves the cursor to the last element of the slice if any. +// Otherwise creates a new element in that slice and moves to it. +func (b *Builder) SliceLastOrCreate() error { + t := b.top() + err := checkKind(t.Type(), reflect.Slice) + if err != nil { + return err + } + + if t.Len() == 0 { + return b.SliceNewElem() + } + b.Last() + return nil +} + +// SliceNewElem operates on a slice. It creates a new object (of type contained +// by the slice), append it to the slice, and moves the cursor to the new +// object. +func (b *Builder) SliceNewElem() error { + t := b.top() + err := checkKind(t.Type(), reflect.Slice) + if err != nil { + return err + } + elem := reflect.New(t.Type().Elem()) + newSlice := reflect.Append(t, elem.Elem()) + t.Set(newSlice) + b.replace(t.Index(t.Len() - 1)) + return nil +} + +func (b *Builder) SliceAppend(v reflect.Value) error { + t := b.top() + err := checkKind(t.Type(), reflect.Slice) + if err != nil { + return err + } + newSlice := reflect.Append(t, v) + t.Set(newSlice) + b.replace(t.Index(t.Len() - 1)) + return nil +} + +// Set the value at the cursor to the given string. +// Errors if a string cannot be assigned to the current value. +func (b *Builder) SetString(s string) error { + t := b.top() + + err := checkKind(t.Type(), reflect.String) + if err != nil { + return err + } + + t.SetString(s) + return nil +} + +// Set the value at the cursor to the given boolean. +// Errors if a boolean cannot be assigned to the current value. +func (b *Builder) SetBool(v bool) error { + t := b.top() + + err := checkKind(t.Type(), reflect.Bool) + if err != nil { + return err + } + + t.SetBool(v) + return nil +} + +func checkKind(rt reflect.Type, expected reflect.Kind) error { + if rt.Kind() != expected { + return IncorrectKindError{ + Actual: rt.Kind(), + Expected: expected, + } + } + return nil +} + +type IncorrectKindError struct { + Actual reflect.Kind + Expected reflect.Kind +} + +func (e IncorrectKindError) Error() string { + return fmt.Sprintf("incorrect kind: expected '%s', got '%s'", e.Expected, e.Actual) +} + +type FieldNotFoundError struct { + Struct reflect.Value + FieldName string +} + +func (e FieldNotFoundError) Error() string { + return fmt.Sprintf("field not found: '%s' on '%s'", e.FieldName, e.Struct.Type()) +} diff --git a/internal/reflectbuild/reflectbuild_test.go b/internal/reflectbuild/reflectbuild_test.go new file mode 100644 index 00000000..433a3416 --- /dev/null +++ b/internal/reflectbuild/reflectbuild_test.go @@ -0,0 +1,167 @@ +package reflectbuild_test + +import ( + "reflect" + "testing" + + "github.com/pelletier/go-toml/v2/internal/reflectbuild" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNewBuilderSuccess(t *testing.T) { + x := struct{}{} + _, err := reflectbuild.NewBuilder(&x) + assert.NoError(t, err) +} + +func TestNewBuilderNil(t *testing.T) { + _, err := reflectbuild.NewBuilder(nil) + assert.Error(t, err) +} + +func TestNewBuilderNonPtr(t *testing.T) { + _, err := reflectbuild.NewBuilder(struct{}{}) + assert.Error(t, err) +} + +func TestDigField(t *testing.T) { + x := struct { + Field string + }{} + b, err := reflectbuild.NewBuilder(&x) + require.NoError(t, err) + assert.Error(t, b.DigField("oops")) + assert.NoError(t, b.DigField("Field")) + assert.Error(t, b.DigField("does not work on strings")) +} + +func TestBack(t *testing.T) { + x := struct { + A string + B string + }{} + b, err := reflectbuild.NewBuilder(&x) + require.NoError(t, err) + b.Save() + assert.NoError(t, b.DigField("A")) + assert.NoError(t, b.SetString("A")) + b.Load() + b.Save() + assert.NoError(t, b.DigField("B")) + assert.NoError(t, b.SetString("B")) + assert.Equal(t, "A", x.A) + assert.Equal(t, "B", x.B) + b.Load() // back to root + assert.Panics(t, func() { + b.Load() // oops + }) +} + +func TestReset(t *testing.T) { + x := struct { + A []string + B string + }{} + b, err := reflectbuild.NewBuilder(&x) + require.NoError(t, err) + require.NoError(t, b.DigField("A")) + require.NoError(t, b.SliceNewElem()) + require.NoError(t, b.SetString("hello")) + b.Reset() + require.NoError(t, b.DigField("B")) + require.NoError(t, b.SetString("world")) + + assert.Equal(t, []string{"hello"}, x.A) + assert.Equal(t, "world", x.B) +} + +func TestSetString(t *testing.T) { + x := struct { + Field string + }{} + b, err := reflectbuild.NewBuilder(&x) + require.NoError(t, err) + assert.Error(t, b.SetString("oops")) + require.NoError(t, b.DigField("Field")) + require.NoError(t, b.SetString("hello!")) + assert.Equal(t, "hello!", x.Field) +} + +func TestSliceNewElem(t *testing.T) { + x := struct { + Field []string + }{} + b, err := reflectbuild.NewBuilder(&x) + require.NoError(t, err) + require.NoError(t, b.DigField("Field")) + b.Save() + + require.NoError(t, b.SliceNewElem()) + require.NoError(t, b.SetString("Val1")) + b.Load() + require.NoError(t, b.SliceNewElem()) + require.NoError(t, b.SetString("Val2")) + + require.Error(t, b.SliceNewElem()) + + assert.Equal(t, []string{"Val1", "Val2"}, x.Field) +} + +func TestSliceNewElemNested(t *testing.T) { + x := struct { + Field [][]string + }{} + b, err := reflectbuild.NewBuilder(&x) + require.NoError(t, err) + require.NoError(t, b.DigField("Field")) + + b.Save() + + require.NoError(t, b.SliceNewElem()) + require.NoError(t, b.SliceNewElem()) + require.NoError(t, b.SetString("Val1.1")) + b.Load() + b.Save() + + require.NoError(t, b.SliceNewElem()) + b.Save() + require.NoError(t, b.SliceNewElem()) + require.NoError(t, b.SetString("Val2.1")) + b.Load() + require.NoError(t, b.SliceNewElem()) + require.NoError(t, b.SetString("Val2.2")) + b.Load() + require.NoError(t, b.SliceNewElem()) + + assert.Equal(t, [][]string{{"Val1.1"}, {"Val2.1", "Val2.2"}, nil}, x.Field) +} + +func TestIncorrectKindError(t *testing.T) { + err := reflectbuild.IncorrectKindError{ + Actual: reflect.String, + Expected: reflect.Struct, + } + assert.NotEmpty(t, err.Error()) +} + +func TestFieldNotFoundError(t *testing.T) { + err := reflectbuild.FieldNotFoundError{ + Struct: reflect.ValueOf(struct { + Blah string + }{}), + FieldName: "Foo", + } + assert.NotEmpty(t, err.Error()) +} + +func TestCursor(t *testing.T) { + x := struct { + Field string + }{} + b, err := reflectbuild.NewBuilder(&x) + require.NoError(t, err) + assert.Equal(t, b.Cursor().Kind(), reflect.Struct) + require.NoError(t, b.DigField("Field")) + assert.Equal(t, b.Cursor().Kind(), reflect.String) +} diff --git a/unmarshal.go b/unmarshal.go index 6e83f029..170ee8d2 100644 --- a/unmarshal.go +++ b/unmarshal.go @@ -5,30 +5,24 @@ import ( "encoding/hex" "fmt" "reflect" + + "github.com/pelletier/go-toml/v2/internal/reflectbuild" ) func Unmarshal(data []byte, v interface{}) error { - if v == nil { - return fmt.Errorf("cannot unmarshal to nil target") - } - rv := reflect.ValueOf(v) - if rv.Kind() != reflect.Ptr { - return fmt.Errorf("can only marshal to pointer, not %s", rv.Kind()) - } - - u := &unmarshaler{stack: []reflect.Value{rv.Elem()}} - parseErr := parser{builder: u}.parse(data) - if parseErr != nil { - return parseErr + u := &unmarshaler{} + u.builder, u.err = reflectbuild.NewBuilder(v) + if u.err == nil { + parseErr := parser{builder: u}.parse(data) + if parseErr != nil { + return parseErr + } } return u.err } type unmarshaler struct { - // Each stack frame is a pointer to the root object that should be - // considered when settings values. - // It at least contains the root object passed to Unmarshal. - stack []reflect.Value + builder reflectbuild.Builder // First error that appeared during the construction of the object. // When set all callbacks are no-ops. @@ -41,6 +35,35 @@ type unmarshaler struct { // Table Arrays need a buffer of keys because we need to know which one is // the last one, as it may result in creating a new element in the array. arrayTableKey [][]byte + + // Flag to indicate that the next value is an an assignment. + // Assignments are when the builder already points to the value, and should + // be directly assigned. This is used to distinguish between assigning or + // appending to arrays. + assign bool +} + +func (u *unmarshaler) Assignation() { + u.assign = true +} + +func (u *unmarshaler) ArrayBegin() { + if u.err != nil { + return + } + u.builder.Save() + if u.assign { + u.assign = false + } else { + u.builder.SliceNewElem() + } +} + +func (u *unmarshaler) ArrayEnd() { + if u.err != nil { + return + } + u.builder.Load() } func (u *unmarshaler) ArrayTableBegin() { @@ -56,99 +79,48 @@ func (u *unmarshaler) ArrayTableEnd() { return } - u.parsingTableArray = false - - u.stack = u.stack[:1] - - parent := u.top() - for _, k := range u.arrayTableKey { - switch parent.Type().Kind() { - case reflect.Slice: - l := parent.Len() - parent = parent.Index(l - 1) - case reflect.Struct: - default: - u.err = fmt.Errorf("value of type '%s' cannot have children", parent) - return - } + u.builder.Reset() - f := parent.FieldByName(string(k)) - if !f.IsValid() { - // TODO: implement alternative names - u.err = fmt.Errorf("field '%s' not found", string(k)) + for _, v := range u.arrayTableKey[:len(u.arrayTableKey)-1] { + u.err = u.builder.DigField(string(v)) + if u.err != nil { return } - parent = f + u.err = u.builder.SliceLastOrCreate() } - if parent.Type().Kind() != reflect.Slice { - u.err = fmt.Errorf("array table key is not a slice") + v := u.arrayTableKey[len(u.arrayTableKey)-1] + u.err = u.builder.DigField(string(v)) + if u.err != nil { return } + u.err = u.builder.SliceNewElem() - n := reflect.New(parent.Type().Elem()) - parent.Set(reflect.Append(parent, n.Elem())) - last := parent.Index(parent.Len() - 1) - u.push(last) + u.parsingTableArray = false u.arrayTableKey = u.arrayTableKey[:0] } func (u *unmarshaler) KeyValBegin() { - u.push(u.top()) + u.builder.Save() } func (u *unmarshaler) KeyValEnd() { - u.pop() -} - -func (u *unmarshaler) getOrCreateChild(key string) (reflect.Value, error) { - parent := u.top() - switch parent.Type().Kind() { - case reflect.Slice: - l := parent.Len() - parent = parent.Index(l - 1) - case reflect.Struct: - default: - return reflect.Value{}, fmt.Errorf("value of type '%s' cannot have children", parent) - } - - f := parent.FieldByName(key) - if !f.IsValid() { - // TODO: implement alternative names - return reflect.Value{}, fmt.Errorf("field '%s' not found", key) - } - // TODO create things - return f, nil -} - -func (u *unmarshaler) top() reflect.Value { - return u.stack[len(u.stack)-1] -} - -func (u *unmarshaler) push(v reflect.Value) { - u.stack = append(u.stack, v) -} - -func (u *unmarshaler) pop() { - u.stack = u.stack[:len(u.stack)-1] -} - -func (u *unmarshaler) replace(v reflect.Value) { - u.stack[len(u.stack)-1] = v + u.builder.Load() } func (u *unmarshaler) StringValue(v []byte) { if u.err != nil { return } - - t := u.top() - if t.Type().Kind() == reflect.Slice { - s := reflect.ValueOf(string(v)) - n := reflect.Append(t, s) - t.Set(n) + if u.builder.IsSlice() { + u.builder.Save() + u.err = u.builder.SliceAppend(reflect.ValueOf(string(v))) + if u.err != nil { + return + } + u.builder.Load() } else { - u.top().SetString(string(v)) + u.err = u.builder.SetString(string(v)) } } @@ -156,14 +128,15 @@ func (u *unmarshaler) BoolValue(b bool) { if u.err != nil { return } - - t := u.top() - if t.Type().Kind() == reflect.Slice { - s := reflect.ValueOf(b) - n := reflect.Append(t, s) - t.Set(n) + if u.builder.IsSlice() { + u.builder.Save() + u.err = u.builder.SliceAppend(reflect.ValueOf(b)) + if u.err != nil { + return + } + u.builder.Load() } else { - u.top().SetBool(b) + u.err = u.builder.SetBool(b) } } @@ -175,12 +148,13 @@ func (u *unmarshaler) SimpleKey(v []byte) { if u.parsingTableArray { u.arrayTableKey = append(u.arrayTableKey, v) } else { - target, err := u.getOrCreateChild(string(v)) - if err != nil { - u.err = err - return + if u.builder.Cursor().Kind() == reflect.Slice { + u.err = u.builder.SliceLastOrCreate() + if u.err != nil { + return + } } - u.replace(target) + u.err = u.builder.DigField(string(v)) } } @@ -190,9 +164,7 @@ func (u *unmarshaler) StandardTableBegin() { } // tables are only top-level - u.stack = u.stack[:1] - - u.push(u.top()) + u.builder.Reset() } func (u *unmarshaler) StandardTableEnd() { @@ -210,6 +182,9 @@ type builder interface { ArrayTableEnd() KeyValBegin() KeyValEnd() + ArrayBegin() + ArrayEnd() + Assignation() StringValue(v []byte) BoolValue(b bool) @@ -355,6 +330,7 @@ func (p parser) parseKeyval(b []byte) ([]byte, error) { if err != nil { return nil, err } + p.builder.Assignation() b = p.parseWhitespace(b) return p.parseVal(b) @@ -471,6 +447,9 @@ func (p parser) parseValArray(b []byte) ([]byte, error) { //array-sep = %x2C ; , Comma //ws-comment-newline = *( wschar / [ comment ] newline ) + p.builder.ArrayBegin() + defer p.builder.ArrayEnd() + b = b[1:] first := true diff --git a/unmarshal_test.go b/unmarshal_test.go index af7a8930..b876aecf 100644 --- a/unmarshal_test.go +++ b/unmarshal_test.go @@ -245,6 +245,42 @@ func TestArraySimple(t *testing.T) { assert.Equal(t, []string{"hello", "world"}, x.Foo) } +func TestArrayNestedInTable(t *testing.T) { + x := struct { + Wrapper struct { + Foo []string + } + }{} + doc := `[Wrapper] +Foo = ["hello", "world"]` + err := toml.Unmarshal([]byte(doc), &x) + require.NoError(t, err) + assert.Equal(t, []string{"hello", "world"}, x.Wrapper.Foo) +} + +func TestArrayMixed(t *testing.T) { + x := struct { + Wrapper struct { + Foo []interface{} + } + }{} + doc := `[Wrapper] +Foo = ["hello", true]` + err := toml.Unmarshal([]byte(doc), &x) + require.NoError(t, err) + assert.Equal(t, []interface{}{"hello", true}, x.Wrapper.Foo) +} + +func TestArrayNested(t *testing.T) { + x := struct { + Foo [][]string + }{} + doc := `Foo = [["hello", "world"], ["a"], []]` + err := toml.Unmarshal([]byte(doc), &x) + require.NoError(t, err) + assert.Equal(t, [][]string{{"hello", "world"}, {"a"}, nil}, x.Foo) +} + func TestBool(t *testing.T) { x := struct { Truthy bool From 0982fd5f1f151c058692f7c58dd565008e067667 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Tue, 9 Feb 2021 19:23:45 -0500 Subject: [PATCH 034/228] Check unhandled error --- unmarshal.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unmarshal.go b/unmarshal.go index 170ee8d2..225c04d7 100644 --- a/unmarshal.go +++ b/unmarshal.go @@ -55,7 +55,7 @@ func (u *unmarshaler) ArrayBegin() { if u.assign { u.assign = false } else { - u.builder.SliceNewElem() + u.err = u.builder.SliceNewElem() } } From 84282bbfd391f3b4ed737899cb034ba807af3089 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Tue, 9 Feb 2021 19:26:50 -0500 Subject: [PATCH 035/228] Move parser code --- parser.go | 614 +++++++++++++++++++++++++++++++++++++++++++++++++++ unmarshal.go | 611 -------------------------------------------------- 2 files changed, 614 insertions(+), 611 deletions(-) diff --git a/parser.go b/parser.go index f9fa173c..bdcc28d6 100644 --- a/parser.go +++ b/parser.go @@ -1 +1,615 @@ package toml + +import ( + "bytes" + "encoding/hex" + "fmt" +) + +type builder interface { + SimpleKey(v []byte) + + StandardTableBegin() + StandardTableEnd() + ArrayTableBegin() + ArrayTableEnd() + KeyValBegin() + KeyValEnd() + ArrayBegin() + ArrayEnd() + Assignation() + + StringValue(v []byte) + BoolValue(b bool) +} + +type parser struct { + builder builder +} + +func (p parser) parse(b []byte) error { + b, err := p.parseExpression(b) + if err != nil { + return err + } + for len(b) > 0 { + b, err = p.parseNewline(b) + if err != nil { + return err + } + + b, err = p.parseExpression(b) + if err != nil { + return err + } + } + return nil +} + +func (p parser) parseNewline(b []byte) ([]byte, error) { + if b[0] == '\n' { + return b[1:], nil + } + if b[0] == '\r' { + _, rest, err := scanWindowsNewline(b) + return rest, err + } + return nil, fmt.Errorf("expected newline but got %#U", b[0]) +} + +func (p parser) parseExpression(b []byte) ([]byte, error) { + //expression = ws [ comment ] + //expression =/ ws keyval ws [ comment ] + //expression =/ ws table ws [ comment ] + + b = p.parseWhitespace(b) + + if len(b) == 0 { + return b, nil + } + + if b[0] == '#' { + _, rest, err := scanComment(b) + return rest, err + } + if b[0] == '\n' || b[0] == '\r' { + return b, nil + } + + var err error + if b[0] == '[' { + b, err = p.parseTable(b) + } else { + b, err = p.parseKeyval(b) + } + if err != nil { + return nil, err + } + + b = p.parseWhitespace(b) + + if len(b) > 0 && b[0] == '#' { + _, rest, err := scanComment(b) + return rest, err + } + + return b, nil +} + +func (p parser) parseTable(b []byte) ([]byte, error) { + //table = std-table / array-table + if len(b) > 1 && b[1] == '[' { + return p.parseArrayTable(b) + } + return p.parseStdTable(b) +} + +func (p parser) parseArrayTable(b []byte) ([]byte, error) { + //array-table = array-table-open key array-table-close + //array-table-open = %x5B.5B ws ; [[ Double left square bracket + //array-table-close = ws %x5D.5D ; ]] Double right square bracket + + p.builder.ArrayTableBegin() + defer p.builder.ArrayTableEnd() + + b = b[2:] + b = p.parseWhitespace(b) + b, err := p.parseKey(b) + if err != nil { + return nil, err + } + b = p.parseWhitespace(b) + b, err = expect(']', b) + if err != nil { + return nil, err + } + return expect(']', b) +} + +func (p parser) parseStdTable(b []byte) ([]byte, error) { + //std-table = std-table-open key std-table-close + //std-table-open = %x5B ws ; [ Left square bracket + //std-table-close = ws %x5D ; ] Right square bracket + + p.builder.StandardTableBegin() + defer p.builder.StandardTableEnd() + + b = b[1:] + b = p.parseWhitespace(b) + b, err := p.parseKey(b) + if err != nil { + return nil, err + } + b = p.parseWhitespace(b) + + return expect(']', b) +} + +func (p parser) parseKeyval(b []byte) ([]byte, error) { + //keyval = key keyval-sep val + + p.builder.KeyValBegin() + defer p.builder.KeyValEnd() + + b, err := p.parseKey(b) + if err != nil { + return nil, err + } + + //keyval-sep = ws %x3D ws ; = + + b = p.parseWhitespace(b) + b, err = expect('=', b) + if err != nil { + return nil, err + } + p.builder.Assignation() + b = p.parseWhitespace(b) + + return p.parseVal(b) +} + +func (p parser) parseVal(b []byte) ([]byte, error) { + // val = string / boolean / array / inline-table / date-time / float / integer + if len(b) == 0 { + return nil, fmt.Errorf("expected value, not eof") + } + + var err error + c := b[0] + + switch c { + // strings + case '"': + var v []byte + if scanFollowsMultilineBasicStringDelimiter(b) { + v, b, err = p.parseMultilineBasicString(b) + } else { + v, b, err = p.parseBasicString(b) + } + if err == nil { + p.builder.StringValue(v) + } + return b, err + case '\'': + var v []byte + if scanFollowsMultilineLiteralStringDelimiter(b) { + v, b, err = p.parseMultilineLiteralString(b) + } else { + v, b, err = p.parseLiteralString(b) + } + if err == nil { + p.builder.StringValue(v) + } + return b, err + case 't': + if !scanFollowsTrue(b) { + return nil, fmt.Errorf("expected 'true'") + } + p.builder.BoolValue(true) + return b[4:], nil + case 'f': + if !scanFollowsFalse(b) { + return nil, fmt.Errorf("expected 'false'") + } + p.builder.BoolValue(false) + return b[5:], nil + case '[': + return p.parseValArray(b) + case '{': + return p.parseInlineTable(b) + + // TODO date-time + + // TODO float + + // TODO integer + default: + return nil, fmt.Errorf("unexpected char") + } +} + +func (p parser) parseLiteralString(b []byte) ([]byte, []byte, error) { + v, rest, err := scanLiteralString(b) + if err != nil { + return nil, nil, err + } + return v[1 : len(v)-1], rest, nil +} + +func (p parser) parseInlineTable(b []byte) ([]byte, error) { + //inline-table = inline-table-open [ inline-table-keyvals ] inline-table-close + //inline-table-open = %x7B ws ; { + //inline-table-close = ws %x7D ; } + //inline-table-sep = ws %x2C ws ; , Comma + //inline-table-keyvals = keyval [ inline-table-sep inline-table-keyvals ] + + b = b[1:] + + first := true + var err error + for len(b) > 0 { + b = p.parseWhitespace(b) + if b[0] == '}' { + break + } + + if !first { + b, err = expect(',', b) + if err != nil { + return nil, err + } + b = p.parseWhitespace(b) + } + b, err = p.parseKeyval(b) + if err != nil { + return nil, err + } + + first = false + } + return expect('}', b) +} + +func (p parser) parseValArray(b []byte) ([]byte, error) { + //array = array-open [ array-values ] ws-comment-newline array-close + //array-open = %x5B ; [ + //array-close = %x5D ; ] + //array-values = ws-comment-newline val ws-comment-newline array-sep array-values + //array-values =/ ws-comment-newline val ws-comment-newline [ array-sep ] + //array-sep = %x2C ; , Comma + //ws-comment-newline = *( wschar / [ comment ] newline ) + + p.builder.ArrayBegin() + defer p.builder.ArrayEnd() + + b = b[1:] + + first := true + var err error + for len(b) > 0 { + b, err = p.parseOptionalWhitespaceCommentNewline(b) + if err != nil { + return nil, err + } + + if len(b) == 0 { + return nil, unexpectedCharacter{b: b} + } + + if b[0] == ']' { + break + } + if b[0] == ',' { + if first { + return nil, fmt.Errorf("array cannot start with comma") + } + b = b[1:] + b, err = p.parseOptionalWhitespaceCommentNewline(b) + if err != nil { + return nil, err + } + } + + b, err = p.parseVal(b) + if err != nil { + return nil, err + } + b, err = p.parseOptionalWhitespaceCommentNewline(b) + if err != nil { + return nil, err + } + first = false + } + + return expect(']', b) +} + +func (p parser) parseOptionalWhitespaceCommentNewline(b []byte) ([]byte, error) { + var err error + b = p.parseWhitespace(b) + if len(b) > 0 && b[0] == '#' { + _, b, err = scanComment(b) + if err != nil { + return nil, err + } + } + if len(b) > 0 && (b[0] == '\n' || b[0] == '\r') { + b, err = p.parseNewline(b) + if err != nil { + return nil, err + } + } + return b, nil +} + +func (p parser) parseMultilineLiteralString(b []byte) ([]byte, []byte, error) { + token, rest, err := scanMultilineLiteralString(b) + if err != nil { + return nil, nil, err + } + + i := 3 + + // skip the immediate new line + if token[i] == '\n' { + i++ + } else if token[i] == '\r' && token[i+1] == '\n' { + i += 2 + } + + return token[i : len(b)-3], rest, err +} + +func (p parser) parseMultilineBasicString(b []byte) ([]byte, []byte, error) { + //ml-basic-string = ml-basic-string-delim [ newline ] ml-basic-body + //ml-basic-string-delim + //ml-basic-string-delim = 3quotation-mark + //ml-basic-body = *mlb-content *( mlb-quotes 1*mlb-content ) [ mlb-quotes ] + // + //mlb-content = mlb-char / newline / mlb-escaped-nl + //mlb-char = mlb-unescaped / escaped + //mlb-quotes = 1*2quotation-mark + //mlb-unescaped = wschar / %x21 / %x23-5B / %x5D-7E / non-ascii + //mlb-escaped-nl = escape ws newline *( wschar / newline ) + + token, rest, err := scanMultilineBasicString(b) + if err != nil { + return nil, nil, err + } + var builder bytes.Buffer + + i := 3 + + // skip the immediate new line + if token[i] == '\n' { + i++ + } else if token[i] == '\r' && token[i+1] == '\n' { + i += 2 + } + + // The scanner ensures that the token starts and ends with quotes and that + // escapes are balanced. + for ; i < len(token)-3; i++ { + c := token[i] + if c == '\\' { + // When the last non-whitespace character on a line is an unescaped \, + // it will be trimmed along with all whitespace (including newlines) up + // to the next non-whitespace character or closing delimiter. + if token[i+1] == '\n' || (token[i+1] == '\r' && token[i+2] == '\n') { + i++ // skip the \ + for ; i < len(token)-3; i++ { + c := token[i] + if !(c == '\n' || c == '\r' || c == ' ' || c == '\t') { + break + } + } + continue + } + + // handle escaping + i++ + c = token[i] + switch c { + case '"', '\\': + builder.WriteByte(c) + case 'b': + builder.WriteByte('\b') + case 'f': + builder.WriteByte('\f') + case 'n': + builder.WriteByte('\n') + case 'r': + builder.WriteByte('\r') + case 't': + builder.WriteByte('\t') + case 'u': + x, err := hexToString(token[i+3:len(token)-3], 4) + if err != nil { + return nil, nil, err + } + builder.WriteString(x) + i += 4 + case 'U': + x, err := hexToString(token[i+3:len(token)-3], 8) + if err != nil { + return nil, nil, err + } + builder.WriteString(x) + i += 8 + default: + return nil, nil, fmt.Errorf("invalid escaped character: %#U", c) + } + } else { + builder.WriteByte(c) + } + } + + return builder.Bytes(), rest, nil +} + +func (p parser) parseKey(b []byte) ([]byte, error) { + //key = simple-key / dotted-key + //simple-key = quoted-key / unquoted-key + // + //unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _ + //quoted-key = basic-string / literal-string + //dotted-key = simple-key 1*( dot-sep simple-key ) + // + //dot-sep = ws %x2E ws ; . Period + + b, err := p.parseSimpleKey(b) + if err != nil { + return nil, err + } + + for { + b = p.parseWhitespace(b) + if len(b) > 0 && b[0] == '.' { + b, err = expect('.', b) + if err != nil { + return nil, err + } + b = p.parseWhitespace(b) + b, err = p.parseSimpleKey(b) + if err != nil { + return nil, err + } + } else { + break + } + } + + return b, nil +} + +func (p parser) parseSimpleKey(b []byte) (rest []byte, err error) { + //simple-key = quoted-key / unquoted-key + //unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _ + //quoted-key = basic-string / literal-string + + if len(b) == 0 { + return nil, unexpectedCharacter{b: b} + } + + var v []byte + if b[0] == '\'' { + v, rest, err = scanLiteralString(b) + } else if b[0] == '"' { + v, rest, err = p.parseBasicString(b) + } else if isUnquotedKeyChar(b[0]) { + v, rest, err = scanUnquotedKey(b) + } else { + return nil, unexpectedCharacter{b: b} + } + p.builder.SimpleKey(v) + return +} + +func (p parser) parseBasicString(b []byte) ([]byte, []byte, error) { + //basic-string = quotation-mark *basic-char quotation-mark + //quotation-mark = %x22 ; " + //basic-char = basic-unescaped / escaped + //basic-unescaped = wschar / %x21 / %x23-5B / %x5D-7E / non-ascii + //escaped = escape escape-seq-char + //escape-seq-char = %x22 ; " quotation mark U+0022 + //escape-seq-char =/ %x5C ; \ reverse solidus U+005C + //escape-seq-char =/ %x62 ; b backspace U+0008 + //escape-seq-char =/ %x66 ; f form feed U+000C + //escape-seq-char =/ %x6E ; n line feed U+000A + //escape-seq-char =/ %x72 ; r carriage return U+000D + //escape-seq-char =/ %x74 ; t tab U+0009 + //escape-seq-char =/ %x75 4HEXDIG ; uXXXX U+XXXX + //escape-seq-char =/ %x55 8HEXDIG ; UXXXXXXXX U+XXXXXXXX + + token, rest, err := scanBasicString(b) + if err != nil { + return nil, nil, err + } + var builder bytes.Buffer + + // The scanner ensures that the token starts and ends with quotes and that + // escapes are balanced. + for i := 1; i < len(token)-1; i++ { + c := token[i] + if c == '\\' { + i++ + c = token[i] + switch c { + case '"', '\\': + builder.WriteByte(c) + case 'b': + builder.WriteByte('\b') + case 'f': + builder.WriteByte('\f') + case 'n': + builder.WriteByte('\n') + case 'r': + builder.WriteByte('\r') + case 't': + builder.WriteByte('\t') + case 'u': + x, err := hexToString(token[i+1:len(token)-1], 4) + if err != nil { + return nil, nil, err + } + builder.WriteString(x) + i += 4 + case 'U': + x, err := hexToString(token[i+1:len(token)-1], 8) + if err != nil { + return nil, nil, err + } + builder.WriteString(x) + i += 8 + default: + return nil, nil, fmt.Errorf("invalid escaped character: %#U", c) + } + } else { + builder.WriteByte(c) + } + } + + return builder.Bytes(), rest, nil +} + +func hexToString(b []byte, length int) (string, error) { + if len(b) < length { + return "", fmt.Errorf("unicode point needs %d hex characters", length) + } + // TODO: slow + b, err := hex.DecodeString(string(b[:length])) + if err != nil { + return "", err + } + return string(b), nil +} + +func (p parser) parseWhitespace(b []byte) []byte { + //ws = *wschar + //wschar = %x20 ; Space + //wschar =/ %x09 ; Horizontal tab + + _, rest := scanWhitespace(b) + return rest +} + +func expect(x byte, b []byte) ([]byte, error) { + if len(b) == 0 || b[0] != x { + return nil, unexpectedCharacter{r: x, b: b} + } + return b[1:], nil +} + +type unexpectedCharacter struct { + r byte + b []byte +} + +func (u unexpectedCharacter) Error() string { + if len(u.b) == 0 { + return fmt.Sprintf("expected %#U, not EOF", u.r) + + } + return fmt.Sprintf("expected %#U, not %#U", u.r, u.b[0]) +} diff --git a/unmarshal.go b/unmarshal.go index 225c04d7..96ecc7de 100644 --- a/unmarshal.go +++ b/unmarshal.go @@ -1,9 +1,6 @@ package toml import ( - "bytes" - "encoding/hex" - "fmt" "reflect" "github.com/pelletier/go-toml/v2/internal/reflectbuild" @@ -172,611 +169,3 @@ func (u *unmarshaler) StandardTableEnd() { return } } - -type builder interface { - SimpleKey(v []byte) - - StandardTableBegin() - StandardTableEnd() - ArrayTableBegin() - ArrayTableEnd() - KeyValBegin() - KeyValEnd() - ArrayBegin() - ArrayEnd() - Assignation() - - StringValue(v []byte) - BoolValue(b bool) -} - -type parser struct { - builder builder -} - -func (p parser) parse(b []byte) error { - b, err := p.parseExpression(b) - if err != nil { - return err - } - for len(b) > 0 { - b, err = p.parseNewline(b) - if err != nil { - return err - } - - b, err = p.parseExpression(b) - if err != nil { - return err - } - } - return nil -} - -func (p parser) parseNewline(b []byte) ([]byte, error) { - if b[0] == '\n' { - return b[1:], nil - } - if b[0] == '\r' { - _, rest, err := scanWindowsNewline(b) - return rest, err - } - return nil, fmt.Errorf("expected newline but got %#U", b[0]) -} - -func (p parser) parseExpression(b []byte) ([]byte, error) { - //expression = ws [ comment ] - //expression =/ ws keyval ws [ comment ] - //expression =/ ws table ws [ comment ] - - b = p.parseWhitespace(b) - - if len(b) == 0 { - return b, nil - } - - if b[0] == '#' { - _, rest, err := scanComment(b) - return rest, err - } - if b[0] == '\n' || b[0] == '\r' { - return b, nil - } - - var err error - if b[0] == '[' { - b, err = p.parseTable(b) - } else { - b, err = p.parseKeyval(b) - } - if err != nil { - return nil, err - } - - b = p.parseWhitespace(b) - - if len(b) > 0 && b[0] == '#' { - _, rest, err := scanComment(b) - return rest, err - } - - return b, nil -} - -func (p parser) parseTable(b []byte) ([]byte, error) { - //table = std-table / array-table - if len(b) > 1 && b[1] == '[' { - return p.parseArrayTable(b) - } - return p.parseStdTable(b) -} - -func (p parser) parseArrayTable(b []byte) ([]byte, error) { - //array-table = array-table-open key array-table-close - //array-table-open = %x5B.5B ws ; [[ Double left square bracket - //array-table-close = ws %x5D.5D ; ]] Double right square bracket - - p.builder.ArrayTableBegin() - defer p.builder.ArrayTableEnd() - - b = b[2:] - b = p.parseWhitespace(b) - b, err := p.parseKey(b) - if err != nil { - return nil, err - } - b = p.parseWhitespace(b) - b, err = expect(']', b) - if err != nil { - return nil, err - } - return expect(']', b) -} - -func (p parser) parseStdTable(b []byte) ([]byte, error) { - //std-table = std-table-open key std-table-close - //std-table-open = %x5B ws ; [ Left square bracket - //std-table-close = ws %x5D ; ] Right square bracket - - p.builder.StandardTableBegin() - defer p.builder.StandardTableEnd() - - b = b[1:] - b = p.parseWhitespace(b) - b, err := p.parseKey(b) - if err != nil { - return nil, err - } - b = p.parseWhitespace(b) - - return expect(']', b) -} - -func (p parser) parseKeyval(b []byte) ([]byte, error) { - //keyval = key keyval-sep val - - p.builder.KeyValBegin() - defer p.builder.KeyValEnd() - - b, err := p.parseKey(b) - if err != nil { - return nil, err - } - - //keyval-sep = ws %x3D ws ; = - - b = p.parseWhitespace(b) - b, err = expect('=', b) - if err != nil { - return nil, err - } - p.builder.Assignation() - b = p.parseWhitespace(b) - - return p.parseVal(b) -} - -func (p parser) parseVal(b []byte) ([]byte, error) { - // val = string / boolean / array / inline-table / date-time / float / integer - if len(b) == 0 { - return nil, fmt.Errorf("expected value, not eof") - } - - var err error - c := b[0] - - switch c { - // strings - case '"': - var v []byte - if scanFollowsMultilineBasicStringDelimiter(b) { - v, b, err = p.parseMultilineBasicString(b) - } else { - v, b, err = p.parseBasicString(b) - } - if err == nil { - p.builder.StringValue(v) - } - return b, err - case '\'': - var v []byte - if scanFollowsMultilineLiteralStringDelimiter(b) { - v, b, err = p.parseMultilineLiteralString(b) - } else { - v, b, err = p.parseLiteralString(b) - } - if err == nil { - p.builder.StringValue(v) - } - return b, err - case 't': - if !scanFollowsTrue(b) { - return nil, fmt.Errorf("expected 'true'") - } - p.builder.BoolValue(true) - return b[4:], nil - case 'f': - if !scanFollowsFalse(b) { - return nil, fmt.Errorf("expected 'false'") - } - p.builder.BoolValue(false) - return b[5:], nil - case '[': - return p.parseValArray(b) - case '{': - return p.parseInlineTable(b) - - // TODO date-time - - // TODO float - - // TODO integer - default: - return nil, fmt.Errorf("unexpected char") - } -} - -func (p parser) parseLiteralString(b []byte) ([]byte, []byte, error) { - v, rest, err := scanLiteralString(b) - if err != nil { - return nil, nil, err - } - return v[1 : len(v)-1], rest, nil -} - -func (p parser) parseInlineTable(b []byte) ([]byte, error) { - //inline-table = inline-table-open [ inline-table-keyvals ] inline-table-close - //inline-table-open = %x7B ws ; { - //inline-table-close = ws %x7D ; } - //inline-table-sep = ws %x2C ws ; , Comma - //inline-table-keyvals = keyval [ inline-table-sep inline-table-keyvals ] - - b = b[1:] - - first := true - var err error - for len(b) > 0 { - b = p.parseWhitespace(b) - if b[0] == '}' { - break - } - - if !first { - b, err = expect(',', b) - if err != nil { - return nil, err - } - b = p.parseWhitespace(b) - } - b, err = p.parseKeyval(b) - if err != nil { - return nil, err - } - - first = false - } - return expect('}', b) -} - -func (p parser) parseValArray(b []byte) ([]byte, error) { - //array = array-open [ array-values ] ws-comment-newline array-close - //array-open = %x5B ; [ - //array-close = %x5D ; ] - //array-values = ws-comment-newline val ws-comment-newline array-sep array-values - //array-values =/ ws-comment-newline val ws-comment-newline [ array-sep ] - //array-sep = %x2C ; , Comma - //ws-comment-newline = *( wschar / [ comment ] newline ) - - p.builder.ArrayBegin() - defer p.builder.ArrayEnd() - - b = b[1:] - - first := true - var err error - for len(b) > 0 { - b, err = p.parseOptionalWhitespaceCommentNewline(b) - if err != nil { - return nil, err - } - - if len(b) == 0 { - return nil, unexpectedCharacter{b: b} - } - - if b[0] == ']' { - break - } - if b[0] == ',' { - if first { - return nil, fmt.Errorf("array cannot start with comma") - } - b = b[1:] - b, err = p.parseOptionalWhitespaceCommentNewline(b) - if err != nil { - return nil, err - } - } - - b, err = p.parseVal(b) - if err != nil { - return nil, err - } - b, err = p.parseOptionalWhitespaceCommentNewline(b) - if err != nil { - return nil, err - } - first = false - } - - return expect(']', b) -} - -func (p parser) parseOptionalWhitespaceCommentNewline(b []byte) ([]byte, error) { - var err error - b = p.parseWhitespace(b) - if len(b) > 0 && b[0] == '#' { - _, b, err = scanComment(b) - if err != nil { - return nil, err - } - } - if len(b) > 0 && (b[0] == '\n' || b[0] == '\r') { - b, err = p.parseNewline(b) - if err != nil { - return nil, err - } - } - return b, nil -} - -func (p parser) parseMultilineLiteralString(b []byte) ([]byte, []byte, error) { - token, rest, err := scanMultilineLiteralString(b) - if err != nil { - return nil, nil, err - } - - i := 3 - - // skip the immediate new line - if token[i] == '\n' { - i++ - } else if token[i] == '\r' && token[i+1] == '\n' { - i += 2 - } - - return token[i : len(b)-3], rest, err -} - -func (p parser) parseMultilineBasicString(b []byte) ([]byte, []byte, error) { - //ml-basic-string = ml-basic-string-delim [ newline ] ml-basic-body - //ml-basic-string-delim - //ml-basic-string-delim = 3quotation-mark - //ml-basic-body = *mlb-content *( mlb-quotes 1*mlb-content ) [ mlb-quotes ] - // - //mlb-content = mlb-char / newline / mlb-escaped-nl - //mlb-char = mlb-unescaped / escaped - //mlb-quotes = 1*2quotation-mark - //mlb-unescaped = wschar / %x21 / %x23-5B / %x5D-7E / non-ascii - //mlb-escaped-nl = escape ws newline *( wschar / newline ) - - token, rest, err := scanMultilineBasicString(b) - if err != nil { - return nil, nil, err - } - var builder bytes.Buffer - - i := 3 - - // skip the immediate new line - if token[i] == '\n' { - i++ - } else if token[i] == '\r' && token[i+1] == '\n' { - i += 2 - } - - // The scanner ensures that the token starts and ends with quotes and that - // escapes are balanced. - for ; i < len(token)-3; i++ { - c := token[i] - if c == '\\' { - // When the last non-whitespace character on a line is an unescaped \, - // it will be trimmed along with all whitespace (including newlines) up - // to the next non-whitespace character or closing delimiter. - if token[i+1] == '\n' || (token[i+1] == '\r' && token[i+2] == '\n') { - i++ // skip the \ - for ; i < len(token)-3; i++ { - c := token[i] - if !(c == '\n' || c == '\r' || c == ' ' || c == '\t') { - break - } - } - continue - } - - // handle escaping - i++ - c = token[i] - switch c { - case '"', '\\': - builder.WriteByte(c) - case 'b': - builder.WriteByte('\b') - case 'f': - builder.WriteByte('\f') - case 'n': - builder.WriteByte('\n') - case 'r': - builder.WriteByte('\r') - case 't': - builder.WriteByte('\t') - case 'u': - x, err := hexToString(token[i+3:len(token)-3], 4) - if err != nil { - return nil, nil, err - } - builder.WriteString(x) - i += 4 - case 'U': - x, err := hexToString(token[i+3:len(token)-3], 8) - if err != nil { - return nil, nil, err - } - builder.WriteString(x) - i += 8 - default: - return nil, nil, fmt.Errorf("invalid escaped character: %#U", c) - } - } else { - builder.WriteByte(c) - } - } - - return builder.Bytes(), rest, nil -} - -func (p parser) parseKey(b []byte) ([]byte, error) { - //key = simple-key / dotted-key - //simple-key = quoted-key / unquoted-key - // - //unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _ - //quoted-key = basic-string / literal-string - //dotted-key = simple-key 1*( dot-sep simple-key ) - // - //dot-sep = ws %x2E ws ; . Period - - b, err := p.parseSimpleKey(b) - if err != nil { - return nil, err - } - - for { - b = p.parseWhitespace(b) - if len(b) > 0 && b[0] == '.' { - b, err = expect('.', b) - if err != nil { - return nil, err - } - b = p.parseWhitespace(b) - b, err = p.parseSimpleKey(b) - if err != nil { - return nil, err - } - } else { - break - } - } - - return b, nil -} - -func (p parser) parseSimpleKey(b []byte) (rest []byte, err error) { - //simple-key = quoted-key / unquoted-key - //unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _ - //quoted-key = basic-string / literal-string - - if len(b) == 0 { - return nil, unexpectedCharacter{b: b} - } - - var v []byte - if b[0] == '\'' { - v, rest, err = scanLiteralString(b) - } else if b[0] == '"' { - v, rest, err = p.parseBasicString(b) - } else if isUnquotedKeyChar(b[0]) { - v, rest, err = scanUnquotedKey(b) - } else { - return nil, unexpectedCharacter{b: b} - } - p.builder.SimpleKey(v) - return -} - -func (p parser) parseBasicString(b []byte) ([]byte, []byte, error) { - //basic-string = quotation-mark *basic-char quotation-mark - //quotation-mark = %x22 ; " - //basic-char = basic-unescaped / escaped - //basic-unescaped = wschar / %x21 / %x23-5B / %x5D-7E / non-ascii - //escaped = escape escape-seq-char - //escape-seq-char = %x22 ; " quotation mark U+0022 - //escape-seq-char =/ %x5C ; \ reverse solidus U+005C - //escape-seq-char =/ %x62 ; b backspace U+0008 - //escape-seq-char =/ %x66 ; f form feed U+000C - //escape-seq-char =/ %x6E ; n line feed U+000A - //escape-seq-char =/ %x72 ; r carriage return U+000D - //escape-seq-char =/ %x74 ; t tab U+0009 - //escape-seq-char =/ %x75 4HEXDIG ; uXXXX U+XXXX - //escape-seq-char =/ %x55 8HEXDIG ; UXXXXXXXX U+XXXXXXXX - - token, rest, err := scanBasicString(b) - if err != nil { - return nil, nil, err - } - var builder bytes.Buffer - - // The scanner ensures that the token starts and ends with quotes and that - // escapes are balanced. - for i := 1; i < len(token)-1; i++ { - c := token[i] - if c == '\\' { - i++ - c = token[i] - switch c { - case '"', '\\': - builder.WriteByte(c) - case 'b': - builder.WriteByte('\b') - case 'f': - builder.WriteByte('\f') - case 'n': - builder.WriteByte('\n') - case 'r': - builder.WriteByte('\r') - case 't': - builder.WriteByte('\t') - case 'u': - x, err := hexToString(token[i+1:len(token)-1], 4) - if err != nil { - return nil, nil, err - } - builder.WriteString(x) - i += 4 - case 'U': - x, err := hexToString(token[i+1:len(token)-1], 8) - if err != nil { - return nil, nil, err - } - builder.WriteString(x) - i += 8 - default: - return nil, nil, fmt.Errorf("invalid escaped character: %#U", c) - } - } else { - builder.WriteByte(c) - } - } - - return builder.Bytes(), rest, nil -} - -func hexToString(b []byte, length int) (string, error) { - if len(b) < length { - return "", fmt.Errorf("unicode point needs %d hex characters", length) - } - // TODO: slow - b, err := hex.DecodeString(string(b[:length])) - if err != nil { - return "", err - } - return string(b), nil -} - -func (p parser) parseWhitespace(b []byte) []byte { - //ws = *wschar - //wschar = %x20 ; Space - //wschar =/ %x09 ; Horizontal tab - - _, rest := scanWhitespace(b) - return rest -} - -func expect(x byte, b []byte) ([]byte, error) { - if len(b) == 0 || b[0] != x { - return nil, unexpectedCharacter{r: x, b: b} - } - return b[1:], nil -} - -type unexpectedCharacter struct { - r byte - b []byte -} - -func (u unexpectedCharacter) Error() string { - if len(u.b) == 0 { - return fmt.Sprintf("expected %#U, not EOF", u.r) - - } - return fmt.Sprintf("expected %#U, not %#U", u.r, u.b[0]) -} From 2660bb8426b5143d08ee5010742bc521cb650fa0 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Tue, 9 Feb 2021 19:36:14 -0500 Subject: [PATCH 036/228] Test inline tables --- unmarshal_test.go | 63 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 57 insertions(+), 6 deletions(-) diff --git a/unmarshal_test.go b/unmarshal_test.go index b876aecf..96d46964 100644 --- a/unmarshal_test.go +++ b/unmarshal_test.go @@ -235,7 +235,7 @@ Name = "plantain" assert.Equal(t, expected, x) } -func TestArraySimple(t *testing.T) { +func TestUnmarshalArraySimple(t *testing.T) { x := struct { Foo []string }{} @@ -245,7 +245,7 @@ func TestArraySimple(t *testing.T) { assert.Equal(t, []string{"hello", "world"}, x.Foo) } -func TestArrayNestedInTable(t *testing.T) { +func TestUnmarshalArrayNestedInTable(t *testing.T) { x := struct { Wrapper struct { Foo []string @@ -258,7 +258,7 @@ Foo = ["hello", "world"]` assert.Equal(t, []string{"hello", "world"}, x.Wrapper.Foo) } -func TestArrayMixed(t *testing.T) { +func TestUnmarshalArrayMixed(t *testing.T) { x := struct { Wrapper struct { Foo []interface{} @@ -271,7 +271,7 @@ Foo = ["hello", true]` assert.Equal(t, []interface{}{"hello", true}, x.Wrapper.Foo) } -func TestArrayNested(t *testing.T) { +func TestUnmarshalArrayNested(t *testing.T) { x := struct { Foo [][]string }{} @@ -281,7 +281,7 @@ func TestArrayNested(t *testing.T) { assert.Equal(t, [][]string{{"hello", "world"}, {"a"}, nil}, x.Foo) } -func TestBool(t *testing.T) { +func TestUnmarshalBool(t *testing.T) { x := struct { Truthy bool Falsey bool @@ -294,10 +294,61 @@ Falsey = false` assert.Equal(t, false, x.Falsey) } -func TestBoolArray(t *testing.T) { +func TestUnmarshalBoolArray(t *testing.T) { x := struct{ Bits []bool }{} doc := `Bits = [true, false, true, true]` err := toml.Unmarshal([]byte(doc), &x) require.NoError(t, err) assert.Equal(t, []bool{true, false, true, true}, x.Bits) } + +func TestUnmarshalInlineTable(t *testing.T) { + doc := ` + Name = { First = "Tom", Last = "Preston-Werner" } + Point = { X = "1", Y = "2" } + Animal = { Type.Name = "pug" }` + + type Name struct { + First string + Last string + } + + type Point struct { + X string + Y string + } + + type Type struct { + Name string + } + + type Animal struct { + Type Type + } + + type Doc struct { + Name Name + Point Point + Animal Animal + } + x := Doc{} + err := toml.Unmarshal([]byte(doc), &x) + require.NoError(t, err) + + expected := Doc{ + Name: Name{ + First: "Tom", + Last: "Preston-Werner", + }, + Point: Point{ + X: "1", + Y: "2", + }, + Animal: Animal{ + Type: Type{ + Name: "pug", + }, + }, + } + assert.Equal(t, expected, x) +} From f6a13d6e0588a63a48f6c2f3f6f26695d123cfc6 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Tue, 9 Feb 2021 20:44:54 -0500 Subject: [PATCH 037/228] wip numbers --- parser.go | 176 ++++++++++++++++++++++++++++++++++++++++++++++++++--- scanner.go | 2 + 2 files changed, 171 insertions(+), 7 deletions(-) diff --git a/parser.go b/parser.go index bdcc28d6..e1293ecf 100644 --- a/parser.go +++ b/parser.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/hex" "fmt" + "math" ) type builder interface { @@ -21,6 +22,7 @@ type builder interface { StringValue(v []byte) BoolValue(b bool) + FloatValue(n float64) } type parser struct { @@ -218,14 +220,8 @@ func (p parser) parseVal(b []byte) ([]byte, error) { return p.parseValArray(b) case '{': return p.parseInlineTable(b) - - // TODO date-time - - // TODO float - - // TODO integer default: - return nil, fmt.Errorf("unexpected char") + return p.parseIntOrFloatOrDateTime(b) } } @@ -594,6 +590,172 @@ func (p parser) parseWhitespace(b []byte) []byte { return rest } +func (p parser) parseIntOrFloatOrDateTime(b []byte) ([]byte, error) { + switch b[0] { + case 'i': + if !scanFollowsInf(b) { + return nil, fmt.Errorf("expected 'inf'") + } + p.builder.FloatValue(math.Inf(1)) + return b[3:], nil + case 'n': + if !scanFollowsNan(b) { + return nil, fmt.Errorf("expected 'nan'") + } + p.builder.FloatValue(math.NaN()) + return b[3:], nil + case '+', '-': + return parseIntOrFloat(b) + } + + if len(b) < 3 { + return parseIntOrFloat(b) + } + for idx, c := range b[:5] { + if c >= '0' && c <= '9' { + continue + } + if idx == 2 && c == ':' { + return parseDateTime(b) + } + if idx == 4 && c == '-' { + return parseDateTime(b) + } + } + return parseIntOrFloat(b) +} + +func parseDateTime(b []byte) ([]byte, error) { + +} + +func (p parser) parseIntOrFloat(b []byte) ([]byte, error) { + r := b[0] + if r == '0' { + if len(b) >= 2 { + var isValidRune validRuneFn + switch b[1] { + case 'x': + isValidRune = isValidHexRune + case 'o': + isValidRune = isValidOctalRune + case 'b': + isValidRune = isValidBinaryRune + default: + if b[1] >= 'a' && b[1] <= 'z' || b[1] >= 'A' && b[1] <= 'Z' { + return nil, fmt.Errorf("unknown number base: %s. possible options are x (hex) o (octal) b (binary)", string(b[1])) + } + } + + if isValidRune != nil { + b = b[2:] + digitSeen := false + for { + if !isValidRune(b[0]) { + break + } + digitSeen = true + b = b[1:] + } + + if !digitSeen { + return nil, fmt.Errorf("number needs at least one digit") + } + + p.builder.IntValue() + return b, nil + } + } + } + + if r == '+' || r == '-' { + b = b[1:] + if scanFollowsInf(b) { + if r == '+' { + p.builder.FloatValue(plusInf) + } else { + p.builder.FloatValue(minusInf) + } + return b, nil + } + if scanFollowsNan(b) { + p.builder.FloatValue(nan) + return b, nil + } + } + + pointSeen := false + expSeen := false + digitSeen := false + for len(b) > 0 { + next := b[0] + if next == '.' { + if pointSeen { + return nil, fmt.Errorf("cannot have two dots in one float") + } + b = b[1:] + if len(b) > 0 && !isDigit(b[0]) { + return nil, fmt.Errorf("float cannot end with a dot") + } + pointSeen = true + } else if next == 'e' || next == 'E' { + expSeen = true + b = b[1:] + if len(b) == 0 { + break + } + if b[0] == '+' || b[0] == '-' { + b = b[1:] + } + } else if isDigit(next) { + digitSeen = true + b = b[1:] + } else if next == '_' { + b = b[1:] + } else { + break + } + if pointSeen && !digitSeen { + return nil, fmt.Errorf("cannot start float with a dot") + } + } + + if !digitSeen { + return nil, fmt.Errorf("no digit in that number") + } + if pointSeen || expSeen { + p.builder.FloatValue() + } else { + p.builder.IntValue() + } + return b, nil +} + +func isDigit(r byte) bool { + return r >= '0' && r <= '9' +} + +var plusInf = math.Inf(1) +var minusInf = math.Inf(-1) +var nan = math.NaN() + +type validRuneFn func(r byte) bool + +func isValidHexRune(r byte) bool { + return r >= 'a' && r <= 'f' || + r >= 'A' && r <= 'F' || + r >= '0' && r <= '9' || + r == '_' +} + +func isValidOctalRune(r byte) bool { + return r >= '0' && r <= '7' || r == '_' +} + +func isValidBinaryRune(r byte) bool { + return r == '0' || r == '1' || r == '_' +} + func expect(x byte, b []byte) ([]byte, error) { if len(b) == 0 || b[0] != x { return nil, unexpectedCharacter{r: x, b: b} diff --git a/scanner.go b/scanner.go index 8c92c00d..7a7d11e3 100644 --- a/scanner.go +++ b/scanner.go @@ -20,6 +20,8 @@ var scanFollowsMultilineBasicStringDelimiter = scanFollows([]byte{'"', '"', '"'} var scanFollowsMultilineLiteralStringDelimiter = scanFollows([]byte{'\'', '\'', '\''}) var scanFollowsTrue = scanFollows([]byte{'t', 'r', 'u', 'e'}) var scanFollowsFalse = scanFollows([]byte{'f', 'a', 'l', 's', 'e'}) +var scanFollowsInf = scanFollows([]byte{'i', 'n', 'f'}) +var scanFollowsNan = scanFollows([]byte{'n', 'a', 'n'}) func scanUnquotedKey(b []byte) ([]byte, []byte, error) { //unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _ From 721fa81f2e14298a63077ac2c403dccd31f67732 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Wed, 10 Feb 2021 10:00:08 -0500 Subject: [PATCH 038/228] Support numbers --- internal/reflectbuild/reflectbuild.go | 48 ++++++++ parser.go | 167 ++++++++++++++++++++++---- unmarshal.go | 32 +++++ unmarshal_test.go | 7 ++ 4 files changed, 232 insertions(+), 22 deletions(-) diff --git a/internal/reflectbuild/reflectbuild.go b/internal/reflectbuild/reflectbuild.go index 7199f9fd..8225d231 100644 --- a/internal/reflectbuild/reflectbuild.go +++ b/internal/reflectbuild/reflectbuild.go @@ -210,6 +210,54 @@ func (b *Builder) SetBool(v bool) error { return nil } +func (b *Builder) SetFloat(n float64) error { + t := b.top() + + err := checkKindFloat(t.Type()) + if err != nil { + return err + } + + t.SetFloat(n) + return nil +} + +func (b *Builder) SetInt(n int64) error { + t := b.top() + + err := checkKindInt(t.Type()) + if err != nil { + return err + } + + t.SetInt(n) + return nil +} + +func checkKindInt(rt reflect.Type) error { + switch rt.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return nil + } + + return IncorrectKindError{ + Actual: rt.Kind(), + Expected: reflect.Int, + } +} + +func checkKindFloat(rt reflect.Type) error { + switch rt.Kind() { + case reflect.Float32, reflect.Float64: + return nil + } + + return IncorrectKindError{ + Actual: rt.Kind(), + Expected: reflect.Float64, + } +} + func checkKind(rt reflect.Type, expected reflect.Kind) error { if rt.Kind() != expected { return IncorrectKindError{ diff --git a/parser.go b/parser.go index e1293ecf..f44facb0 100644 --- a/parser.go +++ b/parser.go @@ -3,8 +3,11 @@ package toml import ( "bytes" "encoding/hex" + "errors" "fmt" "math" + "strconv" + "strings" ) type builder interface { @@ -23,6 +26,7 @@ type builder interface { StringValue(v []byte) BoolValue(b bool) FloatValue(n float64) + IntValue(n int64) } type parser struct { @@ -605,11 +609,11 @@ func (p parser) parseIntOrFloatOrDateTime(b []byte) ([]byte, error) { p.builder.FloatValue(math.NaN()) return b[3:], nil case '+', '-': - return parseIntOrFloat(b) + return p.parseIntOrFloat(b) } if len(b) < 3 { - return parseIntOrFloat(b) + return p.parseIntOrFloat(b) } for idx, c := range b[:5] { if c >= '0' && c <= '9' { @@ -622,48 +626,58 @@ func (p parser) parseIntOrFloatOrDateTime(b []byte) ([]byte, error) { return parseDateTime(b) } } - return parseIntOrFloat(b) + return p.parseIntOrFloat(b) } func parseDateTime(b []byte) ([]byte, error) { - + panic("implement me") } func (p parser) parseIntOrFloat(b []byte) ([]byte, error) { + i := 0 r := b[0] if r == '0' { if len(b) >= 2 { var isValidRune validRuneFn + var parseFn func([]byte) (int64, error) switch b[1] { case 'x': isValidRune = isValidHexRune + parseFn = parseIntHex case 'o': isValidRune = isValidOctalRune + parseFn = parseIntOct case 'b': isValidRune = isValidBinaryRune + parseFn = parseIntBin default: if b[1] >= 'a' && b[1] <= 'z' || b[1] >= 'A' && b[1] <= 'Z' { return nil, fmt.Errorf("unknown number base: %s. possible options are x (hex) o (octal) b (binary)", string(b[1])) } + parseFn = parseIntDec } if isValidRune != nil { - b = b[2:] + i = 2 digitSeen := false for { - if !isValidRune(b[0]) { + if !isValidRune(b[i]) { break } digitSeen = true - b = b[1:] + i++ } if !digitSeen { return nil, fmt.Errorf("number needs at least one digit") } - p.builder.IntValue() - return b, nil + v, err := parseFn(b[:i]) + if err != nil { + return nil, err + } + p.builder.IntValue(v) + return b[i:], nil } } } @@ -687,31 +701,31 @@ func (p parser) parseIntOrFloat(b []byte) ([]byte, error) { pointSeen := false expSeen := false digitSeen := false - for len(b) > 0 { - next := b[0] + for i < len(b) { + next := b[i] if next == '.' { if pointSeen { return nil, fmt.Errorf("cannot have two dots in one float") } - b = b[1:] - if len(b) > 0 && !isDigit(b[0]) { + i++ + if i < len(b) && !isDigit(b[i]) { return nil, fmt.Errorf("float cannot end with a dot") } pointSeen = true } else if next == 'e' || next == 'E' { expSeen = true - b = b[1:] - if len(b) == 0 { + i++ + if i >= len(b) { break } - if b[0] == '+' || b[0] == '-' { - b = b[1:] + if b[i] == '+' || b[i] == '-' { + i++ } } else if isDigit(next) { digitSeen = true - b = b[1:] + i++ } else if next == '_' { - b = b[1:] + i++ } else { break } @@ -724,17 +738,117 @@ func (p parser) parseIntOrFloat(b []byte) ([]byte, error) { return nil, fmt.Errorf("no digit in that number") } if pointSeen || expSeen { - p.builder.FloatValue() + f, err := parseFloat(b[:i]) + if err != nil { + return nil, err + } + p.builder.FloatValue(f) } else { - p.builder.IntValue() + v, err := parseIntDec(b[:i]) + if err != nil { + return nil, err + } + p.builder.IntValue(v) } - return b, nil + return b[i:], nil +} + +func parseFloat(b []byte) (float64, error) { + // TODO: inefficient + tok := string(b) + err := numberContainsInvalidUnderscore(tok) + if err != nil { + return 0, err + } + cleanedVal := cleanupNumberToken(tok) + return strconv.ParseFloat(cleanedVal, 64) +} + +func parseIntHex(b []byte) (int64, error) { + tok := string(b) + cleanedVal := cleanupNumberToken(tok) + err := hexNumberContainsInvalidUnderscore(cleanedVal) + if err != nil { + return 0, nil + } + return strconv.ParseInt(cleanedVal[2:], 16, 64) +} + +func parseIntOct(b []byte) (int64, error) { + tok := string(b) + cleanedVal := cleanupNumberToken(tok) + err := numberContainsInvalidUnderscore(cleanedVal) + if err != nil { + return 0, err + } + return strconv.ParseInt(cleanedVal[2:], 8, 64) +} + +func parseIntBin(b []byte) (int64, error) { + tok := string(b) + cleanedVal := cleanupNumberToken(tok) + err := numberContainsInvalidUnderscore(cleanedVal) + if err != nil { + return 0, err + } + return strconv.ParseInt(cleanedVal[2:], 2, 64) +} + +func parseIntDec(b []byte) (int64, error) { + tok := string(b) + cleanedVal := cleanupNumberToken(tok) + err := numberContainsInvalidUnderscore(cleanedVal) + if err != nil { + return 0, err + } + return strconv.ParseInt(cleanedVal, 10, 64) +} + +func numberContainsInvalidUnderscore(value string) error { + // For large numbers, you may use underscores between digits to enhance + // readability. Each underscore must be surrounded by at least one digit on + // each side. + + hasBefore := false + for idx, r := range value { + if r == '_' { + if !hasBefore || idx+1 >= len(value) { + // can't end with an underscore + return errInvalidUnderscore + } + } + hasBefore = isDigitRune(r) + } + return nil +} + +func hexNumberContainsInvalidUnderscore(value string) error { + hasBefore := false + for idx, r := range value { + if r == '_' { + if !hasBefore || idx+1 >= len(value) { + // can't end with an underscore + return errInvalidUnderscoreHex + } + } + hasBefore = isHexDigit(r) + } + return nil +} + +func cleanupNumberToken(value string) string { + cleanedVal := strings.Replace(value, "_", "", -1) + return cleanedVal } func isDigit(r byte) bool { return r >= '0' && r <= '9' } +func isDigitRune(r rune) bool { + return r >= '0' && r <= '9' +} + var plusInf = math.Inf(1) var minusInf = math.Inf(-1) var nan = math.NaN() @@ -748,6 +862,12 @@ func isValidHexRune(r byte) bool { r == '_' } +func isHexDigit(r rune) bool { + return isDigitRune(r) || + (r >= 'a' && r <= 'f') || + (r >= 'A' && r <= 'F') +} + func isValidOctalRune(r byte) bool { return r >= '0' && r <= '7' || r == '_' } @@ -775,3 +895,6 @@ func (u unexpectedCharacter) Error() string { } return fmt.Sprintf("expected %#U, not %#U", u.r, u.b[0]) } + +var errInvalidUnderscore = errors.New("invalid use of _ in number") +var errInvalidUnderscoreHex = errors.New("invalid use of _ in hex number") diff --git a/unmarshal.go b/unmarshal.go index 96ecc7de..6462999a 100644 --- a/unmarshal.go +++ b/unmarshal.go @@ -137,6 +137,38 @@ func (u *unmarshaler) BoolValue(b bool) { } } +func (u *unmarshaler) FloatValue(n float64) { + if u.err != nil { + return + } + if u.builder.IsSlice() { + u.builder.Save() + u.err = u.builder.SliceAppend(reflect.ValueOf(n)) + if u.err != nil { + return + } + u.builder.Load() + } else { + u.err = u.builder.SetFloat(n) + } +} + +func (u *unmarshaler) IntValue(n int64) { + if u.err != nil { + return + } + if u.builder.IsSlice() { + u.builder.Save() + u.err = u.builder.SliceAppend(reflect.ValueOf(n)) + if u.err != nil { + return + } + u.builder.Load() + } else { + u.err = u.builder.SetInt(n) + } +} + func (u *unmarshaler) SimpleKey(v []byte) { if u.err != nil { return diff --git a/unmarshal_test.go b/unmarshal_test.go index 96d46964..b5fd8f22 100644 --- a/unmarshal_test.go +++ b/unmarshal_test.go @@ -15,6 +15,13 @@ func TestUnmarshalSimple(t *testing.T) { assert.Equal(t, "hello", x.Foo) } +func TestUnmarshalInt(t *testing.T) { + x := struct{ Foo int }{} + err := toml.Unmarshal([]byte(`Foo = 42`), &x) + require.NoError(t, err) + assert.Equal(t, 42, x.Foo) +} + func TestUnmarshalNestedStructs(t *testing.T) { x := struct{ Foo struct{ Bar string } }{} err := toml.Unmarshal([]byte(`Foo.Bar = "hello"`), &x) From 27f0aeee30cf47b677aa9499160c237ec67f7505 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Wed, 10 Feb 2021 10:41:23 -0500 Subject: [PATCH 039/228] Import localtime --- localtime.go | 281 +++++++++++++++++++++++++++++ localtime_test.go | 446 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 727 insertions(+) create mode 100644 localtime.go create mode 100644 localtime_test.go diff --git a/localtime.go b/localtime.go new file mode 100644 index 00000000..a2149e96 --- /dev/null +++ b/localtime.go @@ -0,0 +1,281 @@ +// Implementation of TOML's local date/time. +// Copied over from https://github.com/googleapis/google-cloud-go/blob/master/civil/civil.go +// to avoid pulling all the Google dependencies. +// +// Copyright 2016 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package civil implements types for civil time, a time-zone-independent +// representation of time that follows the rules of the proleptic +// Gregorian calendar with exactly 24-hour days, 60-minute hours, and 60-second +// minutes. +// +// Because they lack location information, these types do not represent unique +// moments or intervals of time. Use time.Time for that purpose. +package toml + +import ( + "fmt" + "time" +) + +// A LocalDate represents a date (year, month, day). +// +// This type does not include location information, and therefore does not +// describe a unique 24-hour timespan. +type LocalDate struct { + Year int // Year (e.g., 2014). + Month time.Month // Month of the year (January = 1, ...). + Day int // Day of the month, starting at 1. +} + +// LocalDateOf returns the LocalDate in which a time occurs in that time's location. +func LocalDateOf(t time.Time) LocalDate { + var d LocalDate + d.Year, d.Month, d.Day = t.Date() + return d +} + +// ParseLocalDate parses a string in RFC3339 full-date format and returns the date value it represents. +func ParseLocalDate(s string) (LocalDate, error) { + t, err := time.Parse("2006-01-02", s) + if err != nil { + return LocalDate{}, err + } + return LocalDateOf(t), nil +} + +// String returns the date in RFC3339 full-date format. +func (d LocalDate) String() string { + return fmt.Sprintf("%04d-%02d-%02d", d.Year, d.Month, d.Day) +} + +// IsValid reports whether the date is valid. +func (d LocalDate) IsValid() bool { + return LocalDateOf(d.In(time.UTC)) == d +} + +// In returns the time corresponding to time 00:00:00 of the date in the location. +// +// In is always consistent with time.LocalDate, even when time.LocalDate returns a time +// on a different day. For example, if loc is America/Indiana/Vincennes, then both +// time.LocalDate(1955, time.May, 1, 0, 0, 0, 0, loc) +// and +// civil.LocalDate{Year: 1955, Month: time.May, Day: 1}.In(loc) +// return 23:00:00 on April 30, 1955. +// +// In panics if loc is nil. +func (d LocalDate) In(loc *time.Location) time.Time { + return time.Date(d.Year, d.Month, d.Day, 0, 0, 0, 0, loc) +} + +// AddDays returns the date that is n days in the future. +// n can also be negative to go into the past. +func (d LocalDate) AddDays(n int) LocalDate { + return LocalDateOf(d.In(time.UTC).AddDate(0, 0, n)) +} + +// DaysSince returns the signed number of days between the date and s, not including the end day. +// This is the inverse operation to AddDays. +func (d LocalDate) DaysSince(s LocalDate) (days int) { + // We convert to Unix time so we do not have to worry about leap seconds: + // Unix time increases by exactly 86400 seconds per day. + deltaUnix := d.In(time.UTC).Unix() - s.In(time.UTC).Unix() + return int(deltaUnix / 86400) +} + +// Before reports whether d1 occurs before d2. +func (d1 LocalDate) Before(d2 LocalDate) bool { + if d1.Year != d2.Year { + return d1.Year < d2.Year + } + if d1.Month != d2.Month { + return d1.Month < d2.Month + } + return d1.Day < d2.Day +} + +// After reports whether d1 occurs after d2. +func (d1 LocalDate) After(d2 LocalDate) bool { + return d2.Before(d1) +} + +// MarshalText implements the encoding.TextMarshaler interface. +// The output is the result of d.String(). +func (d LocalDate) MarshalText() ([]byte, error) { + return []byte(d.String()), nil +} + +// UnmarshalText implements the encoding.TextUnmarshaler interface. +// The date is expected to be a string in a format accepted by ParseLocalDate. +func (d *LocalDate) UnmarshalText(data []byte) error { + var err error + *d, err = ParseLocalDate(string(data)) + return err +} + +// A LocalTime represents a time with nanosecond precision. +// +// This type does not include location information, and therefore does not +// describe a unique moment in time. +// +// This type exists to represent the TIME type in storage-based APIs like BigQuery. +// Most operations on Times are unlikely to be meaningful. Prefer the LocalDateTime type. +type LocalTime struct { + Hour int // The hour of the day in 24-hour format; range [0-23] + Minute int // The minute of the hour; range [0-59] + Second int // The second of the minute; range [0-59] + Nanosecond int // The nanosecond of the second; range [0-999999999] +} + +// LocalTimeOf returns the LocalTime representing the time of day in which a time occurs +// in that time's location. It ignores the date. +func LocalTimeOf(t time.Time) LocalTime { + var tm LocalTime + tm.Hour, tm.Minute, tm.Second = t.Clock() + tm.Nanosecond = t.Nanosecond() + return tm +} + +// ParseLocalTime parses a string and returns the time value it represents. +// ParseLocalTime accepts an extended form of the RFC3339 partial-time format. After +// the HH:MM:SS part of the string, an optional fractional part may appear, +// consisting of a decimal point followed by one to nine decimal digits. +// (RFC3339 admits only one digit after the decimal point). +func ParseLocalTime(s string) (LocalTime, error) { + t, err := time.Parse("15:04:05.999999999", s) + if err != nil { + return LocalTime{}, err + } + return LocalTimeOf(t), nil +} + +// String returns the date in the format described in ParseLocalTime. If Nanoseconds +// is zero, no fractional part will be generated. Otherwise, the result will +// end with a fractional part consisting of a decimal point and nine digits. +func (t LocalTime) String() string { + s := fmt.Sprintf("%02d:%02d:%02d", t.Hour, t.Minute, t.Second) + if t.Nanosecond == 0 { + return s + } + return s + fmt.Sprintf(".%09d", t.Nanosecond) +} + +// IsValid reports whether the time is valid. +func (t LocalTime) IsValid() bool { + // Construct a non-zero time. + tm := time.Date(2, 2, 2, t.Hour, t.Minute, t.Second, t.Nanosecond, time.UTC) + return LocalTimeOf(tm) == t +} + +// MarshalText implements the encoding.TextMarshaler interface. +// The output is the result of t.String(). +func (t LocalTime) MarshalText() ([]byte, error) { + return []byte(t.String()), nil +} + +// UnmarshalText implements the encoding.TextUnmarshaler interface. +// The time is expected to be a string in a format accepted by ParseLocalTime. +func (t *LocalTime) UnmarshalText(data []byte) error { + var err error + *t, err = ParseLocalTime(string(data)) + return err +} + +// A LocalDateTime represents a date and time. +// +// This type does not include location information, and therefore does not +// describe a unique moment in time. +type LocalDateTime struct { + Date LocalDate + Time LocalTime +} + +// Note: We deliberately do not embed LocalDate into LocalDateTime, to avoid promoting AddDays and Sub. + +// LocalDateTimeOf returns the LocalDateTime in which a time occurs in that time's location. +func LocalDateTimeOf(t time.Time) LocalDateTime { + return LocalDateTime{ + Date: LocalDateOf(t), + Time: LocalTimeOf(t), + } +} + +// ParseLocalDateTime parses a string and returns the LocalDateTime it represents. +// ParseLocalDateTime accepts a variant of the RFC3339 date-time format that omits +// the time offset but includes an optional fractional time, as described in +// ParseLocalTime. Informally, the accepted format is +// YYYY-MM-DDTHH:MM:SS[.FFFFFFFFF] +// where the 'T' may be a lower-case 't'. +func ParseLocalDateTime(s string) (LocalDateTime, error) { + t, err := time.Parse("2006-01-02T15:04:05.999999999", s) + if err != nil { + t, err = time.Parse("2006-01-02t15:04:05.999999999", s) + if err != nil { + return LocalDateTime{}, err + } + } + return LocalDateTimeOf(t), nil +} + +// String returns the date in the format described in ParseLocalDate. +func (dt LocalDateTime) String() string { + return dt.Date.String() + "T" + dt.Time.String() +} + +// IsValid reports whether the datetime is valid. +func (dt LocalDateTime) IsValid() bool { + return dt.Date.IsValid() && dt.Time.IsValid() +} + +// In returns the time corresponding to the LocalDateTime in the given location. +// +// If the time is missing or ambigous at the location, In returns the same +// result as time.LocalDate. For example, if loc is America/Indiana/Vincennes, then +// both +// time.LocalDate(1955, time.May, 1, 0, 30, 0, 0, loc) +// and +// civil.LocalDateTime{ +// civil.LocalDate{Year: 1955, Month: time.May, Day: 1}}, +// civil.LocalTime{Minute: 30}}.In(loc) +// return 23:30:00 on April 30, 1955. +// +// In panics if loc is nil. +func (dt LocalDateTime) In(loc *time.Location) time.Time { + return time.Date(dt.Date.Year, dt.Date.Month, dt.Date.Day, dt.Time.Hour, dt.Time.Minute, dt.Time.Second, dt.Time.Nanosecond, loc) +} + +// Before reports whether dt1 occurs before dt2. +func (dt1 LocalDateTime) Before(dt2 LocalDateTime) bool { + return dt1.In(time.UTC).Before(dt2.In(time.UTC)) +} + +// After reports whether dt1 occurs after dt2. +func (dt1 LocalDateTime) After(dt2 LocalDateTime) bool { + return dt2.Before(dt1) +} + +// MarshalText implements the encoding.TextMarshaler interface. +// The output is the result of dt.String(). +func (dt LocalDateTime) MarshalText() ([]byte, error) { + return []byte(dt.String()), nil +} + +// UnmarshalText implements the encoding.TextUnmarshaler interface. +// The datetime is expected to be a string in a format accepted by ParseLocalDateTime +func (dt *LocalDateTime) UnmarshalText(data []byte) error { + var err error + *dt, err = ParseLocalDateTime(string(data)) + return err +} diff --git a/localtime_test.go b/localtime_test.go new file mode 100644 index 00000000..4bbb5b0e --- /dev/null +++ b/localtime_test.go @@ -0,0 +1,446 @@ +// Copyright 2016 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package toml + +import ( + "encoding/json" + "reflect" + "testing" + "time" +) + +func cmpEqual(x, y interface{}) bool { + return reflect.DeepEqual(x, y) +} + +func TestDates(t *testing.T) { + for _, test := range []struct { + date LocalDate + loc *time.Location + wantStr string + wantTime time.Time + }{ + { + date: LocalDate{2014, 7, 29}, + loc: time.Local, + wantStr: "2014-07-29", + wantTime: time.Date(2014, time.July, 29, 0, 0, 0, 0, time.Local), + }, + { + date: LocalDateOf(time.Date(2014, 8, 20, 15, 8, 43, 1, time.Local)), + loc: time.UTC, + wantStr: "2014-08-20", + wantTime: time.Date(2014, 8, 20, 0, 0, 0, 0, time.UTC), + }, + { + date: LocalDateOf(time.Date(999, time.January, 26, 0, 0, 0, 0, time.Local)), + loc: time.UTC, + wantStr: "0999-01-26", + wantTime: time.Date(999, 1, 26, 0, 0, 0, 0, time.UTC), + }, + } { + if got := test.date.String(); got != test.wantStr { + t.Errorf("%#v.String() = %q, want %q", test.date, got, test.wantStr) + } + if got := test.date.In(test.loc); !got.Equal(test.wantTime) { + t.Errorf("%#v.In(%v) = %v, want %v", test.date, test.loc, got, test.wantTime) + } + } +} + +func TestDateIsValid(t *testing.T) { + for _, test := range []struct { + date LocalDate + want bool + }{ + {LocalDate{2014, 7, 29}, true}, + {LocalDate{2000, 2, 29}, true}, + {LocalDate{10000, 12, 31}, true}, + {LocalDate{1, 1, 1}, true}, + {LocalDate{0, 1, 1}, true}, // year zero is OK + {LocalDate{-1, 1, 1}, true}, // negative year is OK + {LocalDate{1, 0, 1}, false}, + {LocalDate{1, 1, 0}, false}, + {LocalDate{2016, 1, 32}, false}, + {LocalDate{2016, 13, 1}, false}, + {LocalDate{1, -1, 1}, false}, + {LocalDate{1, 1, -1}, false}, + } { + got := test.date.IsValid() + if got != test.want { + t.Errorf("%#v: got %t, want %t", test.date, got, test.want) + } + } +} + +func TestParseDate(t *testing.T) { + for _, test := range []struct { + str string + want LocalDate // if empty, expect an error + }{ + {"2016-01-02", LocalDate{2016, 1, 2}}, + {"2016-12-31", LocalDate{2016, 12, 31}}, + {"0003-02-04", LocalDate{3, 2, 4}}, + {"999-01-26", LocalDate{}}, + {"", LocalDate{}}, + {"2016-01-02x", LocalDate{}}, + } { + got, err := ParseLocalDate(test.str) + if got != test.want { + t.Errorf("ParseLocalDate(%q) = %+v, want %+v", test.str, got, test.want) + } + if err != nil && test.want != (LocalDate{}) { + t.Errorf("Unexpected error %v from ParseLocalDate(%q)", err, test.str) + } + } +} + +func TestDateArithmetic(t *testing.T) { + for _, test := range []struct { + desc string + start LocalDate + end LocalDate + days int + }{ + { + desc: "zero days noop", + start: LocalDate{2014, 5, 9}, + end: LocalDate{2014, 5, 9}, + days: 0, + }, + { + desc: "crossing a year boundary", + start: LocalDate{2014, 12, 31}, + end: LocalDate{2015, 1, 1}, + days: 1, + }, + { + desc: "negative number of days", + start: LocalDate{2015, 1, 1}, + end: LocalDate{2014, 12, 31}, + days: -1, + }, + { + desc: "full leap year", + start: LocalDate{2004, 1, 1}, + end: LocalDate{2005, 1, 1}, + days: 366, + }, + { + desc: "full non-leap year", + start: LocalDate{2001, 1, 1}, + end: LocalDate{2002, 1, 1}, + days: 365, + }, + { + desc: "crossing a leap second", + start: LocalDate{1972, 6, 30}, + end: LocalDate{1972, 7, 1}, + days: 1, + }, + { + desc: "dates before the unix epoch", + start: LocalDate{101, 1, 1}, + end: LocalDate{102, 1, 1}, + days: 365, + }, + } { + if got := test.start.AddDays(test.days); got != test.end { + t.Errorf("[%s] %#v.AddDays(%v) = %#v, want %#v", test.desc, test.start, test.days, got, test.end) + } + if got := test.end.DaysSince(test.start); got != test.days { + t.Errorf("[%s] %#v.Sub(%#v) = %v, want %v", test.desc, test.end, test.start, got, test.days) + } + } +} + +func TestDateBefore(t *testing.T) { + for _, test := range []struct { + d1, d2 LocalDate + want bool + }{ + {LocalDate{2016, 12, 31}, LocalDate{2017, 1, 1}, true}, + {LocalDate{2016, 1, 1}, LocalDate{2016, 1, 1}, false}, + {LocalDate{2016, 12, 30}, LocalDate{2016, 12, 31}, true}, + {LocalDate{2016, 1, 30}, LocalDate{2016, 12, 31}, true}, + } { + if got := test.d1.Before(test.d2); got != test.want { + t.Errorf("%v.Before(%v): got %t, want %t", test.d1, test.d2, got, test.want) + } + } +} + +func TestDateAfter(t *testing.T) { + for _, test := range []struct { + d1, d2 LocalDate + want bool + }{ + {LocalDate{2016, 12, 31}, LocalDate{2017, 1, 1}, false}, + {LocalDate{2016, 1, 1}, LocalDate{2016, 1, 1}, false}, + {LocalDate{2016, 12, 30}, LocalDate{2016, 12, 31}, false}, + } { + if got := test.d1.After(test.d2); got != test.want { + t.Errorf("%v.After(%v): got %t, want %t", test.d1, test.d2, got, test.want) + } + } +} + +func TestTimeToString(t *testing.T) { + for _, test := range []struct { + str string + time LocalTime + roundTrip bool // ParseLocalTime(str).String() == str? + }{ + {"13:26:33", LocalTime{13, 26, 33, 0}, true}, + {"01:02:03.000023456", LocalTime{1, 2, 3, 23456}, true}, + {"00:00:00.000000001", LocalTime{0, 0, 0, 1}, true}, + {"13:26:03.1", LocalTime{13, 26, 3, 100000000}, false}, + {"13:26:33.0000003", LocalTime{13, 26, 33, 300}, false}, + } { + gotTime, err := ParseLocalTime(test.str) + if err != nil { + t.Errorf("ParseLocalTime(%q): got error: %v", test.str, err) + continue + } + if gotTime != test.time { + t.Errorf("ParseLocalTime(%q) = %+v, want %+v", test.str, gotTime, test.time) + } + if test.roundTrip { + gotStr := test.time.String() + if gotStr != test.str { + t.Errorf("%#v.String() = %q, want %q", test.time, gotStr, test.str) + } + } + } +} + +func TestTimeOf(t *testing.T) { + for _, test := range []struct { + time time.Time + want LocalTime + }{ + {time.Date(2014, 8, 20, 15, 8, 43, 1, time.Local), LocalTime{15, 8, 43, 1}}, + {time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC), LocalTime{0, 0, 0, 0}}, + } { + if got := LocalTimeOf(test.time); got != test.want { + t.Errorf("LocalTimeOf(%v) = %+v, want %+v", test.time, got, test.want) + } + } +} + +func TestTimeIsValid(t *testing.T) { + for _, test := range []struct { + time LocalTime + want bool + }{ + {LocalTime{0, 0, 0, 0}, true}, + {LocalTime{23, 0, 0, 0}, true}, + {LocalTime{23, 59, 59, 999999999}, true}, + {LocalTime{24, 59, 59, 999999999}, false}, + {LocalTime{23, 60, 59, 999999999}, false}, + {LocalTime{23, 59, 60, 999999999}, false}, + {LocalTime{23, 59, 59, 1000000000}, false}, + {LocalTime{-1, 0, 0, 0}, false}, + {LocalTime{0, -1, 0, 0}, false}, + {LocalTime{0, 0, -1, 0}, false}, + {LocalTime{0, 0, 0, -1}, false}, + } { + got := test.time.IsValid() + if got != test.want { + t.Errorf("%#v: got %t, want %t", test.time, got, test.want) + } + } +} + +func TestDateTimeToString(t *testing.T) { + for _, test := range []struct { + str string + dateTime LocalDateTime + roundTrip bool // ParseLocalDateTime(str).String() == str? + }{ + {"2016-03-22T13:26:33", LocalDateTime{LocalDate{2016, 03, 22}, LocalTime{13, 26, 33, 0}}, true}, + {"2016-03-22T13:26:33.000000600", LocalDateTime{LocalDate{2016, 03, 22}, LocalTime{13, 26, 33, 600}}, true}, + {"2016-03-22t13:26:33", LocalDateTime{LocalDate{2016, 03, 22}, LocalTime{13, 26, 33, 0}}, false}, + } { + gotDateTime, err := ParseLocalDateTime(test.str) + if err != nil { + t.Errorf("ParseLocalDateTime(%q): got error: %v", test.str, err) + continue + } + if gotDateTime != test.dateTime { + t.Errorf("ParseLocalDateTime(%q) = %+v, want %+v", test.str, gotDateTime, test.dateTime) + } + if test.roundTrip { + gotStr := test.dateTime.String() + if gotStr != test.str { + t.Errorf("%#v.String() = %q, want %q", test.dateTime, gotStr, test.str) + } + } + } +} + +func TestParseDateTimeErrors(t *testing.T) { + for _, str := range []string{ + "", + "2016-03-22", // just a date + "13:26:33", // just a time + "2016-03-22 13:26:33", // wrong separating character + "2016-03-22T13:26:33x", // extra at end + } { + if _, err := ParseLocalDateTime(str); err == nil { + t.Errorf("ParseLocalDateTime(%q) succeeded, want error", str) + } + } +} + +func TestDateTimeOf(t *testing.T) { + for _, test := range []struct { + time time.Time + want LocalDateTime + }{ + {time.Date(2014, 8, 20, 15, 8, 43, 1, time.Local), + LocalDateTime{LocalDate{2014, 8, 20}, LocalTime{15, 8, 43, 1}}}, + {time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC), + LocalDateTime{LocalDate{1, 1, 1}, LocalTime{0, 0, 0, 0}}}, + } { + if got := LocalDateTimeOf(test.time); got != test.want { + t.Errorf("LocalDateTimeOf(%v) = %+v, want %+v", test.time, got, test.want) + } + } +} + +func TestDateTimeIsValid(t *testing.T) { + // No need to be exhaustive here; it's just LocalDate.IsValid && LocalTime.IsValid. + for _, test := range []struct { + dt LocalDateTime + want bool + }{ + {LocalDateTime{LocalDate{2016, 3, 20}, LocalTime{0, 0, 0, 0}}, true}, + {LocalDateTime{LocalDate{2016, -3, 20}, LocalTime{0, 0, 0, 0}}, false}, + {LocalDateTime{LocalDate{2016, 3, 20}, LocalTime{24, 0, 0, 0}}, false}, + } { + got := test.dt.IsValid() + if got != test.want { + t.Errorf("%#v: got %t, want %t", test.dt, got, test.want) + } + } +} + +func TestDateTimeIn(t *testing.T) { + dt := LocalDateTime{LocalDate{2016, 1, 2}, LocalTime{3, 4, 5, 6}} + got := dt.In(time.UTC) + want := time.Date(2016, 1, 2, 3, 4, 5, 6, time.UTC) + if !got.Equal(want) { + t.Errorf("got %v, want %v", got, want) + } +} + +func TestDateTimeBefore(t *testing.T) { + d1 := LocalDate{2016, 12, 31} + d2 := LocalDate{2017, 1, 1} + t1 := LocalTime{5, 6, 7, 8} + t2 := LocalTime{5, 6, 7, 9} + for _, test := range []struct { + dt1, dt2 LocalDateTime + want bool + }{ + {LocalDateTime{d1, t1}, LocalDateTime{d2, t1}, true}, + {LocalDateTime{d1, t1}, LocalDateTime{d1, t2}, true}, + {LocalDateTime{d2, t1}, LocalDateTime{d1, t1}, false}, + {LocalDateTime{d2, t1}, LocalDateTime{d2, t1}, false}, + } { + if got := test.dt1.Before(test.dt2); got != test.want { + t.Errorf("%v.Before(%v): got %t, want %t", test.dt1, test.dt2, got, test.want) + } + } +} + +func TestDateTimeAfter(t *testing.T) { + d1 := LocalDate{2016, 12, 31} + d2 := LocalDate{2017, 1, 1} + t1 := LocalTime{5, 6, 7, 8} + t2 := LocalTime{5, 6, 7, 9} + for _, test := range []struct { + dt1, dt2 LocalDateTime + want bool + }{ + {LocalDateTime{d1, t1}, LocalDateTime{d2, t1}, false}, + {LocalDateTime{d1, t1}, LocalDateTime{d1, t2}, false}, + {LocalDateTime{d2, t1}, LocalDateTime{d1, t1}, true}, + {LocalDateTime{d2, t1}, LocalDateTime{d2, t1}, false}, + } { + if got := test.dt1.After(test.dt2); got != test.want { + t.Errorf("%v.After(%v): got %t, want %t", test.dt1, test.dt2, got, test.want) + } + } +} + +func TestMarshalJSON(t *testing.T) { + for _, test := range []struct { + value interface{} + want string + }{ + {LocalDate{1987, 4, 15}, `"1987-04-15"`}, + {LocalTime{18, 54, 2, 0}, `"18:54:02"`}, + {LocalDateTime{LocalDate{1987, 4, 15}, LocalTime{18, 54, 2, 0}}, `"1987-04-15T18:54:02"`}, + } { + bgot, err := json.Marshal(test.value) + if err != nil { + t.Fatal(err) + } + if got := string(bgot); got != test.want { + t.Errorf("%#v: got %s, want %s", test.value, got, test.want) + } + } +} + +func TestUnmarshalJSON(t *testing.T) { + var d LocalDate + var tm LocalTime + var dt LocalDateTime + for _, test := range []struct { + data string + ptr interface{} + want interface{} + }{ + {`"1987-04-15"`, &d, &LocalDate{1987, 4, 15}}, + {`"1987-04-\u0031\u0035"`, &d, &LocalDate{1987, 4, 15}}, + {`"18:54:02"`, &tm, &LocalTime{18, 54, 2, 0}}, + {`"1987-04-15T18:54:02"`, &dt, &LocalDateTime{LocalDate{1987, 4, 15}, LocalTime{18, 54, 2, 0}}}, + } { + if err := json.Unmarshal([]byte(test.data), test.ptr); err != nil { + t.Fatalf("%s: %v", test.data, err) + } + if !cmpEqual(test.ptr, test.want) { + t.Errorf("%s: got %#v, want %#v", test.data, test.ptr, test.want) + } + } + + for _, bad := range []string{"", `""`, `"bad"`, `"1987-04-15x"`, + `19870415`, // a JSON number + `11987-04-15x`, // not a JSON string + + } { + if json.Unmarshal([]byte(bad), &d) == nil { + t.Errorf("%q, LocalDate: got nil, want error", bad) + } + if json.Unmarshal([]byte(bad), &tm) == nil { + t.Errorf("%q, LocalTime: got nil, want error", bad) + } + if json.Unmarshal([]byte(bad), &dt) == nil { + t.Errorf("%q, LocalDateTime: got nil, want error", bad) + } + } +} From 0dad1a950c5c740261da8fb752e5db141fd4ef1b Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Wed, 10 Feb 2021 10:41:34 -0500 Subject: [PATCH 040/228] Import Unmarshal tests (not passing) --- .../imported_tests/unmarshal_imported_test.go | 2438 +++++++++++++++++ 1 file changed, 2438 insertions(+) create mode 100644 internal/imported_tests/unmarshal_imported_test.go diff --git a/internal/imported_tests/unmarshal_imported_test.go b/internal/imported_tests/unmarshal_imported_test.go new file mode 100644 index 00000000..80ec4a8e --- /dev/null +++ b/internal/imported_tests/unmarshal_imported_test.go @@ -0,0 +1,2438 @@ +package imported_tests + +// Those tests were imported directly from go-toml v1 +// https://raw.githubusercontent.com/pelletier/go-toml/a2e52561804c6cd9392ebf0048ca64fe4af67a43/marshal_test.go +// They have been cleaned up to only include Unmarshal tests, and only depend +// on the public API. Tests related to strict mode have been commented out and +// marked as skipped until we figure out if that's something we want in v2. + +import ( + "encoding/json" + "errors" + "fmt" + "reflect" + "strconv" + "strings" + "testing" + "time" + + "github.com/pelletier/go-toml/v2" +) + +type basicMarshalTestStruct struct { + String string `toml:"Zstring"` + StringList []string `toml:"Ystrlist"` + BasicMarshalTestSubAnonymousStruct + Sub basicMarshalTestSubStruct `toml:"Xsubdoc"` + SubList []basicMarshalTestSubStruct `toml:"Wsublist"` +} + +type basicMarshalTestSubStruct struct { + String2 string +} + +type BasicMarshalTestSubAnonymousStruct struct { + String3 string +} + +var basicTestData = basicMarshalTestStruct{ + String: "Hello", + StringList: []string{"Howdy", "Hey There"}, + BasicMarshalTestSubAnonymousStruct: BasicMarshalTestSubAnonymousStruct{"One"}, + Sub: basicMarshalTestSubStruct{"Two"}, + SubList: []basicMarshalTestSubStruct{{"Three"}, {"Four"}}, +} + +var basicTestToml = []byte(`String3 = "One" +Ystrlist = ["Howdy", "Hey There"] +Zstring = "Hello" + +[[Wsublist]] + String2 = "Three" + +[[Wsublist]] + String2 = "Four" + +[Xsubdoc] + String2 = "Two" +`) + +var basicTestTomlCustomIndentation = []byte(`String3 = "One" +Ystrlist = ["Howdy", "Hey There"] +Zstring = "Hello" + +[[Wsublist]] + String2 = "Three" + +[[Wsublist]] + String2 = "Four" + +[Xsubdoc] + String2 = "Two" +`) + +var basicTestTomlOrdered = []byte(`Zstring = "Hello" +Ystrlist = ["Howdy", "Hey There"] +String3 = "One" + +[Xsubdoc] + String2 = "Two" + +[[Wsublist]] + String2 = "Three" + +[[Wsublist]] + String2 = "Four" +`) + +var marshalTestToml = []byte(`title = "TOML Marshal Testing" + +[basic] + bool = true + date = 1979-05-27T07:32:00Z + float = 123.4 + float64 = 123.456782132399 + int = 5000 + string = "Bite me" + uint = 5001 + +[basic_lists] + bools = [true, false, true] + dates = [1979-05-27T07:32:00Z, 1980-05-27T07:32:00Z] + floats = [12.3, 45.6, 78.9] + ints = [8001, 8001, 8002] + strings = ["One", "Two", "Three"] + uints = [5002, 5003] + +[basic_map] + one = "one" + two = "two" + +[subdoc] + + [subdoc.first] + name = "First" + + [subdoc.second] + name = "Second" + +[[subdoclist]] + name = "List.First" + +[[subdoclist]] + name = "List.Second" + +[[subdocptrs]] + name = "Second" +`) + +var marshalOrderPreserveToml = []byte(`title = "TOML Marshal Testing" + +[basic_lists] + floats = [12.3, 45.6, 78.9] + bools = [true, false, true] + dates = [1979-05-27T07:32:00Z, 1980-05-27T07:32:00Z] + ints = [8001, 8001, 8002] + uints = [5002, 5003] + strings = ["One", "Two", "Three"] + +[[subdocptrs]] + name = "Second" + +[basic_map] + one = "one" + two = "two" + +[subdoc] + + [subdoc.second] + name = "Second" + + [subdoc.first] + name = "First" + +[basic] + uint = 5001 + bool = true + float = 123.4 + float64 = 123.456782132399 + int = 5000 + string = "Bite me" + date = 1979-05-27T07:32:00Z + +[[subdoclist]] + name = "List.First" + +[[subdoclist]] + name = "List.Second" +`) + +var mashalOrderPreserveMapToml = []byte(`title = "TOML Marshal Testing" + +[basic_map] + one = "one" + two = "two" + +[long_map] + a7 = "1" + b3 = "2" + c8 = "3" + d4 = "4" + e6 = "5" + f5 = "6" + g10 = "7" + h1 = "8" + i2 = "9" + j9 = "10" +`) + +type Conf struct { + Name string + Age int + Inter interface{} +} + +type NestedStruct struct { + FirstName string + LastName string + Age int +} + +var doc = []byte(`Name = "rui" +Age = 18 + +[Inter] + FirstName = "wang" + LastName = "jl" + Age = 100`) + +func TestInterface(t *testing.T) { + var config Conf + config.Inter = &NestedStruct{} + err := toml.Unmarshal(doc, &config) + expected := Conf{ + Name: "rui", + Age: 18, + Inter: &NestedStruct{ + FirstName: "wang", + LastName: "jl", + Age: 100, + }, + } + if err != nil || !reflect.DeepEqual(config, expected) { + t.Errorf("Bad unmarshal: expected %v, got %v", expected, config) + } +} + +func TestBasicUnmarshal(t *testing.T) { + result := basicMarshalTestStruct{} + err := toml.Unmarshal(basicTestToml, &result) + expected := basicTestData + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(result, expected) { + t.Errorf("Bad unmarshal: expected %v, got %v", expected, result) + } +} + +type quotedKeyMarshalTestStruct struct { + String string `toml:"Z.string-àéù"` + Float float64 `toml:"Yfloat-𝟘"` + Sub basicMarshalTestSubStruct `toml:"Xsubdoc-àéù"` + SubList []basicMarshalTestSubStruct `toml:"W.sublist-𝟘"` +} + +var quotedKeyMarshalTestData = quotedKeyMarshalTestStruct{ + String: "Hello", + Float: 3.5, + Sub: basicMarshalTestSubStruct{"One"}, + SubList: []basicMarshalTestSubStruct{{"Two"}, {"Three"}}, +} + +var quotedKeyMarshalTestToml = []byte(`"Yfloat-𝟘" = 3.5 +"Z.string-àéù" = "Hello" + +[["W.sublist-𝟘"]] + String2 = "Two" + +[["W.sublist-𝟘"]] + String2 = "Three" + +["Xsubdoc-àéù"] + String2 = "One" +`) + +type testDoc struct { + Title string `toml:"title"` + BasicLists testDocBasicLists `toml:"basic_lists"` + SubDocPtrs []*testSubDoc `toml:"subdocptrs"` + BasicMap map[string]string `toml:"basic_map"` + Subdocs testDocSubs `toml:"subdoc"` + Basics testDocBasics `toml:"basic"` + SubDocList []testSubDoc `toml:"subdoclist"` + err int `toml:"shouldntBeHere"` + unexported int `toml:"shouldntBeHere"` + Unexported2 int `toml:"-"` +} + +type testMapDoc struct { + Title string `toml:"title"` + BasicMap map[string]string `toml:"basic_map"` + LongMap map[string]string `toml:"long_map"` +} + +type testDocBasics struct { + Uint uint `toml:"uint"` + Bool bool `toml:"bool"` + Float32 float32 `toml:"float"` + Float64 float64 `toml:"float64"` + Int int `toml:"int"` + String *string `toml:"string"` + Date time.Time `toml:"date"` + unexported int `toml:"shouldntBeHere"` +} + +type testDocBasicLists struct { + Floats []*float32 `toml:"floats"` + Bools []bool `toml:"bools"` + Dates []time.Time `toml:"dates"` + Ints []int `toml:"ints"` + UInts []uint `toml:"uints"` + Strings []string `toml:"strings"` +} + +type testDocSubs struct { + Second *testSubDoc `toml:"second"` + First testSubDoc `toml:"first"` +} + +type testSubDoc struct { + Name string `toml:"name"` + unexported int `toml:"shouldntBeHere"` +} + +var biteMe = "Bite me" +var float1 float32 = 12.3 +var float2 float32 = 45.6 +var float3 float32 = 78.9 +var subdoc = testSubDoc{"Second", 0} + +var docData = testDoc{ + Title: "TOML Marshal Testing", + unexported: 0, + Unexported2: 0, + Basics: testDocBasics{ + Bool: true, + Date: time.Date(1979, 5, 27, 7, 32, 0, 0, time.UTC), + Float32: 123.4, + Float64: 123.456782132399, + Int: 5000, + Uint: 5001, + String: &biteMe, + unexported: 0, + }, + BasicLists: testDocBasicLists{ + Bools: []bool{true, false, true}, + Dates: []time.Time{ + time.Date(1979, 5, 27, 7, 32, 0, 0, time.UTC), + time.Date(1980, 5, 27, 7, 32, 0, 0, time.UTC), + }, + Floats: []*float32{&float1, &float2, &float3}, + Ints: []int{8001, 8001, 8002}, + Strings: []string{"One", "Two", "Three"}, + UInts: []uint{5002, 5003}, + }, + BasicMap: map[string]string{ + "one": "one", + "two": "two", + }, + Subdocs: testDocSubs{ + First: testSubDoc{"First", 0}, + Second: &subdoc, + }, + SubDocList: []testSubDoc{ + {"List.First", 0}, + {"List.Second", 0}, + }, + SubDocPtrs: []*testSubDoc{&subdoc}, +} + +var mapTestDoc = testMapDoc{ + Title: "TOML Marshal Testing", + BasicMap: map[string]string{ + "one": "one", + "two": "two", + }, + LongMap: map[string]string{ + "h1": "8", + "i2": "9", + "b3": "2", + "d4": "4", + "f5": "6", + "e6": "5", + "a7": "1", + "c8": "3", + "j9": "10", + "g10": "7", + }, +} + +func TestDocUnmarshal(t *testing.T) { + result := testDoc{} + err := toml.Unmarshal(marshalTestToml, &result) + expected := docData + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(result, expected) { + resStr, _ := json.MarshalIndent(result, "", " ") + expStr, _ := json.MarshalIndent(expected, "", " ") + t.Errorf("Bad unmarshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expStr, resStr) + } +} + +type tomlTypeCheckTest struct { + name string + item interface{} + typ int //0=primitive, 1=otherslice, 2=treeslice, 3=tree +} + +type unexportedMarshalTestStruct struct { + String string `toml:"string"` + StringList []string `toml:"strlist"` + Sub basicMarshalTestSubStruct `toml:"subdoc"` + SubList []basicMarshalTestSubStruct `toml:"sublist"` + unexported int `toml:"shouldntBeHere"` + Unexported2 int `toml:"-"` +} + +var unexportedTestData = unexportedMarshalTestStruct{ + String: "Hello", + StringList: []string{"Howdy", "Hey There"}, + Sub: basicMarshalTestSubStruct{"One"}, + SubList: []basicMarshalTestSubStruct{{"Two"}, {"Three"}}, + unexported: 0, + Unexported2: 0, +} + +var unexportedTestToml = []byte(`string = "Hello" +strlist = ["Howdy","Hey There"] +unexported = 1 +shouldntBeHere = 2 + +[subdoc] + String2 = "One" + +[[sublist]] + String2 = "Two" + +[[sublist]] + String2 = "Three" +`) + +func TestUnexportedUnmarshal(t *testing.T) { + result := unexportedMarshalTestStruct{} + err := toml.Unmarshal(unexportedTestToml, &result) + expected := unexportedTestData + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(result, expected) { + t.Errorf("Bad unexported unmarshal: expected %v, got %v", expected, result) + } +} + +type errStruct struct { + Bool bool `toml:"bool"` + Date time.Time `toml:"date"` + Float float64 `toml:"float"` + Int int16 `toml:"int"` + String *string `toml:"string"` +} + +var errTomls = []string{ + "bool = truly\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = 5000\nstring = \"Bite me\"", + "bool = true\ndate = 1979-05-27T07:3200Z\nfloat = 123.4\nint = 5000\nstring = \"Bite me\"", + "bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123a4\nint = 5000\nstring = \"Bite me\"", + "bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = j000\nstring = \"Bite me\"", + "bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = 5000\nstring = Bite me", + "bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = 5000\nstring = Bite me", + "bool = 1\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = 5000\nstring = \"Bite me\"", + "bool = true\ndate = 1\nfloat = 123.4\nint = 5000\nstring = \"Bite me\"", + "bool = true\ndate = 1979-05-27T07:32:00Z\n\"sorry\"\nint = 5000\nstring = \"Bite me\"", + "bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = \"sorry\"\nstring = \"Bite me\"", + "bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = 5000\nstring = 1", +} + +type mapErr struct { + Vals map[string]float64 +} + +type intErr struct { + Int1 int + Int2 int8 + Int3 int16 + Int4 int32 + Int5 int64 + UInt1 uint + UInt2 uint8 + UInt3 uint16 + UInt4 uint32 + UInt5 uint64 + Flt1 float32 + Flt2 float64 +} + +var intErrTomls = []string{ + "Int1 = []\nInt2 = 2\nInt3 = 3\nInt4 = 4\nInt5 = 5\nUInt1 = 1\nUInt2 = 2\nUInt3 = 3\nUInt4 = 4\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = 2.0", + "Int1 = 1\nInt2 = []\nInt3 = 3\nInt4 = 4\nInt5 = 5\nUInt1 = 1\nUInt2 = 2\nUInt3 = 3\nUInt4 = 4\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = 2.0", + "Int1 = 1\nInt2 = 2\nInt3 = []\nInt4 = 4\nInt5 = 5\nUInt1 = 1\nUInt2 = 2\nUInt3 = 3\nUInt4 = 4\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = 2.0", + "Int1 = 1\nInt2 = 2\nInt3 = 3\nInt4 = []\nInt5 = 5\nUInt1 = 1\nUInt2 = 2\nUInt3 = 3\nUInt4 = 4\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = 2.0", + "Int1 = 1\nInt2 = 2\nInt3 = 3\nInt4 = 4\nInt5 = []\nUInt1 = 1\nUInt2 = 2\nUInt3 = 3\nUInt4 = 4\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = 2.0", + "Int1 = 1\nInt2 = 2\nInt3 = 3\nInt4 = 4\nInt5 = 5\nUInt1 = []\nUInt2 = 2\nUInt3 = 3\nUInt4 = 4\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = 2.0", + "Int1 = 1\nInt2 = 2\nInt3 = 3\nInt4 = 4\nInt5 = 5\nUInt1 = 1\nUInt2 = []\nUInt3 = 3\nUInt4 = 4\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = 2.0", + "Int1 = 1\nInt2 = 2\nInt3 = 3\nInt4 = 4\nInt5 = 5\nUInt1 = 1\nUInt2 = 2\nUInt3 = []\nUInt4 = 4\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = 2.0", + "Int1 = 1\nInt2 = 2\nInt3 = 3\nInt4 = 4\nInt5 = 5\nUInt1 = 1\nUInt2 = 2\nUInt3 = 3\nUInt4 = []\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = 2.0", + "Int1 = 1\nInt2 = 2\nInt3 = 3\nInt4 = 4\nInt5 = 5\nUInt1 = 1\nUInt2 = 2\nUInt3 = 3\nUInt4 = 4\nUInt5 = []\nFlt1 = 1.0\nFlt2 = 2.0", + "Int1 = 1\nInt2 = 2\nInt3 = 3\nInt4 = 4\nInt5 = 5\nUInt1 = 1\nUInt2 = 2\nUInt3 = 3\nUInt4 = 4\nUInt5 = 5\nFlt1 = []\nFlt2 = 2.0", + "Int1 = 1\nInt2 = 2\nInt3 = 3\nInt4 = 4\nInt5 = 5\nUInt1 = 1\nUInt2 = 2\nUInt3 = 3\nUInt4 = 4\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = []", +} + +func TestErrUnmarshal(t *testing.T) { + for ind, x := range errTomls { + result := errStruct{} + err := toml.Unmarshal([]byte(x), &result) + if err == nil { + t.Errorf("Expected err from case %d\n", ind) + } + } + result2 := mapErr{} + err := toml.Unmarshal([]byte("[Vals]\nfred=\"1.2\""), &result2) + if err == nil { + t.Errorf("Expected err from map") + } + for ind, x := range intErrTomls { + result3 := intErr{} + err := toml.Unmarshal([]byte(x), &result3) + if err == nil { + t.Errorf("Expected int err from case %d\n", ind) + } + } +} + +type emptyMarshalTestStruct struct { + Title string `toml:"title"` + Bool bool `toml:"bool"` + Int int `toml:"int"` + String string `toml:"string"` + StringList []string `toml:"stringlist"` + Ptr *basicMarshalTestStruct `toml:"ptr"` + Map map[string]string `toml:"map"` +} + +var emptyTestData = emptyMarshalTestStruct{ + Title: "Placeholder", + Bool: false, + Int: 0, + String: "", + StringList: []string{}, + Ptr: nil, + Map: map[string]string{}, +} + +var emptyTestToml = []byte(`bool = false +int = 0 +string = "" +stringlist = [] +title = "Placeholder" + +[map] +`) + +type emptyMarshalTestStruct2 struct { + Title string `toml:"title"` + Bool bool `toml:"bool,omitempty"` + Int int `toml:"int, omitempty"` + String string `toml:"string,omitempty "` + StringList []string `toml:"stringlist,omitempty"` + Ptr *basicMarshalTestStruct `toml:"ptr,omitempty"` + Map map[string]string `toml:"map,omitempty"` +} + +var emptyTestData2 = emptyMarshalTestStruct2{ + Title: "Placeholder", + Bool: false, + Int: 0, + String: "", + StringList: []string{}, + Ptr: nil, + Map: map[string]string{}, +} + +var emptyTestToml2 = []byte(`title = "Placeholder" +`) + +func TestEmptytomlUnmarshal(t *testing.T) { + result := emptyMarshalTestStruct{} + err := toml.Unmarshal(emptyTestToml, &result) + expected := emptyTestData + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(result, expected) { + t.Errorf("Bad empty unmarshal: expected %v, got %v", expected, result) + } +} + +func TestEmptyUnmarshalOmit(t *testing.T) { + result := emptyMarshalTestStruct2{} + err := toml.Unmarshal(emptyTestToml, &result) + expected := emptyTestData2 + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(result, expected) { + t.Errorf("Bad empty omit unmarshal: expected %v, got %v", expected, result) + } +} + +type pointerMarshalTestStruct struct { + Str *string + List *[]string + ListPtr *[]*string + Map *map[string]string + MapPtr *map[string]*string + EmptyStr *string + EmptyList *[]string + EmptyMap *map[string]string + DblPtr *[]*[]*string +} + +var pointerStr = "Hello" +var pointerList = []string{"Hello back"} +var pointerListPtr = []*string{&pointerStr} +var pointerMap = map[string]string{"response": "Goodbye"} +var pointerMapPtr = map[string]*string{"alternate": &pointerStr} +var pointerTestData = pointerMarshalTestStruct{ + Str: &pointerStr, + List: &pointerList, + ListPtr: &pointerListPtr, + Map: &pointerMap, + MapPtr: &pointerMapPtr, + EmptyStr: nil, + EmptyList: nil, + EmptyMap: nil, +} + +var pointerTestToml = []byte(`List = ["Hello back"] +ListPtr = ["Hello"] +Str = "Hello" + +[Map] + response = "Goodbye" + +[MapPtr] + alternate = "Hello" +`) + +func TestPointerUnmarshal(t *testing.T) { + result := pointerMarshalTestStruct{} + err := toml.Unmarshal(pointerTestToml, &result) + expected := pointerTestData + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(result, expected) { + t.Errorf("Bad pointer unmarshal: expected %v, got %v", expected, result) + } +} + +func TestUnmarshalTypeMismatch(t *testing.T) { + result := pointerMarshalTestStruct{} + err := toml.Unmarshal([]byte("List = 123"), &result) + if !strings.HasPrefix(err.Error(), "(1, 1): Can't convert 123(int64) to []string(slice)") { + t.Errorf("Type mismatch must be reported: got %v", err.Error()) + } +} + +type nestedMarshalTestStruct struct { + String [][]string + //Struct [][]basicMarshalTestSubStruct + StringPtr *[]*[]*string + // StructPtr *[]*[]*basicMarshalTestSubStruct +} + +var str1 = "Three" +var str2 = "Four" +var strPtr = []*string{&str1, &str2} +var strPtr2 = []*[]*string{&strPtr} + +var nestedTestData = nestedMarshalTestStruct{ + String: [][]string{{"Five", "Six"}, {"One", "Two"}}, + StringPtr: &strPtr2, +} + +var nestedTestToml = []byte(`String = [["Five", "Six"], ["One", "Two"]] +StringPtr = [["Three", "Four"]] +`) + +func TestNestedUnmarshal(t *testing.T) { + result := nestedMarshalTestStruct{} + err := toml.Unmarshal(nestedTestToml, &result) + expected := nestedTestData + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(result, expected) { + t.Errorf("Bad nested unmarshal: expected %v, got %v", expected, result) + } +} + +type customMarshalerParent struct { + Self customMarshaler `toml:"me"` + Friends []customMarshaler `toml:"friends"` +} + +type customMarshaler struct { + FirstName string + LastName string +} + +func (c customMarshaler) MarshalTOML() ([]byte, error) { + fullName := fmt.Sprintf("%s %s", c.FirstName, c.LastName) + return []byte(fullName), nil +} + +var customMarshalerData = customMarshaler{FirstName: "Sally", LastName: "Fields"} +var customMarshalerToml = []byte(`Sally Fields`) +var nestedCustomMarshalerData = customMarshalerParent{ + Self: customMarshaler{FirstName: "Maiku", LastName: "Suteda"}, + Friends: []customMarshaler{customMarshalerData}, +} +var nestedCustomMarshalerToml = []byte(`friends = ["Sally Fields"] +me = "Maiku Suteda" +`) +var nestedCustomMarshalerTomlForUnmarshal = []byte(`[friends] +FirstName = "Sally" +LastName = "Fields"`) + +type IntOrString string + +func (x *IntOrString) MarshalTOML() ([]byte, error) { + s := *(*string)(x) + _, err := strconv.Atoi(s) + if err != nil { + return []byte(fmt.Sprintf(`"%s"`, s)), nil + } + return []byte(s), nil +} + +type textMarshaler struct { + FirstName string + LastName string +} + +func (m textMarshaler) MarshalText() ([]byte, error) { + fullName := fmt.Sprintf("%s %s", m.FirstName, m.LastName) + return []byte(fullName), nil +} + +func TestUnmarshalTextMarshaler(t *testing.T) { + var nested = struct { + Friends textMarshaler `toml:"friends"` + }{} + + var expected = struct { + Friends textMarshaler `toml:"friends"` + }{ + Friends: textMarshaler{FirstName: "Sally", LastName: "Fields"}, + } + + err := toml.Unmarshal(nestedCustomMarshalerTomlForUnmarshal, &nested) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(nested, expected) { + t.Errorf("Bad unmarshal: expected %v, got %v", expected, nested) + } +} + +type precedentMarshaler struct { + FirstName string + LastName string +} + +func (m precedentMarshaler) MarshalText() ([]byte, error) { + return []byte("shadowed"), nil +} + +func (m precedentMarshaler) MarshalTOML() ([]byte, error) { + fullName := fmt.Sprintf("%s %s", m.FirstName, m.LastName) + return []byte(fullName), nil +} + +type customPointerMarshaler struct { + FirstName string + LastName string +} + +func (m *customPointerMarshaler) MarshalTOML() ([]byte, error) { + return []byte(`"hidden"`), nil +} + +type textPointerMarshaler struct { + FirstName string + LastName string +} + +func (m *textPointerMarshaler) MarshalText() ([]byte, error) { + return []byte("hidden"), nil +} + +var commentTestToml = []byte(` +# it's a comment on type +[postgres] + # isCommented = "dvalue" + noComment = "cvalue" + + # A comment on AttrB with a + # break line + password = "bvalue" + + # A comment on AttrA + user = "avalue" + + [[postgres.My]] + + # a comment on my on typeC + My = "Foo" + + [[postgres.My]] + + # a comment on my on typeC + My = "Baar" +`) + +type mapsTestStruct struct { + Simple map[string]string + Paths map[string]string + Other map[string]float64 + X struct { + Y struct { + Z map[string]bool + } + } +} + +var mapsTestData = mapsTestStruct{ + Simple: map[string]string{ + "one plus one": "two", + "next": "three", + }, + Paths: map[string]string{ + "/this/is/a/path": "/this/is/also/a/path", + "/heloo.txt": "/tmp/lololo.txt", + }, + Other: map[string]float64{ + "testing": 3.9999, + }, + X: struct{ Y struct{ Z map[string]bool } }{ + Y: struct{ Z map[string]bool }{ + Z: map[string]bool{ + "is.Nested": true, + }, + }, + }, +} +var mapsTestToml = []byte(` +[Other] + "testing" = 3.9999 + +[Paths] + "/heloo.txt" = "/tmp/lololo.txt" + "/this/is/a/path" = "/this/is/also/a/path" + +[Simple] + "next" = "three" + "one plus one" = "two" + +[X] + + [X.Y] + + [X.Y.Z] + "is.Nested" = true +`) + +type structArrayNoTag struct { + A struct { + B []int64 + C []int64 + } +} + +var customTagTestToml = []byte(` +[postgres] + password = "bvalue" + user = "avalue" + + [[postgres.My]] + My = "Foo" + + [[postgres.My]] + My = "Baar" +`) + +var customCommentTagTestToml = []byte(` +# db connection +[postgres] + + # db pass + password = "bvalue" + + # db user + user = "avalue" +`) + +var customCommentedTagTestToml = []byte(` +[postgres] + # password = "bvalue" + # user = "avalue" +`) + +func TestUnmarshalTabInStringAndQuotedKey(t *testing.T) { + type Test struct { + Field1 string `toml:"Fie ld1"` + Field2 string + } + + type TestCase struct { + desc string + input []byte + expected Test + } + + testCases := []TestCase{ + { + desc: "multiline string with tab", + input: []byte("Field2 = \"\"\"\nhello\tworld\"\"\""), + expected: Test{ + Field2: "hello\tworld", + }, + }, + { + desc: "quoted key with tab", + input: []byte("\"Fie\tld1\" = \"key with tab\""), + expected: Test{ + Field1: "key with tab", + }, + }, + { + desc: "basic string tab", + input: []byte("Field2 = \"hello\tworld\""), + expected: Test{ + Field2: "hello\tworld", + }, + }, + } + + for i := range testCases { + result := Test{} + err := toml.Unmarshal(testCases[i].input, &result) + if err != nil { + t.Errorf("%s test error:%v", testCases[i].desc, err) + continue + } + + if !reflect.DeepEqual(result, testCases[i].expected) { + t.Errorf("%s test error: expected\n-----\n%+v\n-----\ngot\n-----\n%+v\n-----\n", + testCases[i].desc, testCases[i].expected, result) + } + } +} + +var customMultilineTagTestToml = []byte(`int_slice = [ + 1, + 2, + 3, +] +`) + +var testDocBasicToml = []byte(` +[document] + bool_val = true + date_val = 1979-05-27T07:32:00Z + float_val = 123.4 + int_val = 5000 + string_val = "Bite me" + uint_val = 5001 +`) + +type testDocCustomTag struct { + Doc testDocBasicsCustomTag `file:"document"` +} +type testDocBasicsCustomTag struct { + Bool bool `file:"bool_val"` + Date time.Time `file:"date_val"` + Float float32 `file:"float_val"` + Int int `file:"int_val"` + Uint uint `file:"uint_val"` + String *string `file:"string_val"` + unexported int `file:"shouldntBeHere"` +} + +var testDocCustomTagData = testDocCustomTag{ + Doc: testDocBasicsCustomTag{ + Bool: true, + Date: time.Date(1979, 5, 27, 7, 32, 0, 0, time.UTC), + Float: 123.4, + Int: 5000, + Uint: 5001, + String: &biteMe, + unexported: 0, + }, +} + +func TestUnmarshalMap(t *testing.T) { + testToml := []byte(` + a = 1 + b = 2 + c = 3 + `) + var result map[string]int + err := toml.Unmarshal(testToml, &result) + if err != nil { + t.Errorf("Received unexpected error: %s", err) + return + } + + expected := map[string]int{ + "a": 1, + "b": 2, + "c": 3, + } + + if !reflect.DeepEqual(result, expected) { + t.Errorf("Bad unmarshal: expected %v, got %v", expected, result) + } +} + +func TestUnmarshalMapWithTypedKey(t *testing.T) { + testToml := []byte(` + a = 1 + b = 2 + c = 3 + `) + + type letter string + var result map[letter]int + err := toml.Unmarshal(testToml, &result) + if err != nil { + t.Errorf("Received unexpected error: %s", err) + return + } + + expected := map[letter]int{ + "a": 1, + "b": 2, + "c": 3, + } + + if !reflect.DeepEqual(result, expected) { + t.Errorf("Bad unmarshal: expected %v, got %v", expected, result) + } +} + +func TestUnmarshalNonPointer(t *testing.T) { + a := 1 + err := toml.Unmarshal([]byte{}, a) + if err == nil { + t.Fatal("unmarshal should err when given a non pointer") + } +} + +func TestUnmarshalInvalidPointerKind(t *testing.T) { + a := 1 + err := toml.Unmarshal([]byte{}, &a) + if err == nil { + t.Fatal("unmarshal should err when given an invalid pointer type") + } +} + +type testDuration struct { + Nanosec time.Duration `toml:"nanosec"` + Microsec1 time.Duration `toml:"microsec1"` + Microsec2 *time.Duration `toml:"microsec2"` + Millisec time.Duration `toml:"millisec"` + Sec time.Duration `toml:"sec"` + Min time.Duration `toml:"min"` + Hour time.Duration `toml:"hour"` + Mixed time.Duration `toml:"mixed"` + AString string `toml:"a_string"` +} + +var testDurationToml = []byte(` +nanosec = "1ns" +microsec1 = "1us" +microsec2 = "1µs" +millisec = "1ms" +sec = "1s" +min = "1m" +hour = "1h" +mixed = "1h1m1s1ms1µs1ns" +a_string = "15s" +`) + +var testDurationToml2 = []byte(`a_string = "15s" +hour = "1h0m0s" +microsec1 = "1µs" +microsec2 = "1µs" +millisec = "1ms" +min = "1m0s" +mixed = "1h1m1.001001001s" +nanosec = "1ns" +sec = "1s" +`) + +type testBadDuration struct { + Val time.Duration `toml:"val"` +} + +var testCamelCaseKeyToml = []byte(`fooBar = 10`) + +func TestUnmarshalCamelCaseKey(t *testing.T) { + var x struct { + FooBar int + B int + } + + if err := toml.Unmarshal(testCamelCaseKeyToml, &x); err != nil { + t.Fatal(err) + } + + if x.FooBar != 10 { + t.Fatal("Did not set camelCase'd key") + } +} + +func TestUnmarshalNegativeUint(t *testing.T) { + type check struct{ U uint } + err := toml.Unmarshal([]byte("u = -1"), &check{}) + if err.Error() != "(1, 1): -1(int64) is negative so does not fit in uint" { + t.Error("expect err:(1, 1): -1(int64) is negative so does not fit in uint but got:", err) + } +} + +func TestUnmarshalCheckConversionFloatInt(t *testing.T) { + type conversionCheck struct { + U uint + I int + F float64 + } + + errU := toml.Unmarshal([]byte(`u = 1e300`), &conversionCheck{}) + errI := toml.Unmarshal([]byte(`i = 1e300`), &conversionCheck{}) + errF := toml.Unmarshal([]byte(`f = 9223372036854775806`), &conversionCheck{}) + + if errU.Error() != "(1, 1): Can't convert 1e+300(float64) to uint" { + t.Error("expect err:(1, 1): Can't convert 1e+300(float64) to uint but got:", errU) + } + if errI.Error() != "(1, 1): Can't convert 1e+300(float64) to int" { + t.Error("expect err:(1, 1): Can't convert 1e+300(float64) to int but got:", errI) + } + if errF.Error() != "(1, 1): Can't convert 9223372036854775806(int64) to float64" { + t.Error("expect err:(1, 1): Can't convert 9223372036854775806(int64) to float64 but got:", errF) + } +} + +func TestUnmarshalOverflow(t *testing.T) { + type overflow struct { + U8 uint8 + I8 int8 + F32 float32 + } + + errU8 := toml.Unmarshal([]byte(`u8 = 300`), &overflow{}) + errI8 := toml.Unmarshal([]byte(`i8 = 300`), &overflow{}) + errF32 := toml.Unmarshal([]byte(`f32 = 1e300`), &overflow{}) + + if errU8.Error() != "(1, 1): 300(int64) would overflow uint8" { + t.Error("expect err:(1, 1): 300(int64) would overflow uint8 but got:", errU8) + } + if errI8.Error() != "(1, 1): 300(int64) would overflow int8" { + t.Error("expect err:(1, 1): 300(int64) would overflow int8 but got:", errI8) + } + if errF32.Error() != "(1, 1): 1e+300(float64) would overflow float32" { + t.Error("expect err:(1, 1): 1e+300(float64) would overflow float32 but got:", errF32) + } +} + +func TestUnmarshalDefault(t *testing.T) { + type EmbeddedStruct struct { + StringField string `default:"c"` + } + + type aliasUint uint + + var doc struct { + StringField string `default:"a"` + BoolField bool `default:"true"` + UintField uint `default:"1"` + Uint8Field uint8 `default:"8"` + Uint16Field uint16 `default:"16"` + Uint32Field uint32 `default:"32"` + Uint64Field uint64 `default:"64"` + IntField int `default:"-1"` + Int8Field int8 `default:"-8"` + Int16Field int16 `default:"-16"` + Int32Field int32 `default:"-32"` + Int64Field int64 `default:"-64"` + Float32Field float32 `default:"32.1"` + Float64Field float64 `default:"64.1"` + DurationField time.Duration `default:"120ms"` + DurationField2 time.Duration `default:"120000000"` + NonEmbeddedStruct struct { + StringField string `default:"b"` + } + EmbeddedStruct + AliasUintField aliasUint `default:"1000"` + } + + err := toml.Unmarshal([]byte(``), &doc) + if err != nil { + t.Fatal(err) + } + if doc.BoolField != true { + t.Errorf("BoolField should be true, not %t", doc.BoolField) + } + if doc.StringField != "a" { + t.Errorf("StringField should be \"a\", not %s", doc.StringField) + } + if doc.UintField != 1 { + t.Errorf("UintField should be 1, not %d", doc.UintField) + } + if doc.Uint8Field != 8 { + t.Errorf("Uint8Field should be 8, not %d", doc.Uint8Field) + } + if doc.Uint16Field != 16 { + t.Errorf("Uint16Field should be 16, not %d", doc.Uint16Field) + } + if doc.Uint32Field != 32 { + t.Errorf("Uint32Field should be 32, not %d", doc.Uint32Field) + } + if doc.Uint64Field != 64 { + t.Errorf("Uint64Field should be 64, not %d", doc.Uint64Field) + } + if doc.IntField != -1 { + t.Errorf("IntField should be -1, not %d", doc.IntField) + } + if doc.Int8Field != -8 { + t.Errorf("Int8Field should be -8, not %d", doc.Int8Field) + } + if doc.Int16Field != -16 { + t.Errorf("Int16Field should be -16, not %d", doc.Int16Field) + } + if doc.Int32Field != -32 { + t.Errorf("Int32Field should be -32, not %d", doc.Int32Field) + } + if doc.Int64Field != -64 { + t.Errorf("Int64Field should be -64, not %d", doc.Int64Field) + } + if doc.Float32Field != 32.1 { + t.Errorf("Float32Field should be 32.1, not %f", doc.Float32Field) + } + if doc.Float64Field != 64.1 { + t.Errorf("Float64Field should be 64.1, not %f", doc.Float64Field) + } + if doc.DurationField != 120*time.Millisecond { + t.Errorf("DurationField should be 120ms, not %s", doc.DurationField.String()) + } + if doc.DurationField2 != 120*time.Millisecond { + t.Errorf("DurationField2 should be 120000000, not %d", doc.DurationField2) + } + if doc.NonEmbeddedStruct.StringField != "b" { + t.Errorf("StringField should be \"b\", not %s", doc.NonEmbeddedStruct.StringField) + } + if doc.EmbeddedStruct.StringField != "c" { + t.Errorf("StringField should be \"c\", not %s", doc.EmbeddedStruct.StringField) + } + if doc.AliasUintField != 1000 { + t.Errorf("AliasUintField should be 1000, not %d", doc.AliasUintField) + } +} + +func TestUnmarshalDefaultFailureBool(t *testing.T) { + var doc struct { + Field bool `default:"blah"` + } + + err := toml.Unmarshal([]byte(``), &doc) + if err == nil { + t.Fatal("should error") + } +} + +func TestUnmarshalDefaultFailureInt(t *testing.T) { + var doc struct { + Field int `default:"blah"` + } + + err := toml.Unmarshal([]byte(``), &doc) + if err == nil { + t.Fatal("should error") + } +} + +func TestUnmarshalDefaultFailureInt64(t *testing.T) { + var doc struct { + Field int64 `default:"blah"` + } + + err := toml.Unmarshal([]byte(``), &doc) + if err == nil { + t.Fatal("should error") + } +} + +func TestUnmarshalDefaultFailureFloat64(t *testing.T) { + var doc struct { + Field float64 `default:"blah"` + } + + err := toml.Unmarshal([]byte(``), &doc) + if err == nil { + t.Fatal("should error") + } +} + +func TestUnmarshalDefaultFailureDuration(t *testing.T) { + var doc struct { + Field time.Duration `default:"blah"` + } + + err := toml.Unmarshal([]byte(``), &doc) + if err == nil { + t.Fatal("should error") + } +} + +func TestUnmarshalDefaultFailureUnsupported(t *testing.T) { + var doc struct { + Field struct{} `default:"blah"` + } + + err := toml.Unmarshal([]byte(``), &doc) + if err == nil { + t.Fatal("should error") + } +} + +func TestUnmarshalNestedAnonymousStructs(t *testing.T) { + type Nested struct { + Value string `toml:"nested_field"` + } + type Deep struct { + Nested + } + type Document struct { + Deep + Value string `toml:"own_field"` + } + + var doc Document + + err := toml.Unmarshal([]byte(`nested_field = "nested value"`+"\n"+`own_field = "own value"`), &doc) + if err != nil { + t.Fatal("should not error") + } + if doc.Value != "own value" || doc.Nested.Value != "nested value" { + t.Fatal("unexpected values") + } +} + +func TestUnmarshalNestedAnonymousStructs_Controversial(t *testing.T) { + type Nested struct { + Value string `toml:"nested"` + } + type Deep struct { + Nested + } + type Document struct { + Deep + Value string `toml:"own"` + } + + var doc Document + + err := toml.Unmarshal([]byte(`nested = "nested value"`+"\n"+`own = "own value"`), &doc) + if err == nil { + t.Fatal("should error") + } +} + +type unexportedFieldPreservationTest struct { + Exported string `toml:"exported"` + unexported string + Nested1 unexportedFieldPreservationTestNested `toml:"nested1"` + Nested2 *unexportedFieldPreservationTestNested `toml:"nested2"` + Nested3 *unexportedFieldPreservationTestNested `toml:"nested3"` + Slice1 []unexportedFieldPreservationTestNested `toml:"slice1"` + Slice2 []*unexportedFieldPreservationTestNested `toml:"slice2"` +} + +type unexportedFieldPreservationTestNested struct { + Exported1 string `toml:"exported1"` + unexported1 string +} + +func TestUnmarshalPreservesUnexportedFields(t *testing.T) { + doc := ` + exported = "visible" + unexported = "ignored" + + [nested1] + exported1 = "visible1" + unexported1 = "ignored1" + + [nested2] + exported1 = "visible2" + unexported1 = "ignored2" + + [nested3] + exported1 = "visible3" + unexported1 = "ignored3" + + [[slice1]] + exported1 = "visible3" + + [[slice1]] + exported1 = "visible4" + + [[slice2]] + exported1 = "visible5" + ` + + t.Run("unexported field should not be set from toml", func(t *testing.T) { + var actual unexportedFieldPreservationTest + err := toml.Unmarshal([]byte(doc), &actual) + + if err != nil { + t.Fatal("did not expect an error") + } + + expect := unexportedFieldPreservationTest{ + Exported: "visible", + unexported: "", + Nested1: unexportedFieldPreservationTestNested{"visible1", ""}, + Nested2: &unexportedFieldPreservationTestNested{"visible2", ""}, + Nested3: &unexportedFieldPreservationTestNested{"visible3", ""}, + Slice1: []unexportedFieldPreservationTestNested{ + {Exported1: "visible3"}, + {Exported1: "visible4"}, + }, + Slice2: []*unexportedFieldPreservationTestNested{ + {Exported1: "visible5"}, + }, + } + + if !reflect.DeepEqual(actual, expect) { + t.Fatalf("%+v did not equal %+v", actual, expect) + } + }) + + t.Run("unexported field should be preserved", func(t *testing.T) { + actual := unexportedFieldPreservationTest{ + Exported: "foo", + unexported: "bar", + Nested1: unexportedFieldPreservationTestNested{"baz", "bax"}, + Nested2: nil, + Nested3: &unexportedFieldPreservationTestNested{"baz", "bax"}, + } + err := toml.Unmarshal([]byte(doc), &actual) + + if err != nil { + t.Fatal("did not expect an error") + } + + expect := unexportedFieldPreservationTest{ + Exported: "visible", + unexported: "bar", + Nested1: unexportedFieldPreservationTestNested{"visible1", "bax"}, + Nested2: &unexportedFieldPreservationTestNested{"visible2", ""}, + Nested3: &unexportedFieldPreservationTestNested{"visible3", "bax"}, + Slice1: []unexportedFieldPreservationTestNested{ + {Exported1: "visible3"}, + {Exported1: "visible4"}, + }, + Slice2: []*unexportedFieldPreservationTestNested{ + {Exported1: "visible5"}, + }, + } + + if !reflect.DeepEqual(actual, expect) { + t.Fatalf("%+v did not equal %+v", actual, expect) + } + }) +} + +func TestUnmarshalLocalDate(t *testing.T) { + t.Run("ToLocalDate", func(t *testing.T) { + type dateStruct struct { + Date toml.LocalDate + } + + doc := `date = 1979-05-27` + + var obj dateStruct + + err := toml.Unmarshal([]byte(doc), &obj) + + if err != nil { + t.Fatal(err) + } + + if obj.Date.Year != 1979 { + t.Errorf("expected year 1979, got %d", obj.Date.Year) + } + if obj.Date.Month != 5 { + t.Errorf("expected month 5, got %d", obj.Date.Month) + } + if obj.Date.Day != 27 { + t.Errorf("expected day 27, got %d", obj.Date.Day) + } + }) + + t.Run("ToLocalDate", func(t *testing.T) { + type dateStruct struct { + Date time.Time + } + + doc := `date = 1979-05-27` + + var obj dateStruct + + err := toml.Unmarshal([]byte(doc), &obj) + + if err != nil { + t.Fatal(err) + } + + if obj.Date.Year() != 1979 { + t.Errorf("expected year 1979, got %d", obj.Date.Year()) + } + if obj.Date.Month() != 5 { + t.Errorf("expected month 5, got %d", obj.Date.Month()) + } + if obj.Date.Day() != 27 { + t.Errorf("expected day 27, got %d", obj.Date.Day()) + } + }) +} + +func TestUnmarshalLocalDateTime(t *testing.T) { + examples := []struct { + name string + in string + out toml.LocalDateTime + }{ + { + name: "normal", + in: "1979-05-27T07:32:00", + out: toml.LocalDateTime{ + Date: toml.LocalDate{ + Year: 1979, + Month: 5, + Day: 27, + }, + Time: toml.LocalTime{ + Hour: 7, + Minute: 32, + Second: 0, + Nanosecond: 0, + }, + }}, + { + name: "with nanoseconds", + in: "1979-05-27T00:32:00.999999", + out: toml.LocalDateTime{ + Date: toml.LocalDate{ + Year: 1979, + Month: 5, + Day: 27, + }, + Time: toml.LocalTime{ + Hour: 0, + Minute: 32, + Second: 0, + Nanosecond: 999999000, + }, + }, + }, + } + + for i, example := range examples { + doc := fmt.Sprintf(`date = %s`, example.in) + + t.Run(fmt.Sprintf("ToLocalDateTime_%d_%s", i, example.name), func(t *testing.T) { + type dateStruct struct { + Date toml.LocalDateTime + } + + var obj dateStruct + + err := toml.Unmarshal([]byte(doc), &obj) + + if err != nil { + t.Fatal(err) + } + + if obj.Date != example.out { + t.Errorf("expected '%s', got '%s'", example.out, obj.Date) + } + }) + + t.Run(fmt.Sprintf("ToTime_%d_%s", i, example.name), func(t *testing.T) { + type dateStruct struct { + Date time.Time + } + + var obj dateStruct + + err := toml.Unmarshal([]byte(doc), &obj) + + if err != nil { + t.Fatal(err) + } + + if obj.Date.Year() != example.out.Date.Year { + t.Errorf("expected year %d, got %d", example.out.Date.Year, obj.Date.Year()) + } + if obj.Date.Month() != example.out.Date.Month { + t.Errorf("expected month %d, got %d", example.out.Date.Month, obj.Date.Month()) + } + if obj.Date.Day() != example.out.Date.Day { + t.Errorf("expected day %d, got %d", example.out.Date.Day, obj.Date.Day()) + } + if obj.Date.Hour() != example.out.Time.Hour { + t.Errorf("expected hour %d, got %d", example.out.Time.Hour, obj.Date.Hour()) + } + if obj.Date.Minute() != example.out.Time.Minute { + t.Errorf("expected minute %d, got %d", example.out.Time.Minute, obj.Date.Minute()) + } + if obj.Date.Second() != example.out.Time.Second { + t.Errorf("expected second %d, got %d", example.out.Time.Second, obj.Date.Second()) + } + if obj.Date.Nanosecond() != example.out.Time.Nanosecond { + t.Errorf("expected nanoseconds %d, got %d", example.out.Time.Nanosecond, obj.Date.Nanosecond()) + } + }) + } +} + +func TestUnmarshalLocalTime(t *testing.T) { + examples := []struct { + name string + in string + out toml.LocalTime + }{ + { + name: "normal", + in: "07:32:00", + out: toml.LocalTime{ + Hour: 7, + Minute: 32, + Second: 0, + Nanosecond: 0, + }, + }, + { + name: "with nanoseconds", + in: "00:32:00.999999", + out: toml.LocalTime{ + Hour: 0, + Minute: 32, + Second: 0, + Nanosecond: 999999000, + }, + }, + } + + for i, example := range examples { + doc := fmt.Sprintf(`Time = %s`, example.in) + + t.Run(fmt.Sprintf("ToLocalTime_%d_%s", i, example.name), func(t *testing.T) { + type dateStruct struct { + Time toml.LocalTime + } + + var obj dateStruct + + err := toml.Unmarshal([]byte(doc), &obj) + + if err != nil { + t.Fatal(err) + } + + if obj.Time != example.out { + t.Errorf("expected '%s', got '%s'", example.out, obj.Time) + } + }) + } +} + +// test case for issue #339 +func TestUnmarshalSameInnerField(t *testing.T) { + type InterStruct2 struct { + Test string + Name string + Age int + } + type Inter2 struct { + Name string + Age int + InterStruct2 InterStruct2 + } + type Server struct { + Name string `toml:"name"` + Inter2 Inter2 `toml:"inter2"` + } + + var server Server + + if err := toml.Unmarshal([]byte(`name = "123" +[inter2] +name = "inter2" +age = 222`), &server); err == nil { + expected := Server{ + Name: "123", + Inter2: Inter2{ + Name: "inter2", + Age: 222, + }, + } + if !reflect.DeepEqual(server, expected) { + t.Errorf("Bad unmarshal: expected %v, got %v", expected, server) + } + } else { + t.Fatalf("unexpected error: %v", err) + } +} + +func TestUnmarshalToNilInterface(t *testing.T) { + doc := []byte(` +PrimitiveField = "Hello" +ArrayField = [1,2,3] +InterfacePointerField = "World" + +[StructField] +Field1 = 123 +Field2 = "Field2" + +[MapField] +MapField1 = [4,5,6] +MapField2 = {A = "A"} +MapField3 = false + +[[StructArrayField]] +Name = "Allen" +Age = 20 + +[[StructArrayField]] +Name = "Jack" +Age = 23 +`) + + type OuterStruct struct { + PrimitiveField interface{} + ArrayField interface{} + StructArrayField interface{} + MapField map[string]interface{} + StructField interface{} + NilField interface{} + InterfacePointerField *interface{} + } + + var s interface{} = "World" + expected := OuterStruct{ + PrimitiveField: "Hello", + ArrayField: []interface{}{int64(1), int64(2), int64(3)}, + StructField: map[string]interface{}{ + "Field1": int64(123), + "Field2": "Field2", + }, + MapField: map[string]interface{}{ + "MapField1": []interface{}{int64(4), int64(5), int64(6)}, + "MapField2": map[string]interface{}{ + "A": "A", + }, + "MapField3": false, + }, + NilField: nil, + InterfacePointerField: &s, + StructArrayField: []map[string]interface{}{ + { + "Name": "Allen", + "Age": int64(20), + }, + { + "Name": "Jack", + "Age": int64(23), + }, + }, + } + actual := OuterStruct{} + if err := toml.Unmarshal(doc, &actual); err == nil { + if !reflect.DeepEqual(actual, expected) { + t.Errorf("Bad unmarshal: expected %v, got %v", expected, actual) + } + } else { + t.Fatal(err) + } +} + +func TestUnmarshalToNonNilInterface(t *testing.T) { + doc := []byte(` +PrimitiveField = "Allen" +ArrayField = [1,2,3] + +[StructField] +InnerField = "After1" + +[PointerField] +InnerField = "After2" + +[InterfacePointerField] +InnerField = "After" + +[MapField] +MapField1 = [4,5,6] +MapField2 = {A = "A"} +MapField3 = false + +[[StructArrayField]] +InnerField = "After3" + +[[StructArrayField]] +InnerField = "After4" +`) + type InnerStruct struct { + InnerField interface{} + } + + type OuterStruct struct { + PrimitiveField interface{} + ArrayField interface{} + StructArrayField interface{} + MapField map[string]interface{} + StructField interface{} + PointerField interface{} + NilField interface{} + InterfacePointerField *interface{} + } + + var s interface{} = InnerStruct{"After"} + expected := OuterStruct{ + PrimitiveField: "Allen", + ArrayField: []int{1, 2, 3}, + StructField: InnerStruct{InnerField: "After1"}, + MapField: map[string]interface{}{ + "MapField1": []interface{}{int64(4), int64(5), int64(6)}, + "MapField2": map[string]interface{}{ + "A": "A", + }, + "MapField3": false, + }, + PointerField: &InnerStruct{InnerField: "After2"}, + NilField: nil, + InterfacePointerField: &s, + StructArrayField: []InnerStruct{ + {InnerField: "After3"}, + {InnerField: "After4"}, + }, + } + actual := OuterStruct{ + PrimitiveField: "aaa", + ArrayField: []int{100, 200, 300, 400}, + StructField: InnerStruct{InnerField: "Before1"}, + MapField: map[string]interface{}{ + "MapField1": []int{4, 5, 6}, + "MapField2": map[string]string{ + "B": "BBB", + }, + "MapField3": true, + }, + PointerField: &InnerStruct{InnerField: "Before2"}, + NilField: nil, + InterfacePointerField: &s, + StructArrayField: []InnerStruct{ + {InnerField: "Before3"}, + {InnerField: "Before4"}, + }, + } + if err := toml.Unmarshal(doc, &actual); err == nil { + if !reflect.DeepEqual(actual, expected) { + t.Errorf("Bad unmarshal: expected %v, got %v", expected, actual) + } + } else { + t.Fatal(err) + } +} + +func TestUnmarshalNil(t *testing.T) { + if err := toml.Unmarshal([]byte(`whatever = "whatever"`), nil); err == nil { + t.Errorf("Expected err from nil marshal") + } + if err := toml.Unmarshal([]byte(`whatever = "whatever"`), (*struct{})(nil)); err == nil { + t.Errorf("Expected err from nil marshal") + } +} + +var sliceTomlDemo = []byte(`str_slice = ["Howdy","Hey There"] +str_slice_ptr= ["Howdy","Hey There"] +int_slice=[1,2] +int_slice_ptr=[1,2] +[[struct_slice]] +String2="1" +[[struct_slice]] +String2="2" +[[struct_slice_ptr]] +String2="1" +[[struct_slice_ptr]] +String2="2" +`) + +type sliceStruct struct { + Slice []string ` toml:"str_slice" ` + SlicePtr *[]string ` toml:"str_slice_ptr" ` + IntSlice []int ` toml:"int_slice" ` + IntSlicePtr *[]int ` toml:"int_slice_ptr" ` + StructSlice []basicMarshalTestSubStruct ` toml:"struct_slice" ` + StructSlicePtr *[]basicMarshalTestSubStruct ` toml:"struct_slice_ptr" ` +} + +type arrayStruct struct { + Slice [4]string ` toml:"str_slice" ` + SlicePtr *[4]string ` toml:"str_slice_ptr" ` + IntSlice [4]int ` toml:"int_slice" ` + IntSlicePtr *[4]int ` toml:"int_slice_ptr" ` + StructSlice [4]basicMarshalTestSubStruct ` toml:"struct_slice" ` + StructSlicePtr *[4]basicMarshalTestSubStruct ` toml:"struct_slice_ptr" ` +} + +type arrayTooSmallStruct struct { + Slice [1]string ` toml:"str_slice" ` + StructSlice [1]basicMarshalTestSubStruct ` toml:"struct_slice" ` +} + +func TestUnmarshalSlice(t *testing.T) { + var actual sliceStruct + err := toml.Unmarshal(sliceTomlDemo, &actual) + if err != nil { + t.Error("shound not err", err) + } + expected := sliceStruct{ + Slice: []string{"Howdy", "Hey There"}, + SlicePtr: &[]string{"Howdy", "Hey There"}, + IntSlice: []int{1, 2}, + IntSlicePtr: &[]int{1, 2}, + StructSlice: []basicMarshalTestSubStruct{{"1"}, {"2"}}, + StructSlicePtr: &[]basicMarshalTestSubStruct{{"1"}, {"2"}}, + } + if !reflect.DeepEqual(actual, expected) { + t.Errorf("Bad unmarshal: expected %v, got %v", expected, actual) + } + +} + +func TestUnmarshalSliceFail(t *testing.T) { + var actual sliceStruct + err := toml.Unmarshal([]byte(`str_slice = [1, 2]`), &actual) + if err.Error() != "(0, 0): Can't convert 1(int64) to string" { + t.Error("expect err:(0, 0): Can't convert 1(int64) to string but got ", err) + } +} + +func TestUnmarshalSliceFail2(t *testing.T) { + doc := `str_slice=[1,2]` + var actual sliceStruct + err := toml.Unmarshal([]byte(doc), &actual) + if err.Error() != "(1, 1): Can't convert 1(int64) to string" { + t.Error("expect err:(1, 1): Can't convert 1(int64) to string but got ", err) + } + +} + +func TestUnmarshalMixedTypeArray(t *testing.T) { + type TestStruct struct { + ArrayField []interface{} + } + + doc := []byte(`ArrayField = [3.14,100,true,"hello world",{Field = "inner1"},[{Field = "inner2"},{Field = "inner3"}]] +`) + + actual := TestStruct{} + expected := TestStruct{ + ArrayField: []interface{}{ + 3.14, + int64(100), + true, + "hello world", + map[string]interface{}{ + "Field": "inner1", + }, + []map[string]interface{}{ + {"Field": "inner2"}, + {"Field": "inner3"}, + }, + }, + } + + if err := toml.Unmarshal(doc, &actual); err == nil { + if !reflect.DeepEqual(actual, expected) { + t.Errorf("Bad unmarshal: expected %#v, got %#v", expected, actual) + } + } else { + t.Fatal(err) + } +} + +func TestUnmarshalArray(t *testing.T) { + var err error + + var actual1 arrayStruct + err = toml.Unmarshal(sliceTomlDemo, &actual1) + if err != nil { + t.Error("shound not err", err) + } + + expected := arrayStruct{ + Slice: [4]string{"Howdy", "Hey There"}, + SlicePtr: &[4]string{"Howdy", "Hey There"}, + IntSlice: [4]int{1, 2}, + IntSlicePtr: &[4]int{1, 2}, + StructSlice: [4]basicMarshalTestSubStruct{{"1"}, {"2"}}, + StructSlicePtr: &[4]basicMarshalTestSubStruct{{"1"}, {"2"}}, + } + if !reflect.DeepEqual(actual1, expected) { + t.Errorf("Bad unmarshal: expected %v, got %v", expected, actual1) + } +} + +func TestUnmarshalArrayFail(t *testing.T) { + var actual arrayTooSmallStruct + err := toml.Unmarshal([]byte(`str_slice = ["Howdy", "Hey There"]`), &actual) + if err.Error() != "(0, 0): unmarshal: TOML array length (2) exceeds destination array length (1)" { + t.Error("expect err:(0, 0): unmarshal: TOML array length (2) exceeds destination array length (1) but got ", err) + } +} + +func TestUnmarshalArrayFail2(t *testing.T) { + doc := `str_slice=["Howdy","Hey There"]` + + var actual arrayTooSmallStruct + err := toml.Unmarshal([]byte(doc), &actual) + if err.Error() != "(1, 1): unmarshal: TOML array length (2) exceeds destination array length (1)" { + t.Error("expect err:(1, 1): unmarshal: TOML array length (2) exceeds destination array length (1) but got ", err) + } +} + +func TestUnmarshalArrayFail3(t *testing.T) { + doc := `[[struct_slice]] +String2="1" +[[struct_slice]] +String2="2"` + + var actual arrayTooSmallStruct + err := toml.Unmarshal([]byte(doc), &actual) + if err.Error() != "(3, 1): unmarshal: TOML array length (2) exceeds destination array length (1)" { + t.Error("expect err:(3, 1): unmarshal: TOML array length (2) exceeds destination array length (1) but got ", err) + } +} + +func TestDecoderStrict(t *testing.T) { + t.Skip() + // input := ` + //[decoded] + // key = "" + // + //[undecoded] + // key = "" + // + // [undecoded.inner] + // key = "" + // + // [[undecoded.array]] + // key = "" + // + // [[undecoded.array]] + // key = "" + // + //` + // var doc struct { + // Decoded struct { + // Key string + // } + // } + // + // expected := `undecoded keys: ["undecoded.array.0.key" "undecoded.array.1.key" "undecoded.inner.key" "undecoded.key"]` + // + // err := NewDecoder(bytes.NewReader([]byte(input))).Strict(true).Decode(&doc) + // if err == nil { + // t.Error("expected error, got none") + // } else if err.Error() != expected { + // t.Errorf("expect err: %s, got: %s", expected, err.Error()) + // } + // + // if err := NewDecoder(bytes.NewReader([]byte(input))).Decode(&doc); err != nil { + // t.Errorf("unexpected err: %s", err) + // } + // + // var m map[string]interface{} + // if err := NewDecoder(bytes.NewReader([]byte(input))).Decode(&m); err != nil { + // t.Errorf("unexpected err: %s", err) + // } +} + +func TestDecoderStrictValid(t *testing.T) { + t.Skip() + // input := ` + //[decoded] + // key = "" + //` + // var doc struct { + // Decoded struct { + // Key string + // } + // } + // + // err := NewDecoder(bytes.NewReader([]byte(input))).Strict(true).Decode(&doc) + // if err != nil { + // t.Fatal("unexpected error:", err) + // } +} + +type docUnmarshalTOML struct { + Decoded struct { + Key string + } +} + +func (d *docUnmarshalTOML) UnmarshalTOML(i interface{}) error { + if iMap, ok := i.(map[string]interface{}); !ok { + return fmt.Errorf("type assertion error: wants %T, have %T", map[string]interface{}{}, i) + } else if key, ok := iMap["key"]; !ok { + return fmt.Errorf("key '%s' not in map", "key") + } else if keyString, ok := key.(string); !ok { + return fmt.Errorf("type assertion error: wants %T, have %T", "", key) + } else { + d.Decoded.Key = keyString + } + return nil +} + +func TestDecoderStrictCustomUnmarshal(t *testing.T) { + t.Skip() + //input := `key = "ok"` + //var doc docUnmarshalTOML + //err := NewDecoder(bytes.NewReader([]byte(input))).Strict(true).Decode(&doc) + //if err != nil { + // t.Fatal("unexpected error:", err) + //} + //if doc.Decoded.Key != "ok" { + // t.Errorf("Bad unmarshal: expected ok, got %v", doc.Decoded.Key) + //} +} + +type parent struct { + Doc docUnmarshalTOML + DocPointer *docUnmarshalTOML +} + +func TestCustomUnmarshal(t *testing.T) { + input := ` +[Doc] + key = "ok1" +[DocPointer] + key = "ok2" +` + + var d parent + if err := toml.Unmarshal([]byte(input), &d); err != nil { + t.Fatalf("unexpected err: %s", err.Error()) + } + if d.Doc.Decoded.Key != "ok1" { + t.Errorf("Bad unmarshal: expected ok, got %v", d.Doc.Decoded.Key) + } + if d.DocPointer.Decoded.Key != "ok2" { + t.Errorf("Bad unmarshal: expected ok, got %v", d.DocPointer.Decoded.Key) + } +} + +func TestCustomUnmarshalError(t *testing.T) { + input := ` +[Doc] + key = 1 +[DocPointer] + key = "ok2" +` + + expected := "(2, 1): unmarshal toml: type assertion error: wants string, have int64" + + var d parent + err := toml.Unmarshal([]byte(input), &d) + if err == nil { + t.Error("expected error, got none") + } else if err.Error() != expected { + t.Errorf("expect err: %s, got: %s", expected, err.Error()) + } +} + +type intWrapper struct { + Value int +} + +func (w *intWrapper) UnmarshalText(text []byte) error { + var err error + if w.Value, err = strconv.Atoi(string(text)); err == nil { + return nil + } + if b, err := strconv.ParseBool(string(text)); err == nil { + if b { + w.Value = 1 + } + return nil + } + if f, err := strconv.ParseFloat(string(text), 32); err == nil { + w.Value = int(f) + return nil + } + return fmt.Errorf("unsupported: %s", text) +} + +func TestTextUnmarshal(t *testing.T) { + var doc struct { + UnixTime intWrapper + Version *intWrapper + + Bool intWrapper + Int intWrapper + Float intWrapper + } + + input := ` +UnixTime = "12" +Version = "42" +Bool = true +Int = 21 +Float = 2.0 +` + + if err := toml.Unmarshal([]byte(input), &doc); err != nil { + t.Fatalf("unexpected err: %s", err.Error()) + } + if doc.UnixTime.Value != 12 { + t.Fatalf("expected UnixTime: 12 got: %d", doc.UnixTime.Value) + } + if doc.Version.Value != 42 { + t.Fatalf("expected Version: 42 got: %d", doc.Version.Value) + } + if doc.Bool.Value != 1 { + t.Fatalf("expected Bool: 1 got: %d", doc.Bool.Value) + } + if doc.Int.Value != 21 { + t.Fatalf("expected Int: 21 got: %d", doc.Int.Value) + } + if doc.Float.Value != 2 { + t.Fatalf("expected Float: 2 got: %d", doc.Float.Value) + } +} + +func TestTextUnmarshalError(t *testing.T) { + var doc struct { + Failer intWrapper + } + + input := `Failer = "hello"` + if err := toml.Unmarshal([]byte(input), &doc); err == nil { + t.Fatalf("expected err, got none") + } +} + +// issue406 +func TestPreserveNotEmptyField(t *testing.T) { + doc := []byte(`Field1 = "ccc"`) + type Inner struct { + InnerField1 string + InnerField2 int + } + type TestStruct struct { + Field1 string + Field2 int + Field3 Inner + } + + actual := TestStruct{ + "aaa", + 100, + Inner{ + "bbb", + 200, + }, + } + + expected := TestStruct{ + "ccc", + 100, + Inner{ + "bbb", + 200, + }, + } + + err := toml.Unmarshal(doc, &actual) + if err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(actual, expected) { + t.Errorf("Bad unmarshal: expected %+v, got %+v", expected, actual) + } +} + +// github issue 432 +func TestUnmarshalEmptyInterface(t *testing.T) { + doc := []byte(`User = "pelletier"`) + + var v interface{} + + err := toml.Unmarshal(doc, &v) + if err != nil { + t.Fatal(err) + } + + x, ok := v.(map[string]interface{}) + if !ok { + t.Fatal(err) + } + + if x["User"] != "pelletier" { + t.Fatalf("expected User=pelletier, but got %v", x) + } +} + +func TestUnmarshalEmptyInterfaceDeep(t *testing.T) { + doc := []byte(` +User = "pelletier" +Age = 99 + +[foo] +bar = 42 +`) + + var v interface{} + + err := toml.Unmarshal(doc, &v) + if err != nil { + t.Fatal(err) + } + + x, ok := v.(map[string]interface{}) + if !ok { + t.Fatal(err) + } + + expected := map[string]interface{}{ + "User": "pelletier", + "Age": 99, + "foo": map[string]interface{}{ + "bar": 42, + }, + } + + reflect.DeepEqual(x, expected) +} + +type Config struct { + Key string `toml:"key"` + Obj Custom `toml:"obj"` +} + +type Custom struct { + v string +} + +func (c *Custom) UnmarshalTOML(v interface{}) error { + c.v = "called" + return nil +} + +func TestGithubIssue431(t *testing.T) { + doc := `key = "value"` + var c Config + if err := toml.Unmarshal([]byte(doc), &c); err != nil { + t.Fatalf("unexpected error: %s", err) + } + + if c.Key != "value" { + t.Errorf("expected c.Key='value', not '%s'", c.Key) + } + + if c.Obj.v == "called" { + t.Errorf("UnmarshalTOML should not have been called") + } +} + +type durationString struct { + time.Duration +} + +func (d *durationString) UnmarshalTOML(v interface{}) error { + d.Duration = 10 * time.Second + return nil +} + +type config437Error struct { +} + +func (e *config437Error) UnmarshalTOML(v interface{}) error { + return errors.New("expected") +} + +type config437 struct { + HTTP struct { + PingTimeout durationString `toml:"PingTimeout"` + ErrorField config437Error + } `toml:"HTTP"` +} + +func TestGithubIssue437(t *testing.T) { + src := ` +[HTTP] +PingTimeout = "32m" +` + cfg := &config437{} + cfg.HTTP.PingTimeout = durationString{time.Second} + + err := toml.Unmarshal([]byte(src), cfg) + if err != nil { + t.Fatalf("unexpected errors %s", err) + } + expected := durationString{10 * time.Second} + if cfg.HTTP.PingTimeout != expected { + t.Fatalf("expected '%s', got '%s'", expected, cfg.HTTP.PingTimeout) + } +} + +func TestLeafUnmarshalerError(t *testing.T) { + src := ` +[HTTP] +ErrorField = "foo" +` + cfg := &config437{} + + err := toml.Unmarshal([]byte(src), cfg) + if err == nil { + t.Fatalf("error was expected") + } +} From e2a07a3b92ef81fb8e19596de510fe7a5739c5bc Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Wed, 10 Feb 2021 11:43:57 -0500 Subject: [PATCH 041/228] Handle interface field dereference --- .../imported_tests/unmarshal_imported_test.go | 95 +------------------ internal/reflectbuild/reflectbuild.go | 8 ++ parser.go | 6 +- 3 files changed, 17 insertions(+), 92 deletions(-) diff --git a/internal/imported_tests/unmarshal_imported_test.go b/internal/imported_tests/unmarshal_imported_test.go index 80ec4a8e..d174c362 100644 --- a/internal/imported_tests/unmarshal_imported_test.go +++ b/internal/imported_tests/unmarshal_imported_test.go @@ -17,6 +17,8 @@ import ( "time" "github.com/pelletier/go-toml/v2" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) type basicMarshalTestStruct struct { @@ -57,34 +59,6 @@ Zstring = "Hello" String2 = "Two" `) -var basicTestTomlCustomIndentation = []byte(`String3 = "One" -Ystrlist = ["Howdy", "Hey There"] -Zstring = "Hello" - -[[Wsublist]] - String2 = "Three" - -[[Wsublist]] - String2 = "Four" - -[Xsubdoc] - String2 = "Two" -`) - -var basicTestTomlOrdered = []byte(`Zstring = "Hello" -Ystrlist = ["Howdy", "Hey There"] -String3 = "One" - -[Xsubdoc] - String2 = "Two" - -[[Wsublist]] - String2 = "Three" - -[[Wsublist]] - String2 = "Four" -`) - var marshalTestToml = []byte(`title = "TOML Marshal Testing" [basic] @@ -126,66 +100,6 @@ var marshalTestToml = []byte(`title = "TOML Marshal Testing" name = "Second" `) -var marshalOrderPreserveToml = []byte(`title = "TOML Marshal Testing" - -[basic_lists] - floats = [12.3, 45.6, 78.9] - bools = [true, false, true] - dates = [1979-05-27T07:32:00Z, 1980-05-27T07:32:00Z] - ints = [8001, 8001, 8002] - uints = [5002, 5003] - strings = ["One", "Two", "Three"] - -[[subdocptrs]] - name = "Second" - -[basic_map] - one = "one" - two = "two" - -[subdoc] - - [subdoc.second] - name = "Second" - - [subdoc.first] - name = "First" - -[basic] - uint = 5001 - bool = true - float = 123.4 - float64 = 123.456782132399 - int = 5000 - string = "Bite me" - date = 1979-05-27T07:32:00Z - -[[subdoclist]] - name = "List.First" - -[[subdoclist]] - name = "List.Second" -`) - -var mashalOrderPreserveMapToml = []byte(`title = "TOML Marshal Testing" - -[basic_map] - one = "one" - two = "two" - -[long_map] - a7 = "1" - b3 = "2" - c8 = "3" - d4 = "4" - e6 = "5" - f5 = "6" - g10 = "7" - h1 = "8" - i2 = "9" - j9 = "10" -`) - type Conf struct { Name string Age int @@ -210,6 +124,7 @@ func TestInterface(t *testing.T) { var config Conf config.Inter = &NestedStruct{} err := toml.Unmarshal(doc, &config) + require.NoError(t, err) expected := Conf{ Name: "rui", Age: 18, @@ -219,9 +134,7 @@ func TestInterface(t *testing.T) { Age: 100, }, } - if err != nil || !reflect.DeepEqual(config, expected) { - t.Errorf("Bad unmarshal: expected %v, got %v", expected, config) - } + assert.Equal(t, expected, config) } func TestBasicUnmarshal(t *testing.T) { diff --git a/internal/reflectbuild/reflectbuild.go b/internal/reflectbuild/reflectbuild.go index 8225d231..18898b78 100644 --- a/internal/reflectbuild/reflectbuild.go +++ b/internal/reflectbuild/reflectbuild.go @@ -43,6 +43,9 @@ func (b *Builder) top() reflect.Value { func (b *Builder) push(v reflect.Value) { b.stack = append(b.stack, v) + // TODO: remove me. just here to make sure the method is included in the + // binary for debug + b.Dump() } func (b *Builder) pop() { @@ -73,10 +76,15 @@ func (b *Builder) replace(v reflect.Value) { } // DigField pushes the cursor into a field of the current struct. +// Dereferences all pointers found along the way. // Errors if the current value is not a struct, or the field does not exist. func (b *Builder) DigField(s string) error { t := b.top() + for t.Kind() == reflect.Interface || t.Kind() == reflect.Ptr { + t = t.Elem() + } + err := checkKind(t.Type(), reflect.Struct) if err != nil { return err diff --git a/parser.go b/parser.go index f44facb0..2b04cd7b 100644 --- a/parser.go +++ b/parser.go @@ -615,7 +615,11 @@ func (p parser) parseIntOrFloatOrDateTime(b []byte) ([]byte, error) { if len(b) < 3 { return p.parseIntOrFloat(b) } - for idx, c := range b[:5] { + s := 5 + if len(b) < s { + s = len(b) + } + for idx, c := range b[:s] { if c >= '0' && c <= '9' { continue } From 6e79ce63c2f6af97c8c47b3452a4342fc0b7cc29 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Wed, 10 Feb 2021 18:34:54 -0500 Subject: [PATCH 042/228] Reflect write to embeded structs --- internal/reflectbuild/reflectbuild.go | 99 +++++++++++++++++++++- internal/reflectbuild/reflectbuild_test.go | 20 ++--- unmarshal.go | 2 +- 3 files changed, 106 insertions(+), 15 deletions(-) diff --git a/internal/reflectbuild/reflectbuild.go b/internal/reflectbuild/reflectbuild.go index 18898b78..d79175a3 100644 --- a/internal/reflectbuild/reflectbuild.go +++ b/internal/reflectbuild/reflectbuild.go @@ -8,6 +8,13 @@ import ( "strings" ) +// fieldGetters are functions that given a struct return a specific field +// (likely captured in their scope) +type fieldGetter func(s reflect.Value) reflect.Value + +// collection of fieldGetters for a given struct type +type structFieldGetters map[string]fieldGetter + // Builder wraps a value and provides method to modify its structure. // It is a stateful object that keeps a cursor of what part of the object is // being modified. @@ -17,11 +24,89 @@ type Builder struct { // Root is always a pointer to a non-nil value. // Cursor is the top of the stack. stack []reflect.Value + // Struct field tag to use to retrieve name. + nameTag string + // Cache of functions to access specific fields. + fieldGettersCache map[reflect.Type]structFieldGetters +} + +func copyAndAppend(s []int, i int) []int { + ns := make([]int, len(s)+1) + copy(ns, s) + ns[len(ns)-1] = i + return ns +} + +func (b *Builder) getOrGenerateFieldGettersRecursive(m structFieldGetters, idx []int, s reflect.Type) { + for i := 0; i < s.NumField(); i++ { + f := s.Field(i) + if f.PkgPath != "" { + // only consider exported fields + continue + } + // TODO: handle embedded structs + if f.Anonymous { + b.getOrGenerateFieldGettersRecursive(m, copyAndAppend(idx, i), f.Type) + } else { + fieldName, ok := f.Tag.Lookup(b.nameTag) + if !ok { + fieldName = f.Name + } + + if len(idx) == 0 { + m[fieldName] = makeFieldGetterByIndex(i) + } else { + m[fieldName] = makeFieldGetterByIndexes(copyAndAppend(idx, i)) + } + } + } + + if b.fieldGettersCache == nil { + b.fieldGettersCache = make(map[reflect.Type]structFieldGetters, 1) + } + + b.fieldGettersCache[s] = m +} + +func (b *Builder) getOrGenerateFieldGetters(s reflect.Type) structFieldGetters { + if s.Kind() != reflect.Struct { + panic("generateFieldGetters can only be called on a struct") + } + m, ok := b.fieldGettersCache[s] + if ok { + return m + } + + m = make(structFieldGetters, s.NumField()) + b.getOrGenerateFieldGettersRecursive(m, nil, s) + b.fieldGettersCache[s] = m + return m +} + +func makeFieldGetterByIndex(idx int) fieldGetter { + return func(s reflect.Value) reflect.Value { + return s.Field(idx) + } +} + +func makeFieldGetterByIndexes(idx []int) fieldGetter { + return func(s reflect.Value) reflect.Value { + return s.FieldByIndex(idx) + } +} + +func (b *Builder) fieldGetter(t reflect.Type, s string) (fieldGetter, error) { + m := b.getOrGenerateFieldGetters(t) + g, ok := m[s] + if !ok { + return nil, fmt.Errorf("field '%s' not accessible on '%s'", s, t) + } + return g, nil } // NewBuilder creates a Builder to construct v. // If v is nil or not a pointer, an error will be returned. -func NewBuilder(v interface{}) (Builder, error) { +func NewBuilder(tag string, v interface{}) (Builder, error) { if v == nil { return Builder{}, fmt.Errorf("cannot build a nil value") } @@ -32,8 +117,9 @@ func NewBuilder(v interface{}) (Builder, error) { } return Builder{ - root: rv.Elem(), - stack: []reflect.Value{rv.Elem()}, + root: rv.Elem(), + stack: []reflect.Value{rv.Elem()}, + nameTag: tag, }, nil } @@ -90,7 +176,12 @@ func (b *Builder) DigField(s string) error { return err } - f := t.FieldByName(s) + g, err := b.fieldGetter(t.Type(), s) + if err != nil { + return err + } + + f := g(t) if !f.IsValid() { return FieldNotFoundError{FieldName: s, Struct: t} } diff --git a/internal/reflectbuild/reflectbuild_test.go b/internal/reflectbuild/reflectbuild_test.go index 433a3416..9c80dfee 100644 --- a/internal/reflectbuild/reflectbuild_test.go +++ b/internal/reflectbuild/reflectbuild_test.go @@ -11,17 +11,17 @@ import ( func TestNewBuilderSuccess(t *testing.T) { x := struct{}{} - _, err := reflectbuild.NewBuilder(&x) + _, err := reflectbuild.NewBuilder("", &x) assert.NoError(t, err) } func TestNewBuilderNil(t *testing.T) { - _, err := reflectbuild.NewBuilder(nil) + _, err := reflectbuild.NewBuilder("", nil) assert.Error(t, err) } func TestNewBuilderNonPtr(t *testing.T) { - _, err := reflectbuild.NewBuilder(struct{}{}) + _, err := reflectbuild.NewBuilder("", struct{}{}) assert.Error(t, err) } @@ -29,7 +29,7 @@ func TestDigField(t *testing.T) { x := struct { Field string }{} - b, err := reflectbuild.NewBuilder(&x) + b, err := reflectbuild.NewBuilder("", &x) require.NoError(t, err) assert.Error(t, b.DigField("oops")) assert.NoError(t, b.DigField("Field")) @@ -41,7 +41,7 @@ func TestBack(t *testing.T) { A string B string }{} - b, err := reflectbuild.NewBuilder(&x) + b, err := reflectbuild.NewBuilder("", &x) require.NoError(t, err) b.Save() assert.NoError(t, b.DigField("A")) @@ -63,7 +63,7 @@ func TestReset(t *testing.T) { A []string B string }{} - b, err := reflectbuild.NewBuilder(&x) + b, err := reflectbuild.NewBuilder("", &x) require.NoError(t, err) require.NoError(t, b.DigField("A")) require.NoError(t, b.SliceNewElem()) @@ -80,7 +80,7 @@ func TestSetString(t *testing.T) { x := struct { Field string }{} - b, err := reflectbuild.NewBuilder(&x) + b, err := reflectbuild.NewBuilder("", &x) require.NoError(t, err) assert.Error(t, b.SetString("oops")) require.NoError(t, b.DigField("Field")) @@ -92,7 +92,7 @@ func TestSliceNewElem(t *testing.T) { x := struct { Field []string }{} - b, err := reflectbuild.NewBuilder(&x) + b, err := reflectbuild.NewBuilder("", &x) require.NoError(t, err) require.NoError(t, b.DigField("Field")) b.Save() @@ -112,7 +112,7 @@ func TestSliceNewElemNested(t *testing.T) { x := struct { Field [][]string }{} - b, err := reflectbuild.NewBuilder(&x) + b, err := reflectbuild.NewBuilder("", &x) require.NoError(t, err) require.NoError(t, b.DigField("Field")) @@ -159,7 +159,7 @@ func TestCursor(t *testing.T) { x := struct { Field string }{} - b, err := reflectbuild.NewBuilder(&x) + b, err := reflectbuild.NewBuilder("", &x) require.NoError(t, err) assert.Equal(t, b.Cursor().Kind(), reflect.Struct) require.NoError(t, b.DigField("Field")) diff --git a/unmarshal.go b/unmarshal.go index 6462999a..da2cf63b 100644 --- a/unmarshal.go +++ b/unmarshal.go @@ -8,7 +8,7 @@ import ( func Unmarshal(data []byte, v interface{}) error { u := &unmarshaler{} - u.builder, u.err = reflectbuild.NewBuilder(v) + u.builder, u.err = reflectbuild.NewBuilder("toml", v) if u.err == nil { parseErr := parser{builder: u}.parse(data) if parseErr != nil { From 2341b4df006ffa99611218639f03077bbf8d1b84 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Wed, 10 Feb 2021 18:42:43 -0500 Subject: [PATCH 043/228] Comment out json-based comparison --- .../imported_tests/unmarshal_imported_test.go | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/internal/imported_tests/unmarshal_imported_test.go b/internal/imported_tests/unmarshal_imported_test.go index d174c362..1a9e5d5c 100644 --- a/internal/imported_tests/unmarshal_imported_test.go +++ b/internal/imported_tests/unmarshal_imported_test.go @@ -7,7 +7,6 @@ package imported_tests // marked as skipped until we figure out if that's something we want in v2. import ( - "encoding/json" "errors" "fmt" "reflect" @@ -295,14 +294,16 @@ func TestDocUnmarshal(t *testing.T) { result := testDoc{} err := toml.Unmarshal(marshalTestToml, &result) expected := docData - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(result, expected) { - resStr, _ := json.MarshalIndent(result, "", " ") - expStr, _ := json.MarshalIndent(expected, "", " ") - t.Errorf("Bad unmarshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expStr, resStr) - } + require.NoError(t, err) + assert.Equal(t, expected, result) + //if err != nil { + // t.Fatal(err) + //} + //if !reflect.DeepEqual(result, expected) { + // resStr, _ := json.MarshalIndent(result, "", " ") + // expStr, _ := json.MarshalIndent(expected, "", " ") + // t.Errorf("Bad unmarshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expStr, resStr) + //} } type tomlTypeCheckTest struct { From 9ac08febd2aee11fd607d0eb1c59bc7cddee85c5 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Wed, 10 Feb 2021 20:58:22 -0500 Subject: [PATCH 044/228] DateTime/LocalDate/LocalTime implementation --- internal/reflectbuild/reflectbuild.go | 8 +- parser.go | 266 +++++++++++++++++++++++++- unmarshal.go | 69 +++++++ 3 files changed, 338 insertions(+), 5 deletions(-) diff --git a/internal/reflectbuild/reflectbuild.go b/internal/reflectbuild/reflectbuild.go index d79175a3..7b8576e5 100644 --- a/internal/reflectbuild/reflectbuild.go +++ b/internal/reflectbuild/reflectbuild.go @@ -178,7 +178,7 @@ func (b *Builder) DigField(s string) error { g, err := b.fieldGetter(t.Type(), s) if err != nil { - return err + return FieldNotFoundError{FieldName: s, Struct: t} } f := g(t) @@ -333,6 +333,12 @@ func (b *Builder) SetInt(n int64) error { return nil } +func (b *Builder) Set(v reflect.Value) error { + t := b.top() + t.Set(v) + return nil +} + func checkKindInt(rt reflect.Type) error { switch rt.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: diff --git a/parser.go b/parser.go index 2b04cd7b..add080f9 100644 --- a/parser.go +++ b/parser.go @@ -8,6 +8,7 @@ import ( "math" "strconv" "strings" + "time" ) type builder interface { @@ -27,6 +28,10 @@ type builder interface { BoolValue(b bool) FloatValue(n float64) IntValue(n int64) + LocalDateValue(date LocalDate) + LocalDateTimeValue(dt LocalDateTime) + DateTimeValue(dt time.Time) + LocalTimeValue(localTime LocalTime) } type parser struct { @@ -624,17 +629,270 @@ func (p parser) parseIntOrFloatOrDateTime(b []byte) ([]byte, error) { continue } if idx == 2 && c == ':' { - return parseDateTime(b) + return p.parseDateTime(b) } if idx == 4 && c == '-' { - return parseDateTime(b) + return p.parseDateTime(b) } } return p.parseIntOrFloat(b) } -func parseDateTime(b []byte) ([]byte, error) { - panic("implement me") +func digitsToInt(b []byte) int { + x := 0 + for _, d := range b { + x *= 10 + x += int(d - '0') + } + return x +} + +func (p parser) parseDateTime(b []byte) ([]byte, error) { + // we know the first 2 ar digits. + if b[2] == ':' { + return p.parseTime(b) + } + // This state accepts an offset date-time, a local date-time, or a local date. + // + // v--- cursor + // 1979-05-27T07:32:00Z + // 1979-05-27T00:32:00-07:00 + // 1979-05-27T00:32:00.999999-07:00 + // 1979-05-27 07:32:00Z + // 1979-05-27 00:32:00-07:00 + // 1979-05-27 00:32:00.999999-07:00 + // 1979-05-27T07:32:00 + // 1979-05-27T00:32:00.999999 + // 1979-05-27 07:32:00 + // 1979-05-27 00:32:00.999999 + // 1979-05-27 + + // date + + idx := 4 + + localDate := LocalDate{ + Year: digitsToInt(b[:idx]), + } + + for i := 0; i < 2; i++ { + // month + idx++ + if !isDigit(b[idx]) { + return nil, fmt.Errorf("invalid month digit in date: %c", b[idx]) + } + localDate.Month *= 10 + localDate.Month += time.Month(b[idx] - '0') + } + + idx++ + if b[idx] != '-' { + return nil, fmt.Errorf("expected - to separate month of a date, not %c", b[idx]) + } + + for i := 0; i < 2; i++ { + // day + idx++ + if !isDigit(b[idx]) { + return nil, fmt.Errorf("invalid day digit in date: %c", b[idx]) + } + localDate.Day *= 10 + localDate.Day += int(b[idx] - '0') + } + + idx++ + + if idx >= len(b) { + p.builder.LocalDateValue(localDate) + return nil, nil + } else if b[idx] != ' ' && b[idx] != 'T' { + p.builder.LocalDateValue(localDate) + return b[idx:], nil + } + + // check if there is a chance there is anything useful after + if b[idx] == ' ' && (((idx + 2) >= len(b)) || !isDigit(b[idx+1]) || !isDigit(b[idx+2])) { + p.builder.LocalDateValue(localDate) + return b[idx:], nil + } + + //idx++ // skip the T or ' ' + + // time + localTime := LocalTime{} + + for i := 0; i < 2; i++ { + idx++ + if !isDigit(b[idx]) { + return nil, fmt.Errorf("invalid hour digit in time: %c", b[idx]) + } + localTime.Hour *= 10 + localTime.Hour += int(b[idx] - '0') + } + + idx++ + if b[idx] != ':' { + return nil, fmt.Errorf("time hour/minute separator should be :, not %c", b[idx]) + } + + for i := 0; i < 2; i++ { + idx++ + if !isDigit(b[idx]) { + return nil, fmt.Errorf("invalid minute digit in time: %c", b[idx]) + } + localTime.Minute *= 10 + localTime.Minute += int(b[idx] - '0') + } + + idx++ + if b[idx] != ':' { + return nil, fmt.Errorf("time minute/second separator should be :, not %c", b[idx]) + } + + for i := 0; i < 2; i++ { + idx++ + if !isDigit(b[idx]) { + return nil, fmt.Errorf("invalid second digit in time: %c", b[idx]) + } + localTime.Second *= 10 + localTime.Second += int(b[idx] - '0') + } + + idx++ + if idx < len(b) && b[idx] == '.' { + idx++ + idx++ + if !isDigit(b[idx]) { + return nil, fmt.Errorf("expected at least one digit in time's fraction, not %c", b[idx]) + } + + for { + localTime.Nanosecond *= 10 + localTime.Nanosecond += int(b[idx] - '0') + idx++ + if !isDigit(b[idx]) { + break + } + } + } + + if idx >= len(b) || (b[idx] != 'Z' && b[idx] != '+' && b[idx] != '-') { + dt := LocalDateTime{ + Date: localDate, + Time: localTime, + } + p.builder.LocalDateTimeValue(dt) + return b[idx:], nil + } + + loc := time.UTC + + if b[idx] == 'Z' { + idx++ + } else { + start := idx + sign := 1 + if b[idx] == '-' { + sign = -1 + } + + hours := 0 + for i := 0; i < 2; i++ { + idx++ + if !isDigit(b[idx]) { + return nil, fmt.Errorf("invalid hour digit in time offset: %c", b[idx]) + } + hours *= 10 + hours += int(b[idx] - '0') + } + offset := hours * 60 * 60 + + idx++ + if b[idx] != ':' { + return nil, fmt.Errorf("time offset hour/minute separator should be :, not %c", b[idx]) + } + + minutes := 0 + for i := 0; i < 2; i++ { + idx++ + if !isDigit(b[idx]) { + return nil, fmt.Errorf("invalid minute digit in time offset: %c", b[idx]) + } + minutes *= 10 + minutes += int(b[idx] - '0') + } + offset += minutes * 60 + offset *= sign + idx++ + loc = time.FixedZone(string(b[start:idx]), offset) + } + dt := time.Date(localDate.Year, localDate.Month, localDate.Day, localTime.Hour, localTime.Minute, localTime.Second, localTime.Nanosecond, loc) + p.builder.DateTimeValue(dt) + return b[idx:], nil +} + +func (p parser) parseTime(b []byte) ([]byte, error) { + localTime := LocalTime{} + + idx := 0 + + for i := 0; i < 2; i++ { + idx++ + if !isDigit(b[idx]) { + return nil, fmt.Errorf("invalid hour digit in time: %c", b[idx]) + } + localTime.Hour *= 10 + localTime.Hour += int(b[idx] - '0') + } + + idx++ + if b[idx] != ':' { + return nil, fmt.Errorf("time hour/minute separator should be :, not %c", b[idx]) + } + + for i := 0; i < 2; i++ { + idx++ + if !isDigit(b[idx]) { + return nil, fmt.Errorf("invalid minute digit in time: %c", b[idx]) + } + localTime.Minute *= 10 + localTime.Minute += int(b[idx] - '0') + } + + idx++ + if b[idx] != ':' { + return nil, fmt.Errorf("time minute/second separator should be :, not %c", b[idx]) + } + + for i := 0; i < 2; i++ { + idx++ + if !isDigit(b[idx]) { + return nil, fmt.Errorf("invalid second digit in time: %c", b[idx]) + } + localTime.Second *= 10 + localTime.Second += int(b[idx] - '0') + } + + idx++ + if idx < len(b) && b[idx] == '.' { + idx++ + idx++ + if !isDigit(b[idx]) { + return nil, fmt.Errorf("expected at least one digit in time's fraction, not %c", b[idx]) + } + + for { + localTime.Nanosecond *= 10 + localTime.Nanosecond += int(b[idx] - '0') + idx++ + if !isDigit(b[idx]) { + break + } + } + } + + p.builder.LocalTimeValue(localTime) + return b[idx:], nil } func (p parser) parseIntOrFloat(b []byte) ([]byte, error) { diff --git a/unmarshal.go b/unmarshal.go index da2cf63b..34a4184f 100644 --- a/unmarshal.go +++ b/unmarshal.go @@ -2,6 +2,7 @@ package toml import ( "reflect" + "time" "github.com/pelletier/go-toml/v2/internal/reflectbuild" ) @@ -169,6 +170,70 @@ func (u *unmarshaler) IntValue(n int64) { } } +func (u *unmarshaler) LocalDateValue(date LocalDate) { + if u.err != nil { + return + } + if u.builder.IsSlice() { + u.builder.Save() + u.err = u.builder.SliceAppend(reflect.ValueOf(date)) + if u.err != nil { + return + } + u.builder.Load() + } else { + u.err = u.builder.Set(reflect.ValueOf(date)) + } +} + +func (u *unmarshaler) LocalDateTimeValue(dt LocalDateTime) { + if u.err != nil { + return + } + if u.builder.IsSlice() { + u.builder.Save() + u.err = u.builder.SliceAppend(reflect.ValueOf(dt)) + if u.err != nil { + return + } + u.builder.Load() + } else { + u.err = u.builder.Set(reflect.ValueOf(dt)) + } +} + +func (u *unmarshaler) DateTimeValue(dt time.Time) { + if u.err != nil { + return + } + if u.builder.IsSlice() { + u.builder.Save() + u.err = u.builder.SliceAppend(reflect.ValueOf(dt)) + if u.err != nil { + return + } + u.builder.Load() + } else { + u.err = u.builder.Set(reflect.ValueOf(dt)) + } +} + +func (u *unmarshaler) LocalTimeValue(localTime LocalTime) { + if u.err != nil { + return + } + if u.builder.IsSlice() { + u.builder.Save() + u.err = u.builder.SliceAppend(reflect.ValueOf(localTime)) + if u.err != nil { + return + } + u.builder.Load() + } else { + u.err = u.builder.Set(reflect.ValueOf(localTime)) + } +} + func (u *unmarshaler) SimpleKey(v []byte) { if u.err != nil { return @@ -184,6 +249,10 @@ func (u *unmarshaler) SimpleKey(v []byte) { } } u.err = u.builder.DigField(string(v)) + if u.err == nil { + return + } + // TODO: figure out what to do with unexported fields } } From 1f41c556e8650df8e5ba22374e6390d9f1900ae7 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Sat, 13 Feb 2021 14:35:39 -0500 Subject: [PATCH 045/228] Handle missing fields in structs --- unmarshal.go | 71 ++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 55 insertions(+), 16 deletions(-) diff --git a/unmarshal.go b/unmarshal.go index 34a4184f..4dc20c1d 100644 --- a/unmarshal.go +++ b/unmarshal.go @@ -39,14 +39,29 @@ type unmarshaler struct { // be directly assigned. This is used to distinguish between assigning or // appending to arrays. assign bool + + // State that indicates the parser is processing a [table] name. + // Used to know whether the whole table should be skipped or just the + // keyval if a field is missing. + parsingTable bool + + skipKeyValCount uint + skipTable bool +} + +func (u *unmarshaler) skipping() bool { + return u.skipTable || u.skipKeyValCount > 0 } func (u *unmarshaler) Assignation() { + if u.skipping() || u.err != nil { + return + } u.assign = true } func (u *unmarshaler) ArrayBegin() { - if u.err != nil { + if u.skipping() || u.err != nil { return } u.builder.Save() @@ -58,14 +73,14 @@ func (u *unmarshaler) ArrayBegin() { } func (u *unmarshaler) ArrayEnd() { - if u.err != nil { + if u.skipping() || u.err != nil { return } u.builder.Load() } func (u *unmarshaler) ArrayTableBegin() { - if u.err != nil { + if u.skipping() || u.err != nil { return } @@ -73,7 +88,7 @@ func (u *unmarshaler) ArrayTableBegin() { } func (u *unmarshaler) ArrayTableEnd() { - if u.err != nil { + if u.skipping() || u.err != nil { return } @@ -99,15 +114,29 @@ func (u *unmarshaler) ArrayTableEnd() { } func (u *unmarshaler) KeyValBegin() { + if u.skipKeyValCount > 0 { + u.skipKeyValCount++ + return + } + if u.skipping() || u.err != nil { + return + } u.builder.Save() } func (u *unmarshaler) KeyValEnd() { + if u.skipKeyValCount > 0 { + u.skipKeyValCount-- + return + } + if u.skipping() || u.err != nil { + return + } u.builder.Load() } func (u *unmarshaler) StringValue(v []byte) { - if u.err != nil { + if u.skipping() || u.err != nil { return } if u.builder.IsSlice() { @@ -123,7 +152,7 @@ func (u *unmarshaler) StringValue(v []byte) { } func (u *unmarshaler) BoolValue(b bool) { - if u.err != nil { + if u.skipping() || u.err != nil { return } if u.builder.IsSlice() { @@ -139,7 +168,7 @@ func (u *unmarshaler) BoolValue(b bool) { } func (u *unmarshaler) FloatValue(n float64) { - if u.err != nil { + if u.skipping() || u.err != nil { return } if u.builder.IsSlice() { @@ -155,7 +184,7 @@ func (u *unmarshaler) FloatValue(n float64) { } func (u *unmarshaler) IntValue(n int64) { - if u.err != nil { + if u.skipping() || u.err != nil { return } if u.builder.IsSlice() { @@ -171,7 +200,7 @@ func (u *unmarshaler) IntValue(n int64) { } func (u *unmarshaler) LocalDateValue(date LocalDate) { - if u.err != nil { + if u.skipping() || u.err != nil { return } if u.builder.IsSlice() { @@ -187,7 +216,7 @@ func (u *unmarshaler) LocalDateValue(date LocalDate) { } func (u *unmarshaler) LocalDateTimeValue(dt LocalDateTime) { - if u.err != nil { + if u.skipping() || u.err != nil { return } if u.builder.IsSlice() { @@ -203,7 +232,7 @@ func (u *unmarshaler) LocalDateTimeValue(dt LocalDateTime) { } func (u *unmarshaler) DateTimeValue(dt time.Time) { - if u.err != nil { + if u.skipping() || u.err != nil { return } if u.builder.IsSlice() { @@ -219,7 +248,7 @@ func (u *unmarshaler) DateTimeValue(dt time.Time) { } func (u *unmarshaler) LocalTimeValue(localTime LocalTime) { - if u.err != nil { + if u.skipping() || u.err != nil { return } if u.builder.IsSlice() { @@ -235,7 +264,7 @@ func (u *unmarshaler) LocalTimeValue(localTime LocalTime) { } func (u *unmarshaler) SimpleKey(v []byte) { - if u.err != nil { + if u.skipping() || u.err != nil { return } @@ -252,21 +281,31 @@ func (u *unmarshaler) SimpleKey(v []byte) { if u.err == nil { return } + if _, ok := u.err.(reflectbuild.FieldNotFoundError); ok { + u.err = nil + if u.parsingTable { + u.skipTable = true + } else { + u.skipKeyValCount = 1 + } + } // TODO: figure out what to do with unexported fields } } func (u *unmarshaler) StandardTableBegin() { - if u.err != nil { + u.skipTable = false + u.parsingTable = true + if u.skipping() || u.err != nil { return } - // tables are only top-level u.builder.Reset() } func (u *unmarshaler) StandardTableEnd() { - if u.err != nil { + u.parsingTable = false + if u.skipping() || u.err != nil { return } } From 46573551f111b976467921307a96d4139fc2b97b Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Thu, 18 Feb 2021 21:24:26 -0500 Subject: [PATCH 046/228] WIP constructing pointers --- .../imported_tests/unmarshal_imported_test.go | 8 ---- internal/reflectbuild/reflectbuild.go | 47 ++++++++++++++++--- internal/reflectbuild/reflectbuild_test.go | 38 +++++++++++++++ unmarshal.go | 19 ++++---- 4 files changed, 89 insertions(+), 23 deletions(-) diff --git a/internal/imported_tests/unmarshal_imported_test.go b/internal/imported_tests/unmarshal_imported_test.go index 1a9e5d5c..5b96dcd6 100644 --- a/internal/imported_tests/unmarshal_imported_test.go +++ b/internal/imported_tests/unmarshal_imported_test.go @@ -296,14 +296,6 @@ func TestDocUnmarshal(t *testing.T) { expected := docData require.NoError(t, err) assert.Equal(t, expected, result) - //if err != nil { - // t.Fatal(err) - //} - //if !reflect.DeepEqual(result, expected) { - // resStr, _ := json.MarshalIndent(result, "", " ") - // expStr, _ := json.MarshalIndent(expected, "", " ") - // t.Errorf("Bad unmarshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expStr, resStr) - //} } type tomlTypeCheckTest struct { diff --git a/internal/reflectbuild/reflectbuild.go b/internal/reflectbuild/reflectbuild.go index 7b8576e5..2203973f 100644 --- a/internal/reflectbuild/reflectbuild.go +++ b/internal/reflectbuild/reflectbuild.go @@ -44,7 +44,6 @@ func (b *Builder) getOrGenerateFieldGettersRecursive(m structFieldGetters, idx [ // only consider exported fields continue } - // TODO: handle embedded structs if f.Anonymous { b.getOrGenerateFieldGettersRecursive(m, copyAndAppend(idx, i), f.Type) } else { @@ -223,6 +222,10 @@ func (b *Builder) IsSlice() bool { return b.top().Kind() == reflect.Slice } +func (b *Builder) IsSliceOrPtr() bool { + return b.top().Kind() == reflect.Slice || (b.top().Kind() == reflect.Ptr && b.top().Type().Elem().Kind() == reflect.Slice) +} + // Last moves the cursor to the last value of the current value. // For a slice or an array, it is the last element they contain, if any. // For anything else, it's a no-op. @@ -269,12 +272,40 @@ func (b *Builder) SliceNewElem() error { return nil } +func assertPtr(v reflect.Value) { + if v.Kind() != reflect.Ptr { + panic(fmt.Sprintf("value '%s' should be a ptr, not '%s'", v, v.Kind())) + } +} + func (b *Builder) SliceAppend(v reflect.Value) error { + assertPtr(v) + t := b.top() + + // pointer to a slice + if t.Kind() == reflect.Ptr { + // if the pointer is nil we need to allocate the slice + if t.IsNil() { + x := reflect.New(t.Type().Elem()) + t.Set(x) + } + // target the slice itself + t = t.Elem() + } + err := checkKind(t.Type(), reflect.Slice) if err != nil { return err } + + if t.Type().Elem().Kind() == reflect.Ptr { + // if it is a slice of pointers, we can just append + } else { + // otherwise we need to reference the value + v = v.Elem() + } + newSlice := reflect.Append(t, v) t.Set(newSlice) b.replace(t.Index(t.Len() - 1)) @@ -286,12 +317,16 @@ func (b *Builder) SliceAppend(v reflect.Value) error { func (b *Builder) SetString(s string) error { t := b.top() - err := checkKind(t.Type(), reflect.String) - if err != nil { - return err - } + if t.Kind() == reflect.Ptr { + t.Set(reflect.ValueOf(&s)) + } else { + err := checkKind(t.Type(), reflect.String) + if err != nil { + return err + } - t.SetString(s) + t.SetString(s) + } return nil } diff --git a/internal/reflectbuild/reflectbuild_test.go b/internal/reflectbuild/reflectbuild_test.go index 9c80dfee..26d526cd 100644 --- a/internal/reflectbuild/reflectbuild_test.go +++ b/internal/reflectbuild/reflectbuild_test.go @@ -165,3 +165,41 @@ func TestCursor(t *testing.T) { require.NoError(t, b.DigField("Field")) assert.Equal(t, b.Cursor().Kind(), reflect.String) } + +func TestStringPtr(t *testing.T) { + x := struct { + Field *string + }{} + b, err := reflectbuild.NewBuilder("", &x) + require.NoError(t, err) + assert.Equal(t, b.Cursor().Kind(), reflect.Struct) + require.NoError(t, b.DigField("Field")) + assert.NoError(t, b.SetString("A")) + assert.Equal(t, "A", *x.Field) +} + +func TestAppendSlicePtr(t *testing.T) { + x := struct { + Field *[]string + }{} + b, err := reflectbuild.NewBuilder("", &x) + require.NoError(t, err) + assert.Equal(t, b.Cursor().Kind(), reflect.Struct) + require.NoError(t, b.DigField("Field")) + v := "A" + assert.NoError(t, b.SliceAppend(reflect.ValueOf(&v))) + assert.Equal(t, []string{"A"}, *x.Field) +} + +func TestAppendPtrSlicePtr(t *testing.T) { + x := struct { + Field *[]*string + }{} + b, err := reflectbuild.NewBuilder("", &x) + require.NoError(t, err) + assert.Equal(t, b.Cursor().Kind(), reflect.Struct) + require.NoError(t, b.DigField("Field")) + v := "A" + assert.NoError(t, b.SliceAppend(reflect.ValueOf(&v))) + assert.Equal(t, "A", *(*x.Field)[0]) +} diff --git a/unmarshal.go b/unmarshal.go index 4dc20c1d..43f8a94d 100644 --- a/unmarshal.go +++ b/unmarshal.go @@ -139,9 +139,10 @@ func (u *unmarshaler) StringValue(v []byte) { if u.skipping() || u.err != nil { return } - if u.builder.IsSlice() { + if u.builder.IsSliceOrPtr() { u.builder.Save() - u.err = u.builder.SliceAppend(reflect.ValueOf(string(v))) + s := string(v) + u.err = u.builder.SliceAppend(reflect.ValueOf(&s)) if u.err != nil { return } @@ -157,7 +158,7 @@ func (u *unmarshaler) BoolValue(b bool) { } if u.builder.IsSlice() { u.builder.Save() - u.err = u.builder.SliceAppend(reflect.ValueOf(b)) + u.err = u.builder.SliceAppend(reflect.ValueOf(&b)) if u.err != nil { return } @@ -173,7 +174,7 @@ func (u *unmarshaler) FloatValue(n float64) { } if u.builder.IsSlice() { u.builder.Save() - u.err = u.builder.SliceAppend(reflect.ValueOf(n)) + u.err = u.builder.SliceAppend(reflect.ValueOf(&n)) if u.err != nil { return } @@ -189,7 +190,7 @@ func (u *unmarshaler) IntValue(n int64) { } if u.builder.IsSlice() { u.builder.Save() - u.err = u.builder.SliceAppend(reflect.ValueOf(n)) + u.err = u.builder.SliceAppend(reflect.ValueOf(&n)) if u.err != nil { return } @@ -205,7 +206,7 @@ func (u *unmarshaler) LocalDateValue(date LocalDate) { } if u.builder.IsSlice() { u.builder.Save() - u.err = u.builder.SliceAppend(reflect.ValueOf(date)) + u.err = u.builder.SliceAppend(reflect.ValueOf(&date)) if u.err != nil { return } @@ -221,7 +222,7 @@ func (u *unmarshaler) LocalDateTimeValue(dt LocalDateTime) { } if u.builder.IsSlice() { u.builder.Save() - u.err = u.builder.SliceAppend(reflect.ValueOf(dt)) + u.err = u.builder.SliceAppend(reflect.ValueOf(&dt)) if u.err != nil { return } @@ -237,7 +238,7 @@ func (u *unmarshaler) DateTimeValue(dt time.Time) { } if u.builder.IsSlice() { u.builder.Save() - u.err = u.builder.SliceAppend(reflect.ValueOf(dt)) + u.err = u.builder.SliceAppend(reflect.ValueOf(&dt)) if u.err != nil { return } @@ -253,7 +254,7 @@ func (u *unmarshaler) LocalTimeValue(localTime LocalTime) { } if u.builder.IsSlice() { u.builder.Save() - u.err = u.builder.SliceAppend(reflect.ValueOf(localTime)) + u.err = u.builder.SliceAppend(reflect.ValueOf(&localTime)) if u.err != nil { return } From 629a2475a9fb3c49ad56a2d1f7b7acf4066665e0 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Thu, 18 Feb 2021 22:39:33 -0500 Subject: [PATCH 047/228] Handle maps --- go.sum | 1 + internal/reflectbuild/reflectbuild.go | 175 +++++++++++++++++--------- 2 files changed, 116 insertions(+), 60 deletions(-) diff --git a/go.sum b/go.sum index acb88a48..26500d5d 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,7 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= diff --git a/internal/reflectbuild/reflectbuild.go b/internal/reflectbuild/reflectbuild.go index 2203973f..d79b8024 100644 --- a/internal/reflectbuild/reflectbuild.go +++ b/internal/reflectbuild/reflectbuild.go @@ -15,6 +15,34 @@ type fieldGetter func(s reflect.Value) reflect.Value // collection of fieldGetters for a given struct type type structFieldGetters map[string]fieldGetter +type target interface { + get() reflect.Value + set(value reflect.Value) +} + +type valueTarget reflect.Value + +func (v valueTarget) get() reflect.Value { + return reflect.Value(v) +} + +func (v valueTarget) set(value reflect.Value) { + reflect.Value(v).Set(value) +} + +type mapTarget struct { + index reflect.Value + m reflect.Value +} + +func (v mapTarget) get() reflect.Value { + return v.m.MapIndex(v.index) +} + +func (v mapTarget) set(value reflect.Value) { + v.m.SetMapIndex(v.index, value) +} + // Builder wraps a value and provides method to modify its structure. // It is a stateful object that keeps a cursor of what part of the object is // being modified. @@ -23,7 +51,7 @@ type Builder struct { root reflect.Value // Root is always a pointer to a non-nil value. // Cursor is the top of the stack. - stack []reflect.Value + stack []target // Struct field tag to use to retrieve name. nameTag string // Cache of functions to access specific fields. @@ -117,17 +145,17 @@ func NewBuilder(tag string, v interface{}) (Builder, error) { return Builder{ root: rv.Elem(), - stack: []reflect.Value{rv.Elem()}, + stack: []target{valueTarget(rv.Elem())}, nameTag: tag, }, nil } -func (b *Builder) top() reflect.Value { +func (b *Builder) top() target { return b.stack[len(b.stack)-1] } -func (b *Builder) push(v reflect.Value) { - b.stack = append(b.stack, v) +func (b *Builder) duplicate() { + b.stack = append(b.stack, b.stack[len(b.stack)-1]) // TODO: remove me. just here to make sure the method is included in the // binary for debug b.Dump() @@ -149,14 +177,14 @@ func (b *Builder) Dump() string { if i > 0 { str.WriteString(" | ") } - fmt.Fprintf(&str, "%s (%s)", x.Type(), x) + fmt.Fprintf(&str, "%s", x) } str.WriteByte(']') return str.String() } -func (b *Builder) replace(v reflect.Value) { +func (b *Builder) replace(v target) { b.stack[len(b.stack)-1] = v } @@ -165,27 +193,47 @@ func (b *Builder) replace(v reflect.Value) { // Errors if the current value is not a struct, or the field does not exist. func (b *Builder) DigField(s string) error { t := b.top() + v := t.get() - for t.Kind() == reflect.Interface || t.Kind() == reflect.Ptr { - t = t.Elem() + for v.Kind() == reflect.Interface || v.Kind() == reflect.Ptr { + if v.IsNil() { + thing := reflect.New(v.Type().Elem()) + v.Set(thing) + } + v = v.Elem() } - err := checkKind(t.Type(), reflect.Struct) - if err != nil { - return err - } + if v.Kind() == reflect.Map { + // if map is nil, allocate it + if v.IsNil() { + v.Set(reflect.MakeMap(v.Type())) + } - g, err := b.fieldGetter(t.Type(), s) - if err != nil { - return FieldNotFoundError{FieldName: s, Struct: t} - } + // TODO: handle error when map is not indexed by strings + key := reflect.ValueOf(s) - f := g(t) - if !f.IsValid() { - return FieldNotFoundError{FieldName: s, Struct: t} - } + b.replace(mapTarget{ + index: key, + m: v, + }) + } else { + err := checkKind(v.Type(), reflect.Struct) + if err != nil { + return err + } - b.replace(f) + g, err := b.fieldGetter(v.Type(), s) + if err != nil { + return FieldNotFoundError{FieldName: s, Struct: v} + } + + f := g(v) + if !f.IsValid() { + return FieldNotFoundError{FieldName: s, Struct: v} + } + + b.replace(valueTarget(f)) + } return nil } @@ -194,13 +242,13 @@ func (b *Builder) DigField(s string) error { // It can be restored using Back(). // Save points are stored as a stack. func (b *Builder) Save() { - b.push(b.top()) + b.duplicate() } // Reset brings the cursor back to the root object. func (b *Builder) Reset() { b.stack = b.stack[:1] - b.stack[0] = b.root + b.stack[0] = valueTarget(b.root) } // Load is the opposite of Save. It discards the current cursor and loads the @@ -215,15 +263,15 @@ func (b *Builder) Load() { // Cursor returns the value pointed at by the cursor. func (b *Builder) Cursor() reflect.Value { - return b.top() + return b.top().get() } func (b *Builder) IsSlice() bool { - return b.top().Kind() == reflect.Slice + return b.top().get().Kind() == reflect.Slice } func (b *Builder) IsSliceOrPtr() bool { - return b.top().Kind() == reflect.Slice || (b.top().Kind() == reflect.Ptr && b.top().Type().Elem().Kind() == reflect.Slice) + return b.top().get().Kind() == reflect.Slice || (b.top().get().Kind() == reflect.Ptr && b.top().get().Type().Elem().Kind() == reflect.Slice) } // Last moves the cursor to the last value of the current value. @@ -235,7 +283,7 @@ func (b *Builder) Last() { length := b.Cursor().Len() if length > 0 { x := b.Cursor().Index(length - 1) - b.replace(x) + b.replace(valueTarget(x)) // TODO: create a "sliceTarget" ? } } } @@ -244,12 +292,13 @@ func (b *Builder) Last() { // Otherwise creates a new element in that slice and moves to it. func (b *Builder) SliceLastOrCreate() error { t := b.top() - err := checkKind(t.Type(), reflect.Slice) + v := t.get() + err := checkKind(v.Type(), reflect.Slice) if err != nil { return err } - if t.Len() == 0 { + if v.Len() == 0 { return b.SliceNewElem() } b.Last() @@ -261,14 +310,15 @@ func (b *Builder) SliceLastOrCreate() error { // object. func (b *Builder) SliceNewElem() error { t := b.top() - err := checkKind(t.Type(), reflect.Slice) + v := t.get() + err := checkKind(v.Type(), reflect.Slice) if err != nil { return err } - elem := reflect.New(t.Type().Elem()) - newSlice := reflect.Append(t, elem.Elem()) - t.Set(newSlice) - b.replace(t.Index(t.Len() - 1)) + elem := reflect.New(v.Type().Elem()) + newSlice := reflect.Append(v, elem.Elem()) + v.Set(newSlice) + b.replace(valueTarget(v.Index(v.Len() - 1))) // TODO: "sliceTarget"? return nil } @@ -278,37 +328,38 @@ func assertPtr(v reflect.Value) { } } -func (b *Builder) SliceAppend(v reflect.Value) error { - assertPtr(v) +func (b *Builder) SliceAppend(value reflect.Value) error { + assertPtr(value) t := b.top() + v := t.get() // pointer to a slice - if t.Kind() == reflect.Ptr { + if v.Kind() == reflect.Ptr { // if the pointer is nil we need to allocate the slice - if t.IsNil() { - x := reflect.New(t.Type().Elem()) - t.Set(x) + if v.IsNil() { + x := reflect.New(v.Type().Elem()) + v.Set(x) } // target the slice itself - t = t.Elem() + v = v.Elem() } - err := checkKind(t.Type(), reflect.Slice) + err := checkKind(v.Type(), reflect.Slice) if err != nil { return err } - if t.Type().Elem().Kind() == reflect.Ptr { + if v.Type().Elem().Kind() == reflect.Ptr { // if it is a slice of pointers, we can just append } else { // otherwise we need to reference the value - v = v.Elem() + value = value.Elem() } - newSlice := reflect.Append(t, v) - t.Set(newSlice) - b.replace(t.Index(t.Len() - 1)) + newSlice := reflect.Append(v, value) + v.Set(newSlice) + b.replace(valueTarget(v.Index(v.Len() - 1))) // TODO: "sliceTarget" ? return nil } @@ -316,61 +367,65 @@ func (b *Builder) SliceAppend(v reflect.Value) error { // Errors if a string cannot be assigned to the current value. func (b *Builder) SetString(s string) error { t := b.top() + v := t.get() - if t.Kind() == reflect.Ptr { - t.Set(reflect.ValueOf(&s)) + if v.Kind() == reflect.Ptr { + v.Set(reflect.ValueOf(&s)) } else { - err := checkKind(t.Type(), reflect.String) + err := checkKind(v.Type(), reflect.String) if err != nil { return err } - t.SetString(s) + v.SetString(s) } return nil } // Set the value at the cursor to the given boolean. // Errors if a boolean cannot be assigned to the current value. -func (b *Builder) SetBool(v bool) error { +func (b *Builder) SetBool(value bool) error { t := b.top() + v := t.get() - err := checkKind(t.Type(), reflect.Bool) + err := checkKind(v.Type(), reflect.Bool) if err != nil { return err } - t.SetBool(v) + v.SetBool(value) return nil } func (b *Builder) SetFloat(n float64) error { t := b.top() + v := t.get() - err := checkKindFloat(t.Type()) + err := checkKindFloat(v.Type()) if err != nil { return err } - t.SetFloat(n) + v.SetFloat(n) return nil } func (b *Builder) SetInt(n int64) error { t := b.top() + v := t.get() - err := checkKindInt(t.Type()) + err := checkKindInt(v.Type()) if err != nil { return err } - t.SetInt(n) + v.SetInt(n) return nil } func (b *Builder) Set(v reflect.Value) error { t := b.top() - t.Set(v) + t.set(v) return nil } From 052233e858b8b0d4468e5f9f3f9628db0798f0ea Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Thu, 18 Feb 2021 23:30:46 -0500 Subject: [PATCH 048/228] wip: debugging maps --- .../imported_tests/unmarshal_imported_test.go | 6 -- internal/reflectbuild/reflectbuild.go | 60 ++++++++++++++++++- 2 files changed, 58 insertions(+), 8 deletions(-) diff --git a/internal/imported_tests/unmarshal_imported_test.go b/internal/imported_tests/unmarshal_imported_test.go index 5b96dcd6..5b87decd 100644 --- a/internal/imported_tests/unmarshal_imported_test.go +++ b/internal/imported_tests/unmarshal_imported_test.go @@ -298,12 +298,6 @@ func TestDocUnmarshal(t *testing.T) { assert.Equal(t, expected, result) } -type tomlTypeCheckTest struct { - name string - item interface{} - typ int //0=primitive, 1=otherslice, 2=treeslice, 3=tree -} - type unexportedMarshalTestStruct struct { String string `toml:"string"` StringList []string `toml:"strlist"` diff --git a/internal/reflectbuild/reflectbuild.go b/internal/reflectbuild/reflectbuild.go index d79b8024..85a9e6bf 100644 --- a/internal/reflectbuild/reflectbuild.go +++ b/internal/reflectbuild/reflectbuild.go @@ -18,6 +18,8 @@ type structFieldGetters map[string]fieldGetter type target interface { get() reflect.Value set(value reflect.Value) + + fmt.Stringer } type valueTarget reflect.Value @@ -30,6 +32,10 @@ func (v valueTarget) set(value reflect.Value) { reflect.Value(v).Set(value) } +func (v valueTarget) String() string { + return fmt.Sprintf("valueTarget: '%s' (%s)", reflect.Value(v), reflect.Value(v).Type()) +} + type mapTarget struct { index reflect.Value m reflect.Value @@ -43,6 +49,10 @@ func (v mapTarget) set(value reflect.Value) { v.m.SetMapIndex(v.index, value) } +func (v mapTarget) String() string { + return fmt.Sprintf("mapTarget: '%s'[%s]", v.m, v.index) +} + // Builder wraps a value and provides method to modify its structure. // It is a stateful object that keeps a cursor of what part of the object is // being modified. @@ -151,7 +161,9 @@ func NewBuilder(tag string, v interface{}) (Builder, error) { } func (b *Builder) top() target { - return b.stack[len(b.stack)-1] + t := b.stack[len(b.stack)-1] + fmt.Println("TOP:", t) + return t } func (b *Builder) duplicate() { @@ -163,6 +175,7 @@ func (b *Builder) duplicate() { func (b *Builder) pop() { b.stack = b.stack[:len(b.stack)-1] + fmt.Println("POP: top:", b.stack[len(b.stack)-1]) } func (b *Builder) len() int { @@ -185,6 +198,7 @@ func (b *Builder) Dump() string { } func (b *Builder) replace(v target) { + fmt.Println("REPLACING:", v) b.stack[len(b.stack)-1] = v } @@ -357,18 +371,54 @@ func (b *Builder) SliceAppend(value reflect.Value) error { value = value.Elem() } + if v.Type().Elem() != value.Type() { + nv, err := tryConvert(v.Type().Elem(), value) + if err != nil { + return fmt.Errorf("cannot assign '%s' to '%s'", value.Type(), v.Type().Elem()) + } + value = nv + } + newSlice := reflect.Append(v, value) v.Set(newSlice) b.replace(valueTarget(v.Index(v.Len() - 1))) // TODO: "sliceTarget" ? return nil } +func tryConvert(t reflect.Type, value reflect.Value) (reflect.Value, error) { + result := value + if value.Kind() == reflect.Ptr { + if t.Kind() != reflect.Ptr { + return reflect.Value{}, fmt.Errorf("cannot convert pointer to non-pointer") + } + + if value.Type().Elem().ConvertibleTo(t.Elem()) { + result = reflect.New(t.Elem()) + result.Elem().Set(value.Elem().Convert(t.Elem())) + return result, nil + } + } else { + if value.Type().ConvertibleTo(t) { + result = reflect.New(t) + result.Elem().Set(value.Convert(t)) + return result.Elem(), nil + } + } + return result, fmt.Errorf("no conversion found") +} + // Set the value at the cursor to the given string. // Errors if a string cannot be assigned to the current value. func (b *Builder) SetString(s string) error { t := b.top() v := t.get() + if !v.IsValid() { + fmt.Println("============ INVALID ===========") + fmt.Println(b.Dump()) + fmt.Println("==================== ===========") + } + if v.Kind() == reflect.Ptr { v.Set(reflect.ValueOf(&s)) } else { @@ -416,7 +466,13 @@ func (b *Builder) SetInt(n int64) error { err := checkKindInt(v.Type()) if err != nil { - return err + rn := reflect.ValueOf(n) + if rn.Type().ConvertibleTo(v.Type()) { + v.Set(rn.Convert(v.Type())) + return nil + } else { + return err + } } v.SetInt(n) From 7f9822db3597de366661216b13cf08fb78591a8e Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Fri, 19 Feb 2021 08:39:18 -0500 Subject: [PATCH 049/228] Target set methods now check for types --- .../imported_tests/unmarshal_imported_test.go | 49 +++++++++---------- internal/reflectbuild/reflectbuild.go | 43 +++++++++------- 2 files changed, 46 insertions(+), 46 deletions(-) diff --git a/internal/imported_tests/unmarshal_imported_test.go b/internal/imported_tests/unmarshal_imported_test.go index 5b87decd..e5f282fb 100644 --- a/internal/imported_tests/unmarshal_imported_test.go +++ b/internal/imported_tests/unmarshal_imported_test.go @@ -421,26 +421,6 @@ func TestErrUnmarshal(t *testing.T) { } } -type emptyMarshalTestStruct struct { - Title string `toml:"title"` - Bool bool `toml:"bool"` - Int int `toml:"int"` - String string `toml:"string"` - StringList []string `toml:"stringlist"` - Ptr *basicMarshalTestStruct `toml:"ptr"` - Map map[string]string `toml:"map"` -} - -var emptyTestData = emptyMarshalTestStruct{ - Title: "Placeholder", - Bool: false, - Int: 0, - String: "", - StringList: []string{}, - Ptr: nil, - Map: map[string]string{}, -} - var emptyTestToml = []byte(`bool = false int = 0 string = "" @@ -474,15 +454,30 @@ var emptyTestToml2 = []byte(`title = "Placeholder" `) func TestEmptytomlUnmarshal(t *testing.T) { + type emptyMarshalTestStruct struct { + Title string `toml:"title"` + Bool bool `toml:"bool"` + Int int `toml:"int"` + String string `toml:"string"` + StringList []string `toml:"stringlist"` + Ptr *basicMarshalTestStruct `toml:"ptr"` + Map map[string]string `toml:"map"` + } + + emptyTestData := emptyMarshalTestStruct{ + Title: "Placeholder", + Bool: false, + Int: 0, + String: "", + StringList: []string{}, + Ptr: nil, + Map: map[string]string{}, + } + result := emptyMarshalTestStruct{} err := toml.Unmarshal(emptyTestToml, &result) - expected := emptyTestData - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(result, expected) { - t.Errorf("Bad empty unmarshal: expected %v, got %v", expected, result) - } + require.NoError(t, err) + assert.Equal(t, emptyTestData, result) } func TestEmptyUnmarshalOmit(t *testing.T) { diff --git a/internal/reflectbuild/reflectbuild.go b/internal/reflectbuild/reflectbuild.go index 85a9e6bf..d2ce7328 100644 --- a/internal/reflectbuild/reflectbuild.go +++ b/internal/reflectbuild/reflectbuild.go @@ -17,19 +17,32 @@ type structFieldGetters map[string]fieldGetter type target interface { get() reflect.Value - set(value reflect.Value) + set(value reflect.Value) error fmt.Stringer } +func isAssignable(t reflect.Type, v reflect.Value) error { + if v.Type().AssignableTo(t) { + return nil + } + return fmt.Errorf("cannot assign '%s' ('%s') to a '%s'", v, v.Type(), t) +} + type valueTarget reflect.Value func (v valueTarget) get() reflect.Value { return reflect.Value(v) } -func (v valueTarget) set(value reflect.Value) { +func (v valueTarget) set(value reflect.Value) error { + rv := reflect.Value(v) + err := isAssignable(rv.Type(), value) + if err != nil { + return err + } reflect.Value(v).Set(value) + return nil } func (v valueTarget) String() string { @@ -45,8 +58,13 @@ func (v mapTarget) get() reflect.Value { return v.m.MapIndex(v.index) } -func (v mapTarget) set(value reflect.Value) { +func (v mapTarget) set(value reflect.Value) error { + err := isAssignable(v.m.Type().Elem(), value) + if err != nil { + return err + } v.m.SetMapIndex(v.index, value) + return nil } func (v mapTarget) String() string { @@ -413,23 +431,11 @@ func (b *Builder) SetString(s string) error { t := b.top() v := t.get() - if !v.IsValid() { - fmt.Println("============ INVALID ===========") - fmt.Println(b.Dump()) - fmt.Println("==================== ===========") - } - if v.Kind() == reflect.Ptr { v.Set(reflect.ValueOf(&s)) - } else { - err := checkKind(v.Type(), reflect.String) - if err != nil { - return err - } - - v.SetString(s) + return nil } - return nil + return t.set(reflect.ValueOf(s)) } // Set the value at the cursor to the given boolean. @@ -481,8 +487,7 @@ func (b *Builder) SetInt(n int64) error { func (b *Builder) Set(v reflect.Value) error { t := b.top() - t.set(v) - return nil + return t.set(v) } func checkKindInt(rt reflect.Type) error { From 978143ce99a2db9da7bf5a7d46e9f7a3e9ad1e58 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Fri, 19 Feb 2021 08:53:19 -0500 Subject: [PATCH 050/228] Ensure that slices have been allocated when entering array --- internal/reflectbuild/reflectbuild.go | 15 +++++++++++++++ unmarshal.go | 4 ++++ 2 files changed, 19 insertions(+) diff --git a/internal/reflectbuild/reflectbuild.go b/internal/reflectbuild/reflectbuild.go index d2ce7328..4a677975 100644 --- a/internal/reflectbuild/reflectbuild.go +++ b/internal/reflectbuild/reflectbuild.go @@ -490,6 +490,21 @@ func (b *Builder) Set(v reflect.Value) error { return t.set(v) } +// EnsureSlice makes sure that the cursor points to a non-nil slice. +func (b *Builder) EnsureSlice() error { + t := b.top() + v := t.get() + if v.Kind() != reflect.Slice { + return IncorrectKindError{Actual: v.Kind(), Expected: reflect.Slice} + } + + if v.IsNil() { + v.Set(reflect.MakeSlice(v.Type(), 0, 0)) + } + + return nil +} + func checkKindInt(rt reflect.Type) error { switch rt.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: diff --git a/unmarshal.go b/unmarshal.go index 43f8a94d..3dd45590 100644 --- a/unmarshal.go +++ b/unmarshal.go @@ -65,6 +65,10 @@ func (u *unmarshaler) ArrayBegin() { return } u.builder.Save() + u.err = u.builder.EnsureSlice() + if u.err != nil { + return + } if u.assign { u.assign = false } else { From 4526154571816caab84c5d33c1c7d21cb3247bcd Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Fri, 19 Feb 2021 09:39:50 -0500 Subject: [PATCH 051/228] wip --- .../imported_tests/unmarshal_imported_test.go | 64 ++++++++----------- internal/reflectbuild/reflectbuild.go | 60 +++++++++++++++-- internal/reflectbuild/reflectbuild_test.go | 2 +- unmarshal.go | 17 +++-- 4 files changed, 94 insertions(+), 49 deletions(-) diff --git a/internal/imported_tests/unmarshal_imported_test.go b/internal/imported_tests/unmarshal_imported_test.go index e5f282fb..d642fc1c 100644 --- a/internal/imported_tests/unmarshal_imported_test.go +++ b/internal/imported_tests/unmarshal_imported_test.go @@ -430,29 +430,6 @@ title = "Placeholder" [map] `) -type emptyMarshalTestStruct2 struct { - Title string `toml:"title"` - Bool bool `toml:"bool,omitempty"` - Int int `toml:"int, omitempty"` - String string `toml:"string,omitempty "` - StringList []string `toml:"stringlist,omitempty"` - Ptr *basicMarshalTestStruct `toml:"ptr,omitempty"` - Map map[string]string `toml:"map,omitempty"` -} - -var emptyTestData2 = emptyMarshalTestStruct2{ - Title: "Placeholder", - Bool: false, - Int: 0, - String: "", - StringList: []string{}, - Ptr: nil, - Map: map[string]string{}, -} - -var emptyTestToml2 = []byte(`title = "Placeholder" -`) - func TestEmptytomlUnmarshal(t *testing.T) { type emptyMarshalTestStruct struct { Title string `toml:"title"` @@ -481,15 +458,32 @@ func TestEmptytomlUnmarshal(t *testing.T) { } func TestEmptyUnmarshalOmit(t *testing.T) { - result := emptyMarshalTestStruct2{} - err := toml.Unmarshal(emptyTestToml, &result) - expected := emptyTestData2 - if err != nil { - t.Fatal(err) + t.Skipf("Have not figured yet if omitempty is a good idea") + + type emptyMarshalTestStruct2 struct { + Title string `toml:"title"` + Bool bool `toml:"bool,omitempty"` + Int int `toml:"int, omitempty"` + String string `toml:"string,omitempty "` + StringList []string `toml:"stringlist,omitempty"` + Ptr *basicMarshalTestStruct `toml:"ptr,omitempty"` + Map map[string]string `toml:"map,omitempty"` } - if !reflect.DeepEqual(result, expected) { - t.Errorf("Bad empty omit unmarshal: expected %v, got %v", expected, result) + + var emptyTestData2 = emptyMarshalTestStruct2{ + Title: "Placeholder", + Bool: false, + Int: 0, + String: "", + StringList: []string{}, + Ptr: nil, + Map: map[string]string{}, } + + result := emptyMarshalTestStruct2{} + err := toml.Unmarshal(emptyTestToml, &result) + require.NoError(t, err) + assert.Equal(t, emptyTestData2, result) } type pointerMarshalTestStruct struct { @@ -532,15 +526,11 @@ Str = "Hello" `) func TestPointerUnmarshal(t *testing.T) { + t.Log("TOML data:", string(pointerTestToml)) result := pointerMarshalTestStruct{} err := toml.Unmarshal(pointerTestToml, &result) - expected := pointerTestData - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(result, expected) { - t.Errorf("Bad pointer unmarshal: expected %v, got %v", expected, result) - } + require.NoError(t, err) + assert.Equal(t, pointerTestData, result) } func TestUnmarshalTypeMismatch(t *testing.T) { diff --git a/internal/reflectbuild/reflectbuild.go b/internal/reflectbuild/reflectbuild.go index 4a677975..edbcc821 100644 --- a/internal/reflectbuild/reflectbuild.go +++ b/internal/reflectbuild/reflectbuild.go @@ -37,6 +37,14 @@ func (v valueTarget) get() reflect.Value { func (v valueTarget) set(value reflect.Value) error { rv := reflect.Value(v) + + // value is guaranteed to be a pointer + + if rv.Kind() != reflect.Ptr { + // TODO: check value is nil? + value = value.Elem() + } + err := isAssignable(rv.Type(), value) if err != nil { return err @@ -59,6 +67,13 @@ func (v mapTarget) get() reflect.Value { } func (v mapTarget) set(value reflect.Value) error { + // value is guaranteed to be a pointer + + if v.m.Type().Elem().Kind() != reflect.Ptr { + // TODO: check value is nil? + value = value.Elem() + } + err := isAssignable(v.m.Type().Elem(), value) if err != nil { return err @@ -486,6 +501,7 @@ func (b *Builder) SetInt(n int64) error { } func (b *Builder) Set(v reflect.Value) error { + assertPtr(v) t := b.top() return t.set(v) } @@ -494,8 +510,16 @@ func (b *Builder) Set(v reflect.Value) error { func (b *Builder) EnsureSlice() error { t := b.top() v := t.get() + + if v.Kind() == reflect.Ptr { + if v.IsNil() { + v.Set(reflect.New(v.Type().Elem())) + } + v = v.Elem() + } + if v.Kind() != reflect.Slice { - return IncorrectKindError{Actual: v.Kind(), Expected: reflect.Slice} + return IncorrectKindError{Actual: v.Kind(), Expected: []reflect.Kind{reflect.Slice}} } if v.IsNil() { @@ -505,6 +529,27 @@ func (b *Builder) EnsureSlice() error { return nil } +// EnsureStructOrMap makes sure that the cursor points to an initialized +// struct or map. +func (b *Builder) EnsureStructOrMap() error { + t := b.top() + v := t.get() + + switch v.Kind() { + case reflect.Struct: + case reflect.Map: + if v.IsNil() { + return t.set(reflect.MakeMap(v.Type())) + } + default: + return IncorrectKindError{ + Actual: v.Kind(), + Expected: []reflect.Kind{reflect.Struct, reflect.Map}, + } + } + return nil +} + func checkKindInt(rt reflect.Type) error { switch rt.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: @@ -513,7 +558,7 @@ func checkKindInt(rt reflect.Type) error { return IncorrectKindError{ Actual: rt.Kind(), - Expected: reflect.Int, + Expected: []reflect.Kind{reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64}, } } @@ -525,7 +570,7 @@ func checkKindFloat(rt reflect.Type) error { return IncorrectKindError{ Actual: rt.Kind(), - Expected: reflect.Float64, + Expected: []reflect.Kind{reflect.Float64}, } } @@ -533,7 +578,7 @@ func checkKind(rt reflect.Type, expected reflect.Kind) error { if rt.Kind() != expected { return IncorrectKindError{ Actual: rt.Kind(), - Expected: expected, + Expected: []reflect.Kind{expected}, } } return nil @@ -541,11 +586,14 @@ func checkKind(rt reflect.Type, expected reflect.Kind) error { type IncorrectKindError struct { Actual reflect.Kind - Expected reflect.Kind + Expected []reflect.Kind } func (e IncorrectKindError) Error() string { - return fmt.Sprintf("incorrect kind: expected '%s', got '%s'", e.Expected, e.Actual) + if len(e.Expected) < 2 { + return fmt.Sprintf("incorrect kind: expected '%s', got '%s'", e.Expected[0], e.Actual) + } + return fmt.Sprintf("incorrect kind: expected any of '%s', got '%s'", e.Expected, e.Actual) } type FieldNotFoundError struct { diff --git a/internal/reflectbuild/reflectbuild_test.go b/internal/reflectbuild/reflectbuild_test.go index 26d526cd..692e0c23 100644 --- a/internal/reflectbuild/reflectbuild_test.go +++ b/internal/reflectbuild/reflectbuild_test.go @@ -140,7 +140,7 @@ func TestSliceNewElemNested(t *testing.T) { func TestIncorrectKindError(t *testing.T) { err := reflectbuild.IncorrectKindError{ Actual: reflect.String, - Expected: reflect.Struct, + Expected: []reflect.Kind{reflect.Struct}, } assert.NotEmpty(t, err.Error()) } diff --git a/unmarshal.go b/unmarshal.go index 3dd45590..06d8eb95 100644 --- a/unmarshal.go +++ b/unmarshal.go @@ -45,6 +45,10 @@ type unmarshaler struct { // keyval if a field is missing. parsingTable bool + // Counters that indicate that we are skipping TOML expressions. It happens + // when the document contains values that are not in the target struct. + // TODO: signal the parser that it can just scan to avoid processing the + // unused data. skipKeyValCount uint skipTable bool } @@ -152,7 +156,8 @@ func (u *unmarshaler) StringValue(v []byte) { } u.builder.Load() } else { - u.err = u.builder.SetString(string(v)) + s := string(v) + u.err = u.builder.Set(reflect.ValueOf(&s)) } } @@ -216,7 +221,7 @@ func (u *unmarshaler) LocalDateValue(date LocalDate) { } u.builder.Load() } else { - u.err = u.builder.Set(reflect.ValueOf(date)) + u.err = u.builder.Set(reflect.ValueOf(&date)) } } @@ -232,7 +237,7 @@ func (u *unmarshaler) LocalDateTimeValue(dt LocalDateTime) { } u.builder.Load() } else { - u.err = u.builder.Set(reflect.ValueOf(dt)) + u.err = u.builder.Set(reflect.ValueOf(&dt)) } } @@ -248,7 +253,7 @@ func (u *unmarshaler) DateTimeValue(dt time.Time) { } u.builder.Load() } else { - u.err = u.builder.Set(reflect.ValueOf(dt)) + u.err = u.builder.Set(reflect.ValueOf(&dt)) } } @@ -264,7 +269,7 @@ func (u *unmarshaler) LocalTimeValue(localTime LocalTime) { } u.builder.Load() } else { - u.err = u.builder.Set(reflect.ValueOf(localTime)) + u.err = u.builder.Set(reflect.ValueOf(&localTime)) } } @@ -313,4 +318,6 @@ func (u *unmarshaler) StandardTableEnd() { if u.skipping() || u.err != nil { return } + + u.builder.EnsureStructOrMap() } From d24deebee328b9a301a5dd8e7cd184f9c45d384f Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Fri, 19 Feb 2021 19:26:46 -0500 Subject: [PATCH 052/228] wip making reflection tests pass --- .../imported_tests/unmarshal_imported_test.go | 103 ++++++++++-------- internal/reflectbuild/reflectbuild.go | 60 +++++++++- unmarshal.go | 6 +- 3 files changed, 116 insertions(+), 53 deletions(-) diff --git a/internal/imported_tests/unmarshal_imported_test.go b/internal/imported_tests/unmarshal_imported_test.go index d642fc1c..f4672b19 100644 --- a/internal/imported_tests/unmarshal_imported_test.go +++ b/internal/imported_tests/unmarshal_imported_test.go @@ -565,13 +565,8 @@ StringPtr = [["Three", "Four"]] func TestNestedUnmarshal(t *testing.T) { result := nestedMarshalTestStruct{} err := toml.Unmarshal(nestedTestToml, &result) - expected := nestedTestData - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(result, expected) { - t.Errorf("Bad nested unmarshal: expected %v, got %v", expected, result) - } + require.NoError(t, err) + assert.Equal(t, nestedTestData, result) } type customMarshalerParent struct { @@ -822,18 +817,13 @@ func TestUnmarshalTabInStringAndQuotedKey(t *testing.T) { }, } - for i := range testCases { - result := Test{} - err := toml.Unmarshal(testCases[i].input, &result) - if err != nil { - t.Errorf("%s test error:%v", testCases[i].desc, err) - continue - } - - if !reflect.DeepEqual(result, testCases[i].expected) { - t.Errorf("%s test error: expected\n-----\n%+v\n-----\ngot\n-----\n%+v\n-----\n", - testCases[i].desc, testCases[i].expected, result) - } + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + result := Test{} + err := toml.Unmarshal(test.input, &result) + require.NoError(t, err) + assert.Equal(t, test.expected, result) + }) } } @@ -940,9 +930,7 @@ func TestUnmarshalNonPointer(t *testing.T) { func TestUnmarshalInvalidPointerKind(t *testing.T) { a := 1 err := toml.Unmarshal([]byte{}, &a) - if err == nil { - t.Fatal("unmarshal should err when given an invalid pointer type") - } + assert.Error(t, err) } type testDuration struct { @@ -987,6 +975,7 @@ type testBadDuration struct { var testCamelCaseKeyToml = []byte(`fooBar = 10`) func TestUnmarshalCamelCaseKey(t *testing.T) { + t.Skipf("don't know if it is a good idea to automatically convert like that yet") var x struct { FooBar int B int @@ -1004,9 +993,7 @@ func TestUnmarshalCamelCaseKey(t *testing.T) { func TestUnmarshalNegativeUint(t *testing.T) { type check struct{ U uint } err := toml.Unmarshal([]byte("u = -1"), &check{}) - if err.Error() != "(1, 1): -1(int64) is negative so does not fit in uint" { - t.Error("expect err:(1, 1): -1(int64) is negative so does not fit in uint but got:", err) - } + assert.Error(t, err) } func TestUnmarshalCheckConversionFloatInt(t *testing.T) { @@ -1016,18 +1003,31 @@ func TestUnmarshalCheckConversionFloatInt(t *testing.T) { F float64 } - errU := toml.Unmarshal([]byte(`u = 1e300`), &conversionCheck{}) - errI := toml.Unmarshal([]byte(`i = 1e300`), &conversionCheck{}) - errF := toml.Unmarshal([]byte(`f = 9223372036854775806`), &conversionCheck{}) - - if errU.Error() != "(1, 1): Can't convert 1e+300(float64) to uint" { - t.Error("expect err:(1, 1): Can't convert 1e+300(float64) to uint but got:", errU) + type TestCase struct { + desc string + input string } - if errI.Error() != "(1, 1): Can't convert 1e+300(float64) to int" { - t.Error("expect err:(1, 1): Can't convert 1e+300(float64) to int but got:", errI) + + testCases := []TestCase{ + { + desc: "unsigned int", + input: `u = 1e300`, + }, + { + desc: "int", + input: `i = 1e300`, + }, + { + desc: "float", + input: `f = 9223372036854775806`, + }, } - if errF.Error() != "(1, 1): Can't convert 9223372036854775806(int64) to float64" { - t.Error("expect err:(1, 1): Can't convert 9223372036854775806(int64) to float64 but got:", errF) + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + err := toml.Unmarshal([]byte(test.input), &conversionCheck{}) + require.Error(t, err) + }) } } @@ -1038,18 +1038,31 @@ func TestUnmarshalOverflow(t *testing.T) { F32 float32 } - errU8 := toml.Unmarshal([]byte(`u8 = 300`), &overflow{}) - errI8 := toml.Unmarshal([]byte(`i8 = 300`), &overflow{}) - errF32 := toml.Unmarshal([]byte(`f32 = 1e300`), &overflow{}) - - if errU8.Error() != "(1, 1): 300(int64) would overflow uint8" { - t.Error("expect err:(1, 1): 300(int64) would overflow uint8 but got:", errU8) + type TestCase struct { + desc string + input string } - if errI8.Error() != "(1, 1): 300(int64) would overflow int8" { - t.Error("expect err:(1, 1): 300(int64) would overflow int8 but got:", errI8) + + testCases := []TestCase{ + { + desc: "byte", + input: `u8 = 300`, + }, + { + desc: "int8", + input: `i8 = 300`, + }, + { + desc: "float32", + input: `f32 = 1e300`, + }, } - if errF32.Error() != "(1, 1): 1e+300(float64) would overflow float32" { - t.Error("expect err:(1, 1): 1e+300(float64) would overflow float32 but got:", errF32) + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + err := toml.Unmarshal([]byte(test.input), &overflow{}) + require.Error(t, err) + }) } } diff --git a/internal/reflectbuild/reflectbuild.go b/internal/reflectbuild/reflectbuild.go index edbcc821..945bc3a5 100644 --- a/internal/reflectbuild/reflectbuild.go +++ b/internal/reflectbuild/reflectbuild.go @@ -47,7 +47,10 @@ func (v valueTarget) set(value reflect.Value) error { err := isAssignable(rv.Type(), value) if err != nil { - return err + if !value.Type().ConvertibleTo(rv.Type()) { + return err + } + value = value.Convert(rv.Type()) } reflect.Value(v).Set(value) return nil @@ -74,14 +77,27 @@ func (v mapTarget) set(value reflect.Value) error { value = value.Elem() } - err := isAssignable(v.m.Type().Elem(), value) + targetType := v.m.Type().Elem() + value, err := convertAsNeeded(targetType, value) if err != nil { return err } + v.m.SetMapIndex(v.index, value) return nil } +func convertAsNeeded(t reflect.Type, v reflect.Value) (reflect.Value, error) { + err := isAssignable(t, v) + if err != nil { + if !v.Type().ConvertibleTo(t) { + return reflect.Value{}, err + } + v = v.Convert(t) + } + return v, nil +} + func (v mapTarget) String() string { return fmt.Sprintf("mapTarget: '%s'[%s]", v.m, v.index) } @@ -259,6 +275,11 @@ func (b *Builder) DigField(s string) error { // TODO: handle error when map is not indexed by strings key := reflect.ValueOf(s) + key, err := convertAsNeeded(v.Type().Key(), key) + if err != nil { + return err + } + b.replace(mapTarget{ index: key, m: v, @@ -358,6 +379,11 @@ func (b *Builder) SliceLastOrCreate() error { func (b *Builder) SliceNewElem() error { t := b.top() v := t.get() + + if v.Kind() == reflect.Ptr { + v = v.Elem() + } + err := checkKind(v.Type(), reflect.Slice) if err != nil { return err @@ -519,7 +545,11 @@ func (b *Builder) EnsureSlice() error { } if v.Kind() != reflect.Slice { - return IncorrectKindError{Actual: v.Kind(), Expected: []reflect.Kind{reflect.Slice}} + return IncorrectKindError{ + Reason: "EnsureSlice", + Actual: v.Kind(), + Expected: []reflect.Kind{reflect.Slice}, + } } if v.IsNil() { @@ -539,10 +569,13 @@ func (b *Builder) EnsureStructOrMap() error { case reflect.Struct: case reflect.Map: if v.IsNil() { - return t.set(reflect.MakeMap(v.Type())) + x := reflect.New(v.Type()) + x.Elem().Set(reflect.MakeMap(v.Type())) + return t.set(x) } default: return IncorrectKindError{ + Reason: "EnsureStructOrMap", Actual: v.Kind(), Expected: []reflect.Kind{reflect.Struct, reflect.Map}, } @@ -557,6 +590,7 @@ func checkKindInt(rt reflect.Type) error { } return IncorrectKindError{ + Reason: "CheckKindInt", Actual: rt.Kind(), Expected: []reflect.Kind{reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64}, } @@ -569,6 +603,7 @@ func checkKindFloat(rt reflect.Type) error { } return IncorrectKindError{ + Reason: "CheckKindFloat", Actual: rt.Kind(), Expected: []reflect.Kind{reflect.Float64}, } @@ -577,6 +612,7 @@ func checkKindFloat(rt reflect.Type) error { func checkKind(rt reflect.Type, expected reflect.Kind) error { if rt.Kind() != expected { return IncorrectKindError{ + Reason: "CheckKind", Actual: rt.Kind(), Expected: []reflect.Kind{expected}, } @@ -585,15 +621,27 @@ func checkKind(rt reflect.Type, expected reflect.Kind) error { } type IncorrectKindError struct { + Reason string Actual reflect.Kind Expected []reflect.Kind } func (e IncorrectKindError) Error() string { + b := strings.Builder{} + b.WriteString("incorrect kind: ") + if len(e.Expected) < 2 { - return fmt.Sprintf("incorrect kind: expected '%s', got '%s'", e.Expected[0], e.Actual) + b.WriteString(fmt.Sprintf("expected '%s', got '%s'", e.Expected[0], e.Actual)) + } else { + b.WriteString(fmt.Sprintf("expected any of '%s', got '%s'", e.Expected, e.Actual)) } - return fmt.Sprintf("incorrect kind: expected any of '%s', got '%s'", e.Expected, e.Actual) + + if e.Reason != "" { + b.WriteString(": ") + b.WriteString(e.Reason) + } + + return b.String() } type FieldNotFoundError struct { diff --git a/unmarshal.go b/unmarshal.go index 06d8eb95..882551f1 100644 --- a/unmarshal.go +++ b/unmarshal.go @@ -189,7 +189,8 @@ func (u *unmarshaler) FloatValue(n float64) { } u.builder.Load() } else { - u.err = u.builder.SetFloat(n) + u.err = u.builder.Set(reflect.ValueOf(&n)) + //u.err = u.builder.SetFloat(n) } } @@ -205,7 +206,8 @@ func (u *unmarshaler) IntValue(n int64) { } u.builder.Load() } else { - u.err = u.builder.SetInt(n) + u.err = u.builder.Set(reflect.ValueOf(&n)) + //u.err = u.builder.SetInt(n) } } From c77f1d815c7123b284d240550268c45672029aac Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Fri, 19 Feb 2021 19:48:43 -0500 Subject: [PATCH 053/228] Skip default tags tests --- .../imported_tests/unmarshal_imported_test.go | 293 +++++++++--------- 1 file changed, 147 insertions(+), 146 deletions(-) diff --git a/internal/imported_tests/unmarshal_imported_test.go b/internal/imported_tests/unmarshal_imported_test.go index f4672b19..6d7922c3 100644 --- a/internal/imported_tests/unmarshal_imported_test.go +++ b/internal/imported_tests/unmarshal_imported_test.go @@ -11,7 +11,6 @@ import ( "fmt" "reflect" "strconv" - "strings" "testing" "time" @@ -536,9 +535,7 @@ func TestPointerUnmarshal(t *testing.T) { func TestUnmarshalTypeMismatch(t *testing.T) { result := pointerMarshalTestStruct{} err := toml.Unmarshal([]byte("List = 123"), &result) - if !strings.HasPrefix(err.Error(), "(1, 1): Can't convert 123(int64) to []string(slice)") { - t.Errorf("Type mismatch must be reported: got %v", err.Error()) - } + assert.Error(t, err) } type nestedMarshalTestStruct struct { @@ -1067,163 +1064,166 @@ func TestUnmarshalOverflow(t *testing.T) { } func TestUnmarshalDefault(t *testing.T) { - type EmbeddedStruct struct { - StringField string `default:"c"` - } - - type aliasUint uint + t.Skipf("don't know if it is a good idea to have `default`") + t.Run("main", func(t *testing.T) { + type EmbeddedStruct struct { + StringField string `default:"c"` + } - var doc struct { - StringField string `default:"a"` - BoolField bool `default:"true"` - UintField uint `default:"1"` - Uint8Field uint8 `default:"8"` - Uint16Field uint16 `default:"16"` - Uint32Field uint32 `default:"32"` - Uint64Field uint64 `default:"64"` - IntField int `default:"-1"` - Int8Field int8 `default:"-8"` - Int16Field int16 `default:"-16"` - Int32Field int32 `default:"-32"` - Int64Field int64 `default:"-64"` - Float32Field float32 `default:"32.1"` - Float64Field float64 `default:"64.1"` - DurationField time.Duration `default:"120ms"` - DurationField2 time.Duration `default:"120000000"` - NonEmbeddedStruct struct { - StringField string `default:"b"` + type aliasUint uint + + var doc struct { + StringField string `default:"a"` + BoolField bool `default:"true"` + UintField uint `default:"1"` + Uint8Field uint8 `default:"8"` + Uint16Field uint16 `default:"16"` + Uint32Field uint32 `default:"32"` + Uint64Field uint64 `default:"64"` + IntField int `default:"-1"` + Int8Field int8 `default:"-8"` + Int16Field int16 `default:"-16"` + Int32Field int32 `default:"-32"` + Int64Field int64 `default:"-64"` + Float32Field float32 `default:"32.1"` + Float64Field float64 `default:"64.1"` + DurationField time.Duration `default:"120ms"` + DurationField2 time.Duration `default:"120000000"` + NonEmbeddedStruct struct { + StringField string `default:"b"` + } + EmbeddedStruct + AliasUintField aliasUint `default:"1000"` } - EmbeddedStruct - AliasUintField aliasUint `default:"1000"` - } - err := toml.Unmarshal([]byte(``), &doc) - if err != nil { - t.Fatal(err) - } - if doc.BoolField != true { - t.Errorf("BoolField should be true, not %t", doc.BoolField) - } - if doc.StringField != "a" { - t.Errorf("StringField should be \"a\", not %s", doc.StringField) - } - if doc.UintField != 1 { - t.Errorf("UintField should be 1, not %d", doc.UintField) - } - if doc.Uint8Field != 8 { - t.Errorf("Uint8Field should be 8, not %d", doc.Uint8Field) - } - if doc.Uint16Field != 16 { - t.Errorf("Uint16Field should be 16, not %d", doc.Uint16Field) - } - if doc.Uint32Field != 32 { - t.Errorf("Uint32Field should be 32, not %d", doc.Uint32Field) - } - if doc.Uint64Field != 64 { - t.Errorf("Uint64Field should be 64, not %d", doc.Uint64Field) - } - if doc.IntField != -1 { - t.Errorf("IntField should be -1, not %d", doc.IntField) - } - if doc.Int8Field != -8 { - t.Errorf("Int8Field should be -8, not %d", doc.Int8Field) - } - if doc.Int16Field != -16 { - t.Errorf("Int16Field should be -16, not %d", doc.Int16Field) - } - if doc.Int32Field != -32 { - t.Errorf("Int32Field should be -32, not %d", doc.Int32Field) - } - if doc.Int64Field != -64 { - t.Errorf("Int64Field should be -64, not %d", doc.Int64Field) - } - if doc.Float32Field != 32.1 { - t.Errorf("Float32Field should be 32.1, not %f", doc.Float32Field) - } - if doc.Float64Field != 64.1 { - t.Errorf("Float64Field should be 64.1, not %f", doc.Float64Field) - } - if doc.DurationField != 120*time.Millisecond { - t.Errorf("DurationField should be 120ms, not %s", doc.DurationField.String()) - } - if doc.DurationField2 != 120*time.Millisecond { - t.Errorf("DurationField2 should be 120000000, not %d", doc.DurationField2) - } - if doc.NonEmbeddedStruct.StringField != "b" { - t.Errorf("StringField should be \"b\", not %s", doc.NonEmbeddedStruct.StringField) - } - if doc.EmbeddedStruct.StringField != "c" { - t.Errorf("StringField should be \"c\", not %s", doc.EmbeddedStruct.StringField) - } - if doc.AliasUintField != 1000 { - t.Errorf("AliasUintField should be 1000, not %d", doc.AliasUintField) - } -} + err := toml.Unmarshal([]byte(``), &doc) + if err != nil { + t.Fatal(err) + } + if doc.BoolField != true { + t.Errorf("BoolField should be true, not %t", doc.BoolField) + } + if doc.StringField != "a" { + t.Errorf("StringField should be \"a\", not %s", doc.StringField) + } + if doc.UintField != 1 { + t.Errorf("UintField should be 1, not %d", doc.UintField) + } + if doc.Uint8Field != 8 { + t.Errorf("Uint8Field should be 8, not %d", doc.Uint8Field) + } + if doc.Uint16Field != 16 { + t.Errorf("Uint16Field should be 16, not %d", doc.Uint16Field) + } + if doc.Uint32Field != 32 { + t.Errorf("Uint32Field should be 32, not %d", doc.Uint32Field) + } + if doc.Uint64Field != 64 { + t.Errorf("Uint64Field should be 64, not %d", doc.Uint64Field) + } + if doc.IntField != -1 { + t.Errorf("IntField should be -1, not %d", doc.IntField) + } + if doc.Int8Field != -8 { + t.Errorf("Int8Field should be -8, not %d", doc.Int8Field) + } + if doc.Int16Field != -16 { + t.Errorf("Int16Field should be -16, not %d", doc.Int16Field) + } + if doc.Int32Field != -32 { + t.Errorf("Int32Field should be -32, not %d", doc.Int32Field) + } + if doc.Int64Field != -64 { + t.Errorf("Int64Field should be -64, not %d", doc.Int64Field) + } + if doc.Float32Field != 32.1 { + t.Errorf("Float32Field should be 32.1, not %f", doc.Float32Field) + } + if doc.Float64Field != 64.1 { + t.Errorf("Float64Field should be 64.1, not %f", doc.Float64Field) + } + if doc.DurationField != 120*time.Millisecond { + t.Errorf("DurationField should be 120ms, not %s", doc.DurationField.String()) + } + if doc.DurationField2 != 120*time.Millisecond { + t.Errorf("DurationField2 should be 120000000, not %d", doc.DurationField2) + } + if doc.NonEmbeddedStruct.StringField != "b" { + t.Errorf("StringField should be \"b\", not %s", doc.NonEmbeddedStruct.StringField) + } + if doc.EmbeddedStruct.StringField != "c" { + t.Errorf("StringField should be \"c\", not %s", doc.EmbeddedStruct.StringField) + } + if doc.AliasUintField != 1000 { + t.Errorf("AliasUintField should be 1000, not %d", doc.AliasUintField) + } + }) -func TestUnmarshalDefaultFailureBool(t *testing.T) { - var doc struct { - Field bool `default:"blah"` - } + t.Run("failure bool", func(t *testing.T) { + var doc struct { + Field bool `default:"blah"` + } - err := toml.Unmarshal([]byte(``), &doc) - if err == nil { - t.Fatal("should error") - } -} + err := toml.Unmarshal([]byte(``), &doc) + if err == nil { + t.Fatal("should error") + } + }) -func TestUnmarshalDefaultFailureInt(t *testing.T) { - var doc struct { - Field int `default:"blah"` - } + t.Run("failure int", func(t *testing.T) { + var doc struct { + Field int `default:"blah"` + } - err := toml.Unmarshal([]byte(``), &doc) - if err == nil { - t.Fatal("should error") - } -} + err := toml.Unmarshal([]byte(``), &doc) + if err == nil { + t.Fatal("should error") + } + }) -func TestUnmarshalDefaultFailureInt64(t *testing.T) { - var doc struct { - Field int64 `default:"blah"` - } + t.Run("failure int64", func(t *testing.T) { + var doc struct { + Field int64 `default:"blah"` + } - err := toml.Unmarshal([]byte(``), &doc) - if err == nil { - t.Fatal("should error") - } -} + err := toml.Unmarshal([]byte(``), &doc) + if err == nil { + t.Fatal("should error") + } + }) -func TestUnmarshalDefaultFailureFloat64(t *testing.T) { - var doc struct { - Field float64 `default:"blah"` - } + t.Run("failure float64", func(t *testing.T) { + var doc struct { + Field float64 `default:"blah"` + } - err := toml.Unmarshal([]byte(``), &doc) - if err == nil { - t.Fatal("should error") - } -} + err := toml.Unmarshal([]byte(``), &doc) + if err == nil { + t.Fatal("should error") + } + }) -func TestUnmarshalDefaultFailureDuration(t *testing.T) { - var doc struct { - Field time.Duration `default:"blah"` - } + t.Run("failure duration", func(t *testing.T) { + var doc struct { + Field time.Duration `default:"blah"` + } - err := toml.Unmarshal([]byte(``), &doc) - if err == nil { - t.Fatal("should error") - } -} + err := toml.Unmarshal([]byte(``), &doc) + if err == nil { + t.Fatal("should error") + } + }) -func TestUnmarshalDefaultFailureUnsupported(t *testing.T) { - var doc struct { - Field struct{} `default:"blah"` - } + t.Run("failure unsupported", func(t *testing.T) { + var doc struct { + Field struct{} `default:"blah"` + } - err := toml.Unmarshal([]byte(``), &doc) - if err == nil { - t.Fatal("should error") - } + err := toml.Unmarshal([]byte(``), &doc) + if err == nil { + t.Fatal("should error") + } + }) } func TestUnmarshalNestedAnonymousStructs(t *testing.T) { @@ -1250,6 +1250,7 @@ func TestUnmarshalNestedAnonymousStructs(t *testing.T) { } func TestUnmarshalNestedAnonymousStructs_Controversial(t *testing.T) { + // TODO: what does encoding/json do? type Nested struct { Value string `toml:"nested"` } From bf051f1718c2f6371ed81729d9d44c4b30369c3b Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Mon, 1 Mar 2021 20:50:18 -0500 Subject: [PATCH 054/228] Fixed some tests --- .../imported_tests/unmarshal_imported_test.go | 31 +++++-------------- internal/reflectbuild/reflectbuild.go | 18 +++++++++-- parser.go | 5 +++ scanner.go | 2 +- 4 files changed, 30 insertions(+), 26 deletions(-) diff --git a/internal/imported_tests/unmarshal_imported_test.go b/internal/imported_tests/unmarshal_imported_test.go index 6d7922c3..c172efba 100644 --- a/internal/imported_tests/unmarshal_imported_test.go +++ b/internal/imported_tests/unmarshal_imported_test.go @@ -1779,12 +1779,8 @@ InnerField = "After4" } func TestUnmarshalNil(t *testing.T) { - if err := toml.Unmarshal([]byte(`whatever = "whatever"`), nil); err == nil { - t.Errorf("Expected err from nil marshal") - } - if err := toml.Unmarshal([]byte(`whatever = "whatever"`), (*struct{})(nil)); err == nil { - t.Errorf("Expected err from nil marshal") - } + assert.Error(t, toml.Unmarshal([]byte(`whatever = "whatever"`), nil)) + assert.Error(t, toml.Unmarshal([]byte(`whatever = "whatever"`), (*struct{})(nil))) } var sliceTomlDemo = []byte(`str_slice = ["Howdy","Hey There"] @@ -1846,20 +1842,13 @@ func TestUnmarshalSlice(t *testing.T) { func TestUnmarshalSliceFail(t *testing.T) { var actual sliceStruct - err := toml.Unmarshal([]byte(`str_slice = [1, 2]`), &actual) - if err.Error() != "(0, 0): Can't convert 1(int64) to string" { - t.Error("expect err:(0, 0): Can't convert 1(int64) to string but got ", err) - } + assert.Error(t, toml.Unmarshal([]byte(`str_slice = [1, 2]`), &actual)) } func TestUnmarshalSliceFail2(t *testing.T) { doc := `str_slice=[1,2]` var actual sliceStruct - err := toml.Unmarshal([]byte(doc), &actual) - if err.Error() != "(1, 1): Can't convert 1(int64) to string" { - t.Error("expect err:(1, 1): Can't convert 1(int64) to string but got ", err) - } - + assert.Error(t, toml.Unmarshal([]byte(doc), &actual)) } func TestUnmarshalMixedTypeArray(t *testing.T) { @@ -2209,18 +2198,14 @@ func TestUnmarshalEmptyInterface(t *testing.T) { if err != nil { t.Fatal(err) } + require.IsType(t, map[string]interface{}{}, v) - x, ok := v.(map[string]interface{}) - if !ok { - t.Fatal(err) - } - - if x["User"] != "pelletier" { - t.Fatalf("expected User=pelletier, but got %v", x) - } + x := v.(map[string]interface{}) + assert.Equal(t, "pelletier", x["User"]) } func TestUnmarshalEmptyInterfaceDeep(t *testing.T) { + t.Skipf("TODO") doc := []byte(` User = "pelletier" Age = 99 diff --git a/internal/reflectbuild/reflectbuild.go b/internal/reflectbuild/reflectbuild.go index 945bc3a5..ccdbb86a 100644 --- a/internal/reflectbuild/reflectbuild.go +++ b/internal/reflectbuild/reflectbuild.go @@ -202,6 +202,10 @@ func NewBuilder(tag string, v interface{}) (Builder, error) { return Builder{}, fmt.Errorf("cannot build a %s: need a pointer", rv.Type().Kind()) } + if rv.IsNil() { + return Builder{}, fmt.Errorf("cannot build a nil value") + } + return Builder{ root: rv.Elem(), stack: []target{valueTarget(rv.Elem())}, @@ -251,6 +255,8 @@ func (b *Builder) replace(v target) { b.stack[len(b.stack)-1] = v } +var mapStringInterfaceType = reflect.TypeOf(map[string]interface{}{}) + // DigField pushes the cursor into a field of the current struct. // Dereferences all pointers found along the way. // Errors if the current value is not a struct, or the field does not exist. @@ -259,9 +265,17 @@ func (b *Builder) DigField(s string) error { v := t.get() for v.Kind() == reflect.Interface || v.Kind() == reflect.Ptr { + if v.Kind() == reflect.Interface { + fmt.Println("STOP") + } + if v.IsNil() { - thing := reflect.New(v.Type().Elem()) - v.Set(thing) + if v.Kind() == reflect.Ptr { + thing := reflect.New(v.Type().Elem()) + v.Set(thing) + } else { + v.Set(reflect.MakeMap(mapStringInterfaceType)) + } } v = v.Elem() } diff --git a/parser.go b/parser.go index add080f9..081b1795 100644 --- a/parser.go +++ b/parser.go @@ -770,6 +770,11 @@ func (p parser) parseDateTime(b []byte) ([]byte, error) { localTime.Nanosecond *= 10 localTime.Nanosecond += int(b[idx] - '0') idx++ + + if idx < len(b) { + break + } + if !isDigit(b[idx]) { break } diff --git a/scanner.go b/scanner.go index 7a7d11e3..9ee67faf 100644 --- a/scanner.go +++ b/scanner.go @@ -154,7 +154,7 @@ func scanMultilineBasicString(b []byte) ([]byte, []byte, error) { switch b[i] { case '"': if scanFollowsMultilineBasicStringDelimiter(b[i:]) { - return b[:i+3], b[:i+3], nil + return b[:i+3], b[i+3:], nil } case '\\': if len(b) < i+2 { From 2cee819ce45dd2add7e21ba19239097bd703fe9f Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Tue, 2 Mar 2021 10:37:17 -0500 Subject: [PATCH 055/228] Going back and forth on whether types should always be converted --- .../imported_tests/unmarshal_imported_test.go | 13 +-- internal/reflectbuild/reflectbuild.go | 85 +++++++------------ unmarshal.go | 1 - 3 files changed, 39 insertions(+), 60 deletions(-) diff --git a/internal/imported_tests/unmarshal_imported_test.go b/internal/imported_tests/unmarshal_imported_test.go index c172efba..d468ceb5 100644 --- a/internal/imported_tests/unmarshal_imported_test.go +++ b/internal/imported_tests/unmarshal_imported_test.go @@ -925,6 +925,7 @@ func TestUnmarshalNonPointer(t *testing.T) { } func TestUnmarshalInvalidPointerKind(t *testing.T) { + t.Skipf("should this really be an error?") a := 1 err := toml.Unmarshal([]byte{}, &a) assert.Error(t, err) @@ -988,8 +989,9 @@ func TestUnmarshalCamelCaseKey(t *testing.T) { } func TestUnmarshalNegativeUint(t *testing.T) { + t.Skipf("not sure if we this should always error") type check struct{ U uint } - err := toml.Unmarshal([]byte("u = -1"), &check{}) + err := toml.Unmarshal([]byte("U = -1"), &check{}) assert.Error(t, err) } @@ -1008,15 +1010,15 @@ func TestUnmarshalCheckConversionFloatInt(t *testing.T) { testCases := []TestCase{ { desc: "unsigned int", - input: `u = 1e300`, + input: `U = 1e300`, }, { desc: "int", - input: `i = 1e300`, + input: `I = 1e300`, }, { desc: "float", - input: `f = 9223372036854775806`, + input: `F = 9223372036854775806`, }, } @@ -1250,7 +1252,7 @@ func TestUnmarshalNestedAnonymousStructs(t *testing.T) { } func TestUnmarshalNestedAnonymousStructs_Controversial(t *testing.T) { - // TODO: what does encoding/json do? + t.Skipf("TODO: what does encoding/json do?") type Nested struct { Value string `toml:"nested"` } @@ -2291,6 +2293,7 @@ type config437 struct { } func TestGithubIssue437(t *testing.T) { + t.Skipf("unmarshalTOML not implemented") src := ` [HTTP] PingTimeout = "32m" diff --git a/internal/reflectbuild/reflectbuild.go b/internal/reflectbuild/reflectbuild.go index ccdbb86a..0bd74e3b 100644 --- a/internal/reflectbuild/reflectbuild.go +++ b/internal/reflectbuild/reflectbuild.go @@ -45,13 +45,12 @@ func (v valueTarget) set(value reflect.Value) error { value = value.Elem() } - err := isAssignable(rv.Type(), value) + targetType := rv.Type() + value, err := tryConvert(targetType, value) if err != nil { - if !value.Type().ConvertibleTo(rv.Type()) { - return err - } - value = value.Convert(rv.Type()) + return err } + reflect.Value(v).Set(value) return nil } @@ -78,7 +77,7 @@ func (v mapTarget) set(value reflect.Value) error { } targetType := v.m.Type().Elem() - value, err := convertAsNeeded(targetType, value) + value, err := tryConvert(targetType, value) if err != nil { return err } @@ -87,17 +86,6 @@ func (v mapTarget) set(value reflect.Value) error { return nil } -func convertAsNeeded(t reflect.Type, v reflect.Value) (reflect.Value, error) { - err := isAssignable(t, v) - if err != nil { - if !v.Type().ConvertibleTo(t) { - return reflect.Value{}, err - } - v = v.Convert(t) - } - return v, nil -} - func (v mapTarget) String() string { return fmt.Sprintf("mapTarget: '%s'[%s]", v.m, v.index) } @@ -289,7 +277,7 @@ func (b *Builder) DigField(s string) error { // TODO: handle error when map is not indexed by strings key := reflect.ValueOf(s) - key, err := convertAsNeeded(v.Type().Key(), key) + key, err := tryConvert(v.Type().Key(), key) if err != nil { return err } @@ -445,11 +433,11 @@ func (b *Builder) SliceAppend(value reflect.Value) error { } if v.Type().Elem() != value.Type() { - nv, err := tryConvert(v.Type().Elem(), value) - if err != nil { - return fmt.Errorf("cannot assign '%s' to '%s'", value.Type(), v.Type().Elem()) - } - value = nv + //nv, err := tryConvert(v.Type().Elem(), value) + //if err != nil { + return fmt.Errorf("cannot assign '%s' to '%s'", value.Type(), v.Type().Elem()) + //} + //value = nv } newSlice := reflect.Append(v, value) @@ -460,24 +448,32 @@ func (b *Builder) SliceAppend(value reflect.Value) error { func tryConvert(t reflect.Type, value reflect.Value) (reflect.Value, error) { result := value + + if value.Type().AssignableTo(t) { + return result, nil + } + if value.Kind() == reflect.Ptr { if t.Kind() != reflect.Ptr { return reflect.Value{}, fmt.Errorf("cannot convert pointer to non-pointer") } - if value.Type().Elem().ConvertibleTo(t.Elem()) { - result = reflect.New(t.Elem()) - result.Elem().Set(value.Elem().Convert(t.Elem())) - return result, nil - } - } else { - if value.Type().ConvertibleTo(t) { - result = reflect.New(t) - result.Elem().Set(value.Convert(t)) - return result.Elem(), nil - } + value = value.Elem() + t = t.Elem() } - return result, fmt.Errorf("no conversion found") + + if !value.Type().ConvertibleTo(t) { + return result, fmt.Errorf("cannot convert '%s' to '%s'", value.Type(), t) + } + + switch t.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + value.Convert(reflect.TypeOf(int64(0))) + } + + result = reflect.New(t) + result.Elem().Set(value.Convert(t)) + return result.Elem(), nil } // Set the value at the cursor to the given string. @@ -521,25 +517,6 @@ func (b *Builder) SetFloat(n float64) error { return nil } -func (b *Builder) SetInt(n int64) error { - t := b.top() - v := t.get() - - err := checkKindInt(v.Type()) - if err != nil { - rn := reflect.ValueOf(n) - if rn.Type().ConvertibleTo(v.Type()) { - v.Set(rn.Convert(v.Type())) - return nil - } else { - return err - } - } - - v.SetInt(n) - return nil -} - func (b *Builder) Set(v reflect.Value) error { assertPtr(v) t := b.top() diff --git a/unmarshal.go b/unmarshal.go index 882551f1..60b309d0 100644 --- a/unmarshal.go +++ b/unmarshal.go @@ -207,7 +207,6 @@ func (u *unmarshaler) IntValue(n int64) { u.builder.Load() } else { u.err = u.builder.Set(reflect.ValueOf(&n)) - //u.err = u.builder.SetInt(n) } } From f698c102c717ce493744e81cfb4fe291d3d91661 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Mon, 8 Mar 2021 09:42:19 -0500 Subject: [PATCH 056/228] Convert should only handle specific types --- .../imported_tests/unmarshal_imported_test.go | 40 ++++----- internal/reflectbuild/reflectbuild.go | 84 +++++++++++++++---- 2 files changed, 87 insertions(+), 37 deletions(-) diff --git a/internal/imported_tests/unmarshal_imported_test.go b/internal/imported_tests/unmarshal_imported_test.go index d468ceb5..b0a2cac2 100644 --- a/internal/imported_tests/unmarshal_imported_test.go +++ b/internal/imported_tests/unmarshal_imported_test.go @@ -350,20 +350,6 @@ type errStruct struct { String *string `toml:"string"` } -var errTomls = []string{ - "bool = truly\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = 5000\nstring = \"Bite me\"", - "bool = true\ndate = 1979-05-27T07:3200Z\nfloat = 123.4\nint = 5000\nstring = \"Bite me\"", - "bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123a4\nint = 5000\nstring = \"Bite me\"", - "bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = j000\nstring = \"Bite me\"", - "bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = 5000\nstring = Bite me", - "bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = 5000\nstring = Bite me", - "bool = 1\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = 5000\nstring = \"Bite me\"", - "bool = true\ndate = 1\nfloat = 123.4\nint = 5000\nstring = \"Bite me\"", - "bool = true\ndate = 1979-05-27T07:32:00Z\n\"sorry\"\nint = 5000\nstring = \"Bite me\"", - "bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = \"sorry\"\nstring = \"Bite me\"", - "bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = 5000\nstring = 1", -} - type mapErr struct { Vals map[string]float64 } @@ -399,12 +385,28 @@ var intErrTomls = []string{ } func TestErrUnmarshal(t *testing.T) { + var errTomls = []string{ + "bool = truly\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = 5000\nstring = \"Bite me\"", + "bool = true\ndate = 1979-05-27T07:3200Z\nfloat = 123.4\nint = 5000\nstring = \"Bite me\"", + "bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123a4\nint = 5000\nstring = \"Bite me\"", + "bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = j000\nstring = \"Bite me\"", + "bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = 5000\nstring = Bite me", + "bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = 5000\nstring = Bite me", + "bool = 1\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = 5000\nstring = \"Bite me\"", + "bool = true\ndate = 1\nfloat = 123.4\nint = 5000\nstring = \"Bite me\"", + "bool = true\ndate = 1979-05-27T07:32:00Z\n\"sorry\"\nint = 5000\nstring = \"Bite me\"", + "bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = \"sorry\"\nstring = \"Bite me\"", + "bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = 5000\nstring = 1", + } + for ind, x := range errTomls { - result := errStruct{} - err := toml.Unmarshal([]byte(x), &result) - if err == nil { - t.Errorf("Expected err from case %d\n", ind) - } + t.Run(fmt.Sprintf("Base Case %d", ind), func(t *testing.T) { + result := errStruct{} + err := toml.Unmarshal([]byte(x), &result) + if err == nil { + t.Errorf("Expected err from case %d\n", ind) + } + }) } result2 := mapErr{} err := toml.Unmarshal([]byte("[Vals]\nfred=\"1.2\""), &result2) diff --git a/internal/reflectbuild/reflectbuild.go b/internal/reflectbuild/reflectbuild.go index 0bd74e3b..c7f3c18c 100644 --- a/internal/reflectbuild/reflectbuild.go +++ b/internal/reflectbuild/reflectbuild.go @@ -22,13 +22,6 @@ type target interface { fmt.Stringer } -func isAssignable(t reflect.Type, v reflect.Value) error { - if v.Type().AssignableTo(t) { - return nil - } - return fmt.Errorf("cannot assign '%s' ('%s') to a '%s'", v, v.Type(), t) -} - type valueTarget reflect.Value func (v valueTarget) get() reflect.Value { @@ -39,6 +32,9 @@ func (v valueTarget) set(value reflect.Value) error { rv := reflect.Value(v) // value is guaranteed to be a pointer + if value.Kind() != reflect.Ptr { + panic(fmt.Sprintf("set() should receive a pointer, not a '%s'", value.Kind())) + } if rv.Kind() != reflect.Ptr { // TODO: check value is nil? @@ -46,12 +42,12 @@ func (v valueTarget) set(value reflect.Value) error { } targetType := rv.Type() - value, err := tryConvert(targetType, value) + value, err := convert(targetType, value) if err != nil { return err } - reflect.Value(v).Set(value) + rv.Set(value) return nil } @@ -77,7 +73,7 @@ func (v mapTarget) set(value reflect.Value) error { } targetType := v.m.Type().Elem() - value, err := tryConvert(targetType, value) + value, err := convert(targetType, value) if err != nil { return err } @@ -277,7 +273,7 @@ func (b *Builder) DigField(s string) error { // TODO: handle error when map is not indexed by strings key := reflect.ValueOf(s) - key, err := tryConvert(v.Type().Key(), key) + key, err := convert(v.Type().Key(), key) if err != nil { return err } @@ -433,7 +429,7 @@ func (b *Builder) SliceAppend(value reflect.Value) error { } if v.Type().Elem() != value.Type() { - //nv, err := tryConvert(v.Type().Elem(), value) + //nv, err := convert(v.Type().Elem(), value) //if err != nil { return fmt.Errorf("cannot assign '%s' to '%s'", value.Type(), v.Type().Elem()) //} @@ -446,7 +442,19 @@ func (b *Builder) SliceAppend(value reflect.Value) error { return nil } -func tryConvert(t reflect.Type, value reflect.Value) (reflect.Value, error) { +// convert value so that it can be assigned to t. +// +// Conversion rules: +// +// * Pointers are de-referenced as needed. +// * Integer types are converted between each other as long as they don't +// overflow. +// * Float types are converted between each other as long as they don't +// overflow. +// +// TODO: this function acts as a switchboard. Runtime has enough information to +// generate per-type functions avoiding the double type switches. +func convert(t reflect.Type, value reflect.Value) (reflect.Value, error) { result := value if value.Type().AssignableTo(t) { @@ -462,13 +470,20 @@ func tryConvert(t reflect.Type, value reflect.Value) (reflect.Value, error) { t = t.Elem() } - if !value.Type().ConvertibleTo(t) { - return result, fmt.Errorf("cannot convert '%s' to '%s'", value.Type(), t) - } - + var err error switch t.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - value.Convert(reflect.TypeOf(int64(0))) + value, err = convertInt(t, value) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + value, err = convertUint(t, value) + case reflect.Float32, reflect.Float64: + value, err = convertFloat(t, value) + default: + err = fmt.Errorf("not converting a %s into a %s", value.Kind(), t.Kind()) + } + + if err != nil { + return value, err } result = reflect.New(t) @@ -476,6 +491,39 @@ func tryConvert(t reflect.Type, value reflect.Value) (reflect.Value, error) { return result.Elem(), nil } +func convertInt(t reflect.Type, value reflect.Value) (reflect.Value, error) { + switch value.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return value.Convert(t), nil // reflect.TypeOf(int64(0)) + default: + return value, fmt.Errorf("cannot convert %s to integer (%s)", value.Kind(), t.Kind()) + } +} + +func convertUint(t reflect.Type, value reflect.Value) (reflect.Value, error) { + switch value.Kind() { + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return value.Convert(t), nil // reflect.TypeOf(int64(0)) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + x := value.Int() + if x < 0 { + return value, fmt.Errorf("cannot store negative integer '%d' into %s", x, t.Kind()) + } + return value.Convert(t), nil + default: + return value, fmt.Errorf("cannot convert %s to unsigned integer (%s)", value.Kind(), t.Kind()) + } +} + +func convertFloat(t reflect.Type, value reflect.Value) (reflect.Value, error) { + switch value.Kind() { + case reflect.Float32, reflect.Float64: + return value.Convert(t), nil + default: + return value, fmt.Errorf("cannot convert %s to integer (%s)", value.Kind(), t.Kind()) + } +} + // Set the value at the cursor to the given string. // Errors if a string cannot be assigned to the current value. func (b *Builder) SetString(s string) error { From c35bcc55192bb6337c3d1d4d264e8f14caa39e46 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Mon, 8 Mar 2021 10:01:56 -0500 Subject: [PATCH 057/228] Convert returns pointer if a pointer is passed --- internal/reflectbuild/reflectbuild.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/internal/reflectbuild/reflectbuild.go b/internal/reflectbuild/reflectbuild.go index c7f3c18c..f8e8af29 100644 --- a/internal/reflectbuild/reflectbuild.go +++ b/internal/reflectbuild/reflectbuild.go @@ -428,12 +428,9 @@ func (b *Builder) SliceAppend(value reflect.Value) error { value = value.Elem() } - if v.Type().Elem() != value.Type() { - //nv, err := convert(v.Type().Elem(), value) - //if err != nil { - return fmt.Errorf("cannot assign '%s' to '%s'", value.Type(), v.Type().Elem()) - //} - //value = nv + value, err = convert(v.Type().Elem(), value) + if err != nil { + return err } newSlice := reflect.Append(v, value) @@ -455,12 +452,11 @@ func (b *Builder) SliceAppend(value reflect.Value) error { // TODO: this function acts as a switchboard. Runtime has enough information to // generate per-type functions avoiding the double type switches. func convert(t reflect.Type, value reflect.Value) (reflect.Value, error) { - result := value - if value.Type().AssignableTo(t) { - return result, nil + return value, nil } + returnPtr := false if value.Kind() == reflect.Ptr { if t.Kind() != reflect.Ptr { return reflect.Value{}, fmt.Errorf("cannot convert pointer to non-pointer") @@ -468,6 +464,7 @@ func convert(t reflect.Type, value reflect.Value) (reflect.Value, error) { value = value.Elem() t = t.Elem() + returnPtr = true } var err error @@ -486,8 +483,11 @@ func convert(t reflect.Type, value reflect.Value) (reflect.Value, error) { return value, err } - result = reflect.New(t) + result := reflect.New(t) // TODO: remove alloc result.Elem().Set(value.Convert(t)) + if returnPtr { + return result, nil + } return result.Elem(), nil } From 90f3b658c69a95f84111f41abdf658237ec936be Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Mon, 8 Mar 2021 20:27:04 -0500 Subject: [PATCH 058/228] Support type aliases --- internal/reflectbuild/reflectbuild.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/reflectbuild/reflectbuild.go b/internal/reflectbuild/reflectbuild.go index f8e8af29..b1466b40 100644 --- a/internal/reflectbuild/reflectbuild.go +++ b/internal/reflectbuild/reflectbuild.go @@ -467,6 +467,10 @@ func convert(t reflect.Type, value reflect.Value) (reflect.Value, error) { returnPtr = true } + if t.Kind() == value.Kind() { + return value.Convert(t), nil + } + var err error switch t.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: From 87b9d1cf987fab1ba3c0961804184994b69d6d24 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Mon, 8 Mar 2021 21:01:53 -0500 Subject: [PATCH 059/228] Handle overflows --- .../imported_tests/unmarshal_imported_test.go | 6 +- internal/reflectbuild/reflectbuild.go | 148 +++++++++++++++++- 2 files changed, 147 insertions(+), 7 deletions(-) diff --git a/internal/imported_tests/unmarshal_imported_test.go b/internal/imported_tests/unmarshal_imported_test.go index b0a2cac2..4ef0fb24 100644 --- a/internal/imported_tests/unmarshal_imported_test.go +++ b/internal/imported_tests/unmarshal_imported_test.go @@ -1047,15 +1047,15 @@ func TestUnmarshalOverflow(t *testing.T) { testCases := []TestCase{ { desc: "byte", - input: `u8 = 300`, + input: `U8 = 300`, }, { desc: "int8", - input: `i8 = 300`, + input: `I8 = 300`, }, { desc: "float32", - input: `f32 = 1e300`, + input: `F32 = 1e300`, }, } diff --git a/internal/reflectbuild/reflectbuild.go b/internal/reflectbuild/reflectbuild.go index b1466b40..0ad06e44 100644 --- a/internal/reflectbuild/reflectbuild.go +++ b/internal/reflectbuild/reflectbuild.go @@ -4,6 +4,7 @@ package reflectbuild import ( "fmt" + "math" "reflect" "strings" ) @@ -495,33 +496,172 @@ func convert(t reflect.Type, value reflect.Value) (reflect.Value, error) { return result.Elem(), nil } +type IntegerOverflowErr struct { + value int64 + min int64 + max int64 + kind reflect.Kind +} + +func (e IntegerOverflowErr) Error() string { + return fmt.Sprintf("integer overflow: cannot store %d in %s [%d, %d]", e.value, e.kind, e.min, e.max) +} + +const maxInt = int64(^uint(0) >> 1) +const minInt = -maxInt - 1 + func convertInt(t reflect.Type, value reflect.Value) (reflect.Value, error) { switch value.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return value.Convert(t), nil // reflect.TypeOf(int64(0)) + x := value.Int() + + switch t.Kind() { + case reflect.Int: + if x > maxInt || x < minInt { + return value, IntegerOverflowErr{ + value: x, + min: minInt, + max: maxInt, + kind: t.Kind(), + } + } + case reflect.Int8: + if x > math.MaxInt8 || x < math.MinInt8 { + return value, IntegerOverflowErr{ + value: x, + min: math.MinInt8, + max: math.MaxInt8, + kind: t.Kind(), + } + } + case reflect.Int16: + if x > math.MaxInt16 || x < math.MinInt16 { + return value, IntegerOverflowErr{ + value: x, + min: math.MinInt16, + max: math.MaxInt16, + kind: t.Kind(), + } + } + case reflect.Int32: + if x > math.MaxInt32 || x < math.MinInt32 { + return value, IntegerOverflowErr{ + value: x, + min: math.MinInt32, + max: math.MaxInt32, + kind: t.Kind(), + } + } + case reflect.Int64: + if x > math.MaxInt64 || x < math.MinInt64 { + return value, IntegerOverflowErr{ + value: x, + min: math.MinInt64, + max: math.MaxInt64, + kind: t.Kind(), + } + } + } + + return value.Convert(t), nil default: return value, fmt.Errorf("cannot convert %s to integer (%s)", value.Kind(), t.Kind()) } } +type UnsignedIntegerOverflowErr struct { + value uint64 + max uint64 + kind reflect.Kind +} + +func (e UnsignedIntegerOverflowErr) Error() string { + return fmt.Sprintf("unsigned integer overflow: cannot store %d in %s [max %d]", e.value, e.kind, e.max) +} + func convertUint(t reflect.Type, value reflect.Value) (reflect.Value, error) { switch value.Kind() { case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + err := convertUintOverflowCheck(t.Kind(), value.Uint()) + if err != nil { + return value, err + } return value.Convert(t), nil // reflect.TypeOf(int64(0)) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - x := value.Int() - if x < 0 { - return value, fmt.Errorf("cannot store negative integer '%d' into %s", x, t.Kind()) + signed := value.Int() + if signed < 0 { + return value, fmt.Errorf("cannot store negative integer '%d' into %s", signed, t.Kind()) + } + + x := uint64(signed) + err := convertUintOverflowCheck(t.Kind(), x) + if err != nil { + return value, err } + return value.Convert(t), nil default: return value, fmt.Errorf("cannot convert %s to unsigned integer (%s)", value.Kind(), t.Kind()) } } +const maxUint = uint64(^uint(0)) + +func convertUintOverflowCheck(t reflect.Kind, x uint64) error { + switch t { + case reflect.Uint: + if x > maxUint { + return UnsignedIntegerOverflowErr{ + value: x, + max: maxUint, + kind: t, + } + } + case reflect.Uint8: + if x > math.MaxUint8 { + return UnsignedIntegerOverflowErr{ + value: x, + max: math.MaxUint8, + kind: t, + } + } + case reflect.Uint16: + if x > math.MaxUint16 { + return UnsignedIntegerOverflowErr{ + value: x, + max: math.MaxUint16, + kind: t, + } + } + case reflect.Uint32: + if x > math.MaxUint32 { + return UnsignedIntegerOverflowErr{ + value: x, + max: math.MaxUint32, + kind: t, + } + } + case reflect.Uint64: + if x > math.MaxUint64 { + return UnsignedIntegerOverflowErr{ + value: x, + max: math.MaxUint64, + kind: t, + } + } + } + return nil +} + func convertFloat(t reflect.Type, value reflect.Value) (reflect.Value, error) { switch value.Kind() { case reflect.Float32, reflect.Float64: + if t.Kind() == reflect.Float32 { + f := value.Float() + if f > math.MaxFloat32 { + return value, fmt.Errorf("float overflow: %f does not fit in %s [max %f]") + } + } return value.Convert(t), nil default: return value, fmt.Errorf("cannot convert %s to integer (%s)", value.Kind(), t.Kind()) From a1c9b661b4993215ad9a66b460230e1d9d080cc1 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Mon, 8 Mar 2021 21:41:03 -0500 Subject: [PATCH 060/228] Allocate slice if needed --- internal/imported_tests/unmarshal_imported_test.go | 9 ++------- internal/reflectbuild/reflectbuild.go | 6 ++++++ unmarshal.go | 14 +++++++------- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/internal/imported_tests/unmarshal_imported_test.go b/internal/imported_tests/unmarshal_imported_test.go index 4ef0fb24..911c3a9f 100644 --- a/internal/imported_tests/unmarshal_imported_test.go +++ b/internal/imported_tests/unmarshal_imported_test.go @@ -1827,9 +1827,7 @@ type arrayTooSmallStruct struct { func TestUnmarshalSlice(t *testing.T) { var actual sliceStruct err := toml.Unmarshal(sliceTomlDemo, &actual) - if err != nil { - t.Error("shound not err", err) - } + require.NoError(t, err) expected := sliceStruct{ Slice: []string{"Howdy", "Hey There"}, SlicePtr: &[]string{"Howdy", "Hey There"}, @@ -1838,10 +1836,7 @@ func TestUnmarshalSlice(t *testing.T) { StructSlice: []basicMarshalTestSubStruct{{"1"}, {"2"}}, StructSlicePtr: &[]basicMarshalTestSubStruct{{"1"}, {"2"}}, } - if !reflect.DeepEqual(actual, expected) { - t.Errorf("Bad unmarshal: expected %v, got %v", expected, actual) - } - + assert.Equal(t, expected, actual) } func TestUnmarshalSliceFail(t *testing.T) { diff --git a/internal/reflectbuild/reflectbuild.go b/internal/reflectbuild/reflectbuild.go index 0ad06e44..b79807a3 100644 --- a/internal/reflectbuild/reflectbuild.go +++ b/internal/reflectbuild/reflectbuild.go @@ -380,6 +380,12 @@ func (b *Builder) SliceNewElem() error { v := t.get() if v.Kind() == reflect.Ptr { + // if the pointer is nil we need to allocate the slice + if v.IsNil() { + x := reflect.New(v.Type().Elem()) + v.Set(x) + } + // target the slice itself v = v.Elem() } diff --git a/unmarshal.go b/unmarshal.go index 60b309d0..58cdfac0 100644 --- a/unmarshal.go +++ b/unmarshal.go @@ -165,7 +165,7 @@ func (u *unmarshaler) BoolValue(b bool) { if u.skipping() || u.err != nil { return } - if u.builder.IsSlice() { + if u.builder.IsSliceOrPtr() { u.builder.Save() u.err = u.builder.SliceAppend(reflect.ValueOf(&b)) if u.err != nil { @@ -181,7 +181,7 @@ func (u *unmarshaler) FloatValue(n float64) { if u.skipping() || u.err != nil { return } - if u.builder.IsSlice() { + if u.builder.IsSliceOrPtr() { u.builder.Save() u.err = u.builder.SliceAppend(reflect.ValueOf(&n)) if u.err != nil { @@ -198,7 +198,7 @@ func (u *unmarshaler) IntValue(n int64) { if u.skipping() || u.err != nil { return } - if u.builder.IsSlice() { + if u.builder.IsSliceOrPtr() { u.builder.Save() u.err = u.builder.SliceAppend(reflect.ValueOf(&n)) if u.err != nil { @@ -214,7 +214,7 @@ func (u *unmarshaler) LocalDateValue(date LocalDate) { if u.skipping() || u.err != nil { return } - if u.builder.IsSlice() { + if u.builder.IsSliceOrPtr() { u.builder.Save() u.err = u.builder.SliceAppend(reflect.ValueOf(&date)) if u.err != nil { @@ -230,7 +230,7 @@ func (u *unmarshaler) LocalDateTimeValue(dt LocalDateTime) { if u.skipping() || u.err != nil { return } - if u.builder.IsSlice() { + if u.builder.IsSliceOrPtr() { u.builder.Save() u.err = u.builder.SliceAppend(reflect.ValueOf(&dt)) if u.err != nil { @@ -246,7 +246,7 @@ func (u *unmarshaler) DateTimeValue(dt time.Time) { if u.skipping() || u.err != nil { return } - if u.builder.IsSlice() { + if u.builder.IsSliceOrPtr() { u.builder.Save() u.err = u.builder.SliceAppend(reflect.ValueOf(&dt)) if u.err != nil { @@ -262,7 +262,7 @@ func (u *unmarshaler) LocalTimeValue(localTime LocalTime) { if u.skipping() || u.err != nil { return } - if u.builder.IsSlice() { + if u.builder.IsSliceOrPtr() { u.builder.Save() u.err = u.builder.SliceAppend(reflect.ValueOf(&localTime)) if u.err != nil { From 93a74fca35a9a0706b67abeb7e8c2a3cad4eaad8 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Mon, 8 Mar 2021 21:59:43 -0500 Subject: [PATCH 061/228] todo: inline tables --- .../imported_tests/unmarshal_imported_test.go | 24 ++++++------------- parser.go | 6 +++++ unmarshal.go | 17 +++++++++++++ 3 files changed, 30 insertions(+), 17 deletions(-) diff --git a/internal/imported_tests/unmarshal_imported_test.go b/internal/imported_tests/unmarshal_imported_test.go index 911c3a9f..7a483241 100644 --- a/internal/imported_tests/unmarshal_imported_test.go +++ b/internal/imported_tests/unmarshal_imported_test.go @@ -1887,11 +1887,9 @@ func TestUnmarshalMixedTypeArray(t *testing.T) { func TestUnmarshalArray(t *testing.T) { var err error - var actual1 arrayStruct - err = toml.Unmarshal(sliceTomlDemo, &actual1) - if err != nil { - t.Error("shound not err", err) - } + var actual arrayStruct + err = toml.Unmarshal(sliceTomlDemo, &actual) + require.NoError(t, err) expected := arrayStruct{ Slice: [4]string{"Howdy", "Hey There"}, @@ -1901,17 +1899,13 @@ func TestUnmarshalArray(t *testing.T) { StructSlice: [4]basicMarshalTestSubStruct{{"1"}, {"2"}}, StructSlicePtr: &[4]basicMarshalTestSubStruct{{"1"}, {"2"}}, } - if !reflect.DeepEqual(actual1, expected) { - t.Errorf("Bad unmarshal: expected %v, got %v", expected, actual1) - } + assert.Equal(t, expected, actual) } func TestUnmarshalArrayFail(t *testing.T) { var actual arrayTooSmallStruct err := toml.Unmarshal([]byte(`str_slice = ["Howdy", "Hey There"]`), &actual) - if err.Error() != "(0, 0): unmarshal: TOML array length (2) exceeds destination array length (1)" { - t.Error("expect err:(0, 0): unmarshal: TOML array length (2) exceeds destination array length (1) but got ", err) - } + assert.Error(t, err) } func TestUnmarshalArrayFail2(t *testing.T) { @@ -1919,9 +1913,7 @@ func TestUnmarshalArrayFail2(t *testing.T) { var actual arrayTooSmallStruct err := toml.Unmarshal([]byte(doc), &actual) - if err.Error() != "(1, 1): unmarshal: TOML array length (2) exceeds destination array length (1)" { - t.Error("expect err:(1, 1): unmarshal: TOML array length (2) exceeds destination array length (1) but got ", err) - } + assert.Error(t, err) } func TestUnmarshalArrayFail3(t *testing.T) { @@ -1932,9 +1924,7 @@ String2="2"` var actual arrayTooSmallStruct err := toml.Unmarshal([]byte(doc), &actual) - if err.Error() != "(3, 1): unmarshal: TOML array length (2) exceeds destination array length (1)" { - t.Error("expect err:(3, 1): unmarshal: TOML array length (2) exceeds destination array length (1) but got ", err) - } + assert.Error(t, err) } func TestDecoderStrict(t *testing.T) { diff --git a/parser.go b/parser.go index 081b1795..8ea8b119 100644 --- a/parser.go +++ b/parser.go @@ -23,6 +23,8 @@ type builder interface { ArrayBegin() ArrayEnd() Assignation() + InlineTableBegin() + InlineTableEnd() StringValue(v []byte) BoolValue(b bool) @@ -249,6 +251,9 @@ func (p parser) parseInlineTable(b []byte) ([]byte, error) { //inline-table-sep = ws %x2C ws ; , Comma //inline-table-keyvals = keyval [ inline-table-sep inline-table-keyvals ] + p.builder.InlineTableBegin() + defer p.builder.InlineTableEnd() + b = b[1:] first := true @@ -273,6 +278,7 @@ func (p parser) parseInlineTable(b []byte) ([]byte, error) { first = false } + return expect('}', b) } diff --git a/unmarshal.go b/unmarshal.go index 58cdfac0..84ee28d9 100644 --- a/unmarshal.go +++ b/unmarshal.go @@ -121,6 +121,23 @@ func (u *unmarshaler) ArrayTableEnd() { u.arrayTableKey = u.arrayTableKey[:0] } +func (u *unmarshaler) InlineTableBegin() { + if u.skipping() || u.err != nil { + return + } + + // TODO + +} + +func (u *unmarshaler) InlineTableEnd() { + if u.skipping() || u.err != nil { + return + } + + // TODO +} + func (u *unmarshaler) KeyValBegin() { if u.skipKeyValCount > 0 { u.skipKeyValCount++ From 21d3e85fcca536b9decb24d764fbca8d1054c836 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Sat, 13 Mar 2021 11:38:09 -0500 Subject: [PATCH 062/228] Playing with an AST Idea would be to build a light AST as a first pass, then have the unmarshaler and Document parser do what they need with it. --- internal/ast/ast.go | 162 +++ .../imported_tests/unmarshal_imported_test.go | 24 +- internal/reflectbuild/reflectbuild.go | 72 +- internal/unmarshaler/parser.go | 1192 +++++++++++++++++ internal/unmarshaler/parser_test.go | 50 + internal/unmarshaler/scanner.go | 168 +++ internal/unmarshaler/targets.go | 94 ++ internal/unmarshaler/targets_test.go | 166 +++ internal/unmarshaler/unmarshaler.go | 69 + internal/unmarshaler/unmarshaler_test.go | 39 + unmarshal.go | 32 +- 11 files changed, 2009 insertions(+), 59 deletions(-) create mode 100644 internal/ast/ast.go create mode 100644 internal/unmarshaler/parser.go create mode 100644 internal/unmarshaler/parser_test.go create mode 100644 internal/unmarshaler/scanner.go create mode 100644 internal/unmarshaler/targets.go create mode 100644 internal/unmarshaler/targets_test.go create mode 100644 internal/unmarshaler/unmarshaler.go create mode 100644 internal/unmarshaler/unmarshaler_test.go diff --git a/internal/ast/ast.go b/internal/ast/ast.go new file mode 100644 index 00000000..c0748f6e --- /dev/null +++ b/internal/ast/ast.go @@ -0,0 +1,162 @@ +package ast + +import ( + "fmt" + "strings" +) + +type Kind int + +const ( + // meta + Comment Kind = iota + Key + + // top level structures + Table + ArrayTable + KeyValue + + // containers values + Array + InlineTable + + // values + String + Bool + Float + Integer + LocalDate + LocalDateTime + DateTime + Time +) + +func (k Kind) String() string { + switch k { + case Comment: + return "Comment" + case Key: + return "Key" + case Table: + return "Table" + case ArrayTable: + return "ArrayTable" + case KeyValue: + return "KeyValue" + case Array: + return "Array" + case InlineTable: + return "InlineTable" + case String: + return "String" + case Bool: + return "Bool" + case Float: + return "Float" + case Integer: + return "Integer" + case LocalDate: + return "LocalDate" + case LocalDateTime: + return "LocalDateTime" + case DateTime: + return "DateTime" + case Time: + return "Time" + } + panic(fmt.Errorf("Kind.String() not implemented for '%d'", k)) +} + +type Root []Node + +// Dot returns a dot representation of the AST for debugging. +func (r Root) Sdot() string { + type edge struct { + from int + to int + } + + var nodes []string + var edges []edge // indexes into nodes + + nodes = append(nodes, "root") + + labelForNode := func(node *Node) string { + return fmt.Sprintf("{%s}", node.Kind) + } + + var processNode func(int, *Node) + processNode = func(parentIdx int, node *Node) { + idx := len(nodes) + label := labelForNode(node) + nodes = append(nodes, label) + edges = append(edges, edge{from: parentIdx, to: idx}) + + for _, c := range node.Children { + processNode(idx, &c) + } + } + + for _, n := range r { + processNode(0, &n) + } + + var b strings.Builder + + b.WriteString("digraph tree {\n") + + for i, label := range nodes { + _, _ = fmt.Fprintf(&b, "\tnode%d [label=\"%s\"];\n", i, label) + } + + b.WriteString("\n") + + for _, e := range edges { + _, _ = fmt.Fprintf(&b, "\tnode%d -> node%d;\n", e.from, e.to) + } + + b.WriteString("}") + + return b.String() +} + +type Node struct { + Kind Kind + Data []byte // Raw bytes from the input + + // Arrays have one child per element in the array. + // InlineTables have one child per key-value pair in the table. + // KeyValues have at least two children. The last one is the value. The + // rest make a potentially dotted key. + Children []Node +} + +var NoNode = Node{} + +// Key returns the nodes making the Key of a KeyValue. +// They are guaranteed to be all be of the Kind Key. A simple key would return +// just one element. +// Panics if not called on a KeyValue node, or if the Children are malformed. +func (n *Node) Key() []Node { + if n.Kind != KeyValue { + panic(fmt.Errorf("Key() should only be called on on a KeyValue, not %s", n.Kind)) + } + if len(n.Children) < 2 { + panic(fmt.Errorf("KeyValue should have at least two children, not %d", len(n.Children))) + } + return n.Children[:len(n.Children)-1] +} + +// Value returns a pointer to the value node of a KeyValue. +// Guaranteed to be non-nil. +// Panics if not called on a KeyValue node, or if the Children are malformed. +func (n *Node) Value() *Node { + if n.Kind != KeyValue { + panic(fmt.Errorf("Key() should only be called on on a KeyValue, not %s", n.Kind)) + } + if len(n.Children) < 2 { + panic(fmt.Errorf("KeyValue should have at least two children, not %d", len(n.Children))) + } + return &n.Children[len(n.Children)-1] +} diff --git a/internal/imported_tests/unmarshal_imported_test.go b/internal/imported_tests/unmarshal_imported_test.go index 7a483241..6a268e76 100644 --- a/internal/imported_tests/unmarshal_imported_test.go +++ b/internal/imported_tests/unmarshal_imported_test.go @@ -1855,16 +1855,19 @@ func TestUnmarshalMixedTypeArray(t *testing.T) { ArrayField []interface{} } - doc := []byte(`ArrayField = [3.14,100,true,"hello world",{Field = "inner1"},[{Field = "inner2"},{Field = "inner3"}]] + //doc := []byte(`ArrayField = [3.14,100,true,"hello world",{Field = "inner1"},[{Field = "inner2"},{Field = "inner3"}]] + //`) + + doc := []byte(`ArrayField = [{Field = "inner1"},[{Field = "inner2"},{Field = "inner3"}]] `) actual := TestStruct{} expected := TestStruct{ ArrayField: []interface{}{ - 3.14, - int64(100), - true, - "hello world", + //3.14, + //int64(100), + //true, + //"hello world", map[string]interface{}{ "Field": "inner1", }, @@ -1874,14 +1877,9 @@ func TestUnmarshalMixedTypeArray(t *testing.T) { }, }, } - - if err := toml.Unmarshal(doc, &actual); err == nil { - if !reflect.DeepEqual(actual, expected) { - t.Errorf("Bad unmarshal: expected %#v, got %#v", expected, actual) - } - } else { - t.Fatal(err) - } + err := toml.Unmarshal(doc, &actual) + require.NoError(t, err) + assert.Equal(t, expected, actual) } func TestUnmarshalArray(t *testing.T) { diff --git a/internal/reflectbuild/reflectbuild.go b/internal/reflectbuild/reflectbuild.go index b79807a3..7bc88259 100644 --- a/internal/reflectbuild/reflectbuild.go +++ b/internal/reflectbuild/reflectbuild.go @@ -199,9 +199,7 @@ func NewBuilder(tag string, v interface{}) (Builder, error) { } func (b *Builder) top() target { - t := b.stack[len(b.stack)-1] - fmt.Println("TOP:", t) - return t + return b.stack[len(b.stack)-1] } func (b *Builder) duplicate() { @@ -213,7 +211,6 @@ func (b *Builder) duplicate() { func (b *Builder) pop() { b.stack = b.stack[:len(b.stack)-1] - fmt.Println("POP: top:", b.stack[len(b.stack)-1]) } func (b *Builder) len() int { @@ -236,7 +233,6 @@ func (b *Builder) Dump() string { } func (b *Builder) replace(v target) { - fmt.Println("REPLACING:", v) b.stack[len(b.stack)-1] = v } @@ -250,10 +246,6 @@ func (b *Builder) DigField(s string) error { v := t.get() for v.Kind() == reflect.Interface || v.Kind() == reflect.Ptr { - if v.Kind() == reflect.Interface { - fmt.Println("STOP") - } - if v.IsNil() { if v.Kind() == reflect.Ptr { thing := reflect.New(v.Type().Elem()) @@ -338,7 +330,20 @@ func (b *Builder) IsSlice() bool { } func (b *Builder) IsSliceOrPtr() bool { - return b.top().get().Kind() == reflect.Slice || (b.top().get().Kind() == reflect.Ptr && b.top().get().Type().Elem().Kind() == reflect.Slice) + t := b.top().get() + if t.Kind() == reflect.Slice { + return true + } + + if t.Kind() == reflect.Ptr && t.Type().Elem().Kind() == reflect.Slice { + return true + } + + if t.Kind() == reflect.Interface && !t.IsNil() && t.Elem().Type().Kind() == reflect.Slice { + return true + } + + return false } // Last moves the cursor to the last value of the current value. @@ -502,14 +507,14 @@ func convert(t reflect.Type, value reflect.Value) (reflect.Value, error) { return result.Elem(), nil } -type IntegerOverflowErr struct { +type IntegerOverflowError struct { value int64 min int64 max int64 kind reflect.Kind } -func (e IntegerOverflowErr) Error() string { +func (e IntegerOverflowError) Error() string { return fmt.Sprintf("integer overflow: cannot store %d in %s [%d, %d]", e.value, e.kind, e.min, e.max) } @@ -524,7 +529,7 @@ func convertInt(t reflect.Type, value reflect.Value) (reflect.Value, error) { switch t.Kind() { case reflect.Int: if x > maxInt || x < minInt { - return value, IntegerOverflowErr{ + return value, IntegerOverflowError{ value: x, min: minInt, max: maxInt, @@ -533,7 +538,7 @@ func convertInt(t reflect.Type, value reflect.Value) (reflect.Value, error) { } case reflect.Int8: if x > math.MaxInt8 || x < math.MinInt8 { - return value, IntegerOverflowErr{ + return value, IntegerOverflowError{ value: x, min: math.MinInt8, max: math.MaxInt8, @@ -542,7 +547,7 @@ func convertInt(t reflect.Type, value reflect.Value) (reflect.Value, error) { } case reflect.Int16: if x > math.MaxInt16 || x < math.MinInt16 { - return value, IntegerOverflowErr{ + return value, IntegerOverflowError{ value: x, min: math.MinInt16, max: math.MaxInt16, @@ -551,7 +556,7 @@ func convertInt(t reflect.Type, value reflect.Value) (reflect.Value, error) { } case reflect.Int32: if x > math.MaxInt32 || x < math.MinInt32 { - return value, IntegerOverflowErr{ + return value, IntegerOverflowError{ value: x, min: math.MinInt32, max: math.MaxInt32, @@ -560,7 +565,7 @@ func convertInt(t reflect.Type, value reflect.Value) (reflect.Value, error) { } case reflect.Int64: if x > math.MaxInt64 || x < math.MinInt64 { - return value, IntegerOverflowErr{ + return value, IntegerOverflowError{ value: x, min: math.MinInt64, max: math.MaxInt64, @@ -575,13 +580,13 @@ func convertInt(t reflect.Type, value reflect.Value) (reflect.Value, error) { } } -type UnsignedIntegerOverflowErr struct { +type UnsignedIntegerOverflowError struct { value uint64 max uint64 kind reflect.Kind } -func (e UnsignedIntegerOverflowErr) Error() string { +func (e UnsignedIntegerOverflowError) Error() string { return fmt.Sprintf("unsigned integer overflow: cannot store %d in %s [max %d]", e.value, e.kind, e.max) } @@ -617,7 +622,7 @@ func convertUintOverflowCheck(t reflect.Kind, x uint64) error { switch t { case reflect.Uint: if x > maxUint { - return UnsignedIntegerOverflowErr{ + return UnsignedIntegerOverflowError{ value: x, max: maxUint, kind: t, @@ -625,7 +630,7 @@ func convertUintOverflowCheck(t reflect.Kind, x uint64) error { } case reflect.Uint8: if x > math.MaxUint8 { - return UnsignedIntegerOverflowErr{ + return UnsignedIntegerOverflowError{ value: x, max: math.MaxUint8, kind: t, @@ -633,7 +638,7 @@ func convertUintOverflowCheck(t reflect.Kind, x uint64) error { } case reflect.Uint16: if x > math.MaxUint16 { - return UnsignedIntegerOverflowErr{ + return UnsignedIntegerOverflowError{ value: x, max: math.MaxUint16, kind: t, @@ -641,7 +646,7 @@ func convertUintOverflowCheck(t reflect.Kind, x uint64) error { } case reflect.Uint32: if x > math.MaxUint32 { - return UnsignedIntegerOverflowErr{ + return UnsignedIntegerOverflowError{ value: x, max: math.MaxUint32, kind: t, @@ -649,7 +654,7 @@ func convertUintOverflowCheck(t reflect.Kind, x uint64) error { } case reflect.Uint64: if x > math.MaxUint64 { - return UnsignedIntegerOverflowErr{ + return UnsignedIntegerOverflowError{ value: x, max: math.MaxUint64, kind: t, @@ -665,7 +670,7 @@ func convertFloat(t reflect.Type, value reflect.Value) (reflect.Value, error) { if t.Kind() == reflect.Float32 { f := value.Float() if f > math.MaxFloat32 { - return value, fmt.Errorf("float overflow: %f does not fit in %s [max %f]") + return value, fmt.Errorf("float overflow: %f does not fit in %s [max %f]", f, t, math.MaxFloat32) } } return value.Convert(t), nil @@ -684,7 +689,7 @@ func (b *Builder) SetString(s string) error { v.Set(reflect.ValueOf(&s)) return nil } - return t.set(reflect.ValueOf(s)) + return t.set(reflect.ValueOf(&s)) } // Set the value at the cursor to the given boolean. @@ -762,6 +767,8 @@ func (b *Builder) EnsureStructOrMap() error { x.Elem().Set(reflect.MakeMap(v.Type())) return t.set(x) } + case reflect.Interface: + // TODO: ? default: return IncorrectKindError{ Reason: "EnsureStructOrMap", @@ -772,19 +779,6 @@ func (b *Builder) EnsureStructOrMap() error { return nil } -func checkKindInt(rt reflect.Type) error { - switch rt.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return nil - } - - return IncorrectKindError{ - Reason: "CheckKindInt", - Actual: rt.Kind(), - Expected: []reflect.Kind{reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64}, - } -} - func checkKindFloat(rt reflect.Type) error { switch rt.Kind() { case reflect.Float32, reflect.Float64: diff --git a/internal/unmarshaler/parser.go b/internal/unmarshaler/parser.go new file mode 100644 index 00000000..9a1fb8df --- /dev/null +++ b/internal/unmarshaler/parser.go @@ -0,0 +1,1192 @@ +package unmarshaler + +import ( + "bytes" + "encoding/hex" + "errors" + "fmt" + "math" + "strconv" + "strings" + "time" + + "github.com/pelletier/go-toml/v2" + + "github.com/pelletier/go-toml/v2/internal/ast" +) + +type parser struct { + tree ast.Root +} + +func (p *parser) parse(b []byte) error { + b, err := p.parseExpression(b) + if err != nil { + return err + } + for len(b) > 0 { + b, err = p.parseNewline(b) + if err != nil { + return err + } + + b, err = p.parseExpression(b) + if err != nil { + return err + } + } + return nil +} + +func (p *parser) parseNewline(b []byte) ([]byte, error) { + if b[0] == '\n' { + return b[1:], nil + } + if b[0] == '\r' { + _, rest, err := scanWindowsNewline(b) + return rest, err + } + return nil, fmt.Errorf("expected newline but got %#U", b[0]) +} + +func (p *parser) parseExpression(b []byte) ([]byte, error) { + //expression = ws [ comment ] + //expression =/ ws keyval ws [ comment ] + //expression =/ ws table ws [ comment ] + + b = p.parseWhitespace(b) + + if len(b) == 0 { + return b, nil + } + + if b[0] == '#' { + _, rest, err := scanComment(b) + return rest, err + } + if b[0] == '\n' || b[0] == '\r' { + return b, nil + } + + var err error + var node ast.Node + if b[0] == '[' { + b, err = p.parseTable(b) + } else { + node, b, err = p.parseKeyval(b) + } + if err != nil { + return nil, err + } + + b = p.parseWhitespace(b) + + if len(b) > 0 && b[0] == '#' { + _, rest, err := scanComment(b) + return rest, err + } + + p.tree = append(p.tree, node) + + return b, nil +} + +func (p *parser) parseTable(b []byte) ([]byte, error) { + //table = std-table / array-table + if len(b) > 1 && b[1] == '[' { + return p.parseArrayTable(b) + } + return p.parseStdTable(b) +} + +func (p *parser) parseArrayTable(b []byte) ([]byte, error) { + //array-table = array-table-open key array-table-close + //array-table-open = %x5B.5B ws ; [[ Double left square bracket + //array-table-close = ws %x5D.5D ; ]] Double right square bracket + + // TODO + //b = b[2:] + //b = p.parseWhitespace(b) + //b, err := p.parseKey(b) + //if err != nil { + // return nil, err + //} + //b = p.parseWhitespace(b) + //b, err = expect(']', b) + //if err != nil { + // return nil, err + //} + //return expect(']', b) + + return nil, nil +} + +func (p *parser) parseStdTable(b []byte) ([]byte, error) { + //std-table = std-table-open key std-table-close + //std-table-open = %x5B ws ; [ Left square bracket + //std-table-close = ws %x5D ; ] Right square bracket + + // TODO + //b = b[1:] + //b = p.parseWhitespace(b) + //b, err := p.parseKey(b) + //if err != nil { + // return nil, err + //} + //b = p.parseWhitespace(b) + // + //return expect(']', b) + + return nil, nil +} + +func (p *parser) parseKeyval(b []byte) (ast.Node, []byte, error) { + //keyval = key keyval-sep val + + node := ast.Node{ + Kind: ast.KeyValue, + } + + key, b, err := p.parseKey(b) + if err != nil { + return ast.NoNode, nil, err + } + node.Children = append(node.Children, key...) + + //keyval-sep = ws %x3D ws ; = + + b = p.parseWhitespace(b) + b, err = expect('=', b) + if err != nil { + return ast.NoNode, nil, err + } + b = p.parseWhitespace(b) + + valNode, b, err := p.parseVal(b) + if err == nil { + node.Children = append(node.Children, valNode) + } + return node, b, err +} + +func (p *parser) parseVal(b []byte) (ast.Node, []byte, error) { + // val = string / boolean / array / inline-table / date-time / float / integer + if len(b) == 0 { + return ast.NoNode, nil, fmt.Errorf("expected value, not eof") + } + + node := ast.Node{} + var err error + c := b[0] + + switch c { + // strings + case '"': + var v []byte + if scanFollowsMultilineBasicStringDelimiter(b) { + v, b, err = p.parseMultilineBasicString(b) + } else { + v, b, err = p.parseBasicString(b) + } + if err == nil { + node.Kind = ast.String + node.Data = v + } + return node, b, err + case '\'': + var v []byte + if scanFollowsMultilineLiteralStringDelimiter(b) { + v, b, err = p.parseMultilineLiteralString(b) + } else { + v, b, err = p.parseLiteralString(b) + } + if err == nil { + // TODO + v = v + } + return node, b, err + case 't': + if !scanFollowsTrue(b) { + return node, nil, fmt.Errorf("expected 'true'") + } + // TODO + return node, b[4:], nil + case 'f': + if !scanFollowsFalse(b) { + return node, nil, fmt.Errorf("expected 'false'") + } + // TODO + return node, b[5:], nil + case '[': + // TODO + //return p.parseValArray(b) + case '{': + // TODO + //return p.parseInlineTable(b) + default: + // TODO + //return p.parseIntOrFloatOrDateTime(b) + } + panic("parseVal not finished yet") + return ast.Node{}, nil, nil +} + +func (p *parser) parseLiteralString(b []byte) ([]byte, []byte, error) { + v, rest, err := scanLiteralString(b) + if err != nil { + return nil, nil, err + } + return v[1 : len(v)-1], rest, nil +} + +func (p *parser) parseInlineTable(b []byte) ([]byte, error) { + //inline-table = inline-table-open [ inline-table-keyvals ] inline-table-close + //inline-table-open = %x7B ws ; { + //inline-table-close = ws %x7D ; } + //inline-table-sep = ws %x2C ws ; , Comma + //inline-table-keyvals = keyval [ inline-table-sep inline-table-keyvals ] + + // TODO + //b = b[1:] + // + //first := true + //var err error + //for len(b) > 0 { + // b = p.parseWhitespace(b) + // if b[0] == '}' { + // break + // } + // + // if !first { + // b, err = expect(',', b) + // if err != nil { + // return nil, err + // } + // b = p.parseWhitespace(b) + // } + // b, err = p.parseKeyval(b) + // if err != nil { + // return nil, err + // } + // + // first = false + //} + + return expect('}', b) +} + +func (p *parser) parseValArray(b []byte) ([]byte, error) { + //array = array-open [ array-values ] ws-comment-newline array-close + //array-open = %x5B ; [ + //array-close = %x5D ; ] + //array-values = ws-comment-newline val ws-comment-newline array-sep array-values + //array-values =/ ws-comment-newline val ws-comment-newline [ array-sep ] + //array-sep = %x2C ; , Comma + //ws-comment-newline = *( wschar / [ comment ] newline ) + + // TODO + //b = b[1:] + // + //first := true + //var err error + //for len(b) > 0 { + // b, err = p.parseOptionalWhitespaceCommentNewline(b) + // if err != nil { + // return nil, err + // } + // + // if len(b) == 0 { + // return nil, unexpectedCharacter{b: b} + // } + // + // if b[0] == ']' { + // break + // } + // if b[0] == ',' { + // if first { + // return nil, fmt.Errorf("array cannot start with comma") + // } + // b = b[1:] + // b, err = p.parseOptionalWhitespaceCommentNewline(b) + // if err != nil { + // return nil, err + // } + // } + // + // b, err = p.parseVal(b) + // if err != nil { + // return nil, err + // } + // b, err = p.parseOptionalWhitespaceCommentNewline(b) + // if err != nil { + // return nil, err + // } + // first = false + //} + + return expect(']', b) +} + +func (p *parser) parseOptionalWhitespaceCommentNewline(b []byte) ([]byte, error) { + var err error + b = p.parseWhitespace(b) + if len(b) > 0 && b[0] == '#' { + _, b, err = scanComment(b) + if err != nil { + return nil, err + } + } + if len(b) > 0 && (b[0] == '\n' || b[0] == '\r') { + b, err = p.parseNewline(b) + if err != nil { + return nil, err + } + } + return b, nil +} + +func (p *parser) parseMultilineLiteralString(b []byte) ([]byte, []byte, error) { + token, rest, err := scanMultilineLiteralString(b) + if err != nil { + return nil, nil, err + } + + i := 3 + + // skip the immediate new line + if token[i] == '\n' { + i++ + } else if token[i] == '\r' && token[i+1] == '\n' { + i += 2 + } + + return token[i : len(b)-3], rest, err +} + +func (p *parser) parseMultilineBasicString(b []byte) ([]byte, []byte, error) { + //ml-basic-string = ml-basic-string-delim [ newline ] ml-basic-body + //ml-basic-string-delim + //ml-basic-string-delim = 3quotation-mark + //ml-basic-body = *mlb-content *( mlb-quotes 1*mlb-content ) [ mlb-quotes ] + // + //mlb-content = mlb-char / newline / mlb-escaped-nl + //mlb-char = mlb-unescaped / escaped + //mlb-quotes = 1*2quotation-mark + //mlb-unescaped = wschar / %x21 / %x23-5B / %x5D-7E / non-ascii + //mlb-escaped-nl = escape ws newline *( wschar / newline ) + + token, rest, err := scanMultilineBasicString(b) + if err != nil { + return nil, nil, err + } + var builder bytes.Buffer + + i := 3 + + // skip the immediate new line + if token[i] == '\n' { + i++ + } else if token[i] == '\r' && token[i+1] == '\n' { + i += 2 + } + + // The scanner ensures that the token starts and ends with quotes and that + // escapes are balanced. + for ; i < len(token)-3; i++ { + c := token[i] + if c == '\\' { + // When the last non-whitespace character on a line is an unescaped \, + // it will be trimmed along with all whitespace (including newlines) up + // to the next non-whitespace character or closing delimiter. + if token[i+1] == '\n' || (token[i+1] == '\r' && token[i+2] == '\n') { + i++ // skip the \ + for ; i < len(token)-3; i++ { + c := token[i] + if !(c == '\n' || c == '\r' || c == ' ' || c == '\t') { + break + } + } + continue + } + + // handle escaping + i++ + c = token[i] + switch c { + case '"', '\\': + builder.WriteByte(c) + case 'b': + builder.WriteByte('\b') + case 'f': + builder.WriteByte('\f') + case 'n': + builder.WriteByte('\n') + case 'r': + builder.WriteByte('\r') + case 't': + builder.WriteByte('\t') + case 'u': + x, err := hexToString(token[i+3:len(token)-3], 4) + if err != nil { + return nil, nil, err + } + builder.WriteString(x) + i += 4 + case 'U': + x, err := hexToString(token[i+3:len(token)-3], 8) + if err != nil { + return nil, nil, err + } + builder.WriteString(x) + i += 8 + default: + return nil, nil, fmt.Errorf("invalid escaped character: %#U", c) + } + } else { + builder.WriteByte(c) + } + } + + return builder.Bytes(), rest, nil +} + +func (p *parser) parseKey(b []byte) ([]ast.Node, []byte, error) { + //key = simple-key / dotted-key + //simple-key = quoted-key / unquoted-key + // + //unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _ + //quoted-key = basic-string / literal-string + //dotted-key = simple-key 1*( dot-sep simple-key ) + // + //dot-sep = ws %x2E ws ; . Period + + var nodes []ast.Node + + key, b, err := p.parseSimpleKey(b) + if err != nil { + return nodes, nil, err + } + + nodes = append(nodes, ast.Node{ + Kind: ast.Key, + Data: key, + }) + + for { + b = p.parseWhitespace(b) + if len(b) > 0 && b[0] == '.' { + b, err = expect('.', b) + if err != nil { + return nodes, nil, err + } + b = p.parseWhitespace(b) + key, b, err = p.parseSimpleKey(b) + if err != nil { + return nodes, nil, err + } + nodes = append(nodes, ast.Node{ + Kind: ast.Key, + Data: key, + }) + } else { + break + } + } + + return nodes, b, nil +} + +func (p *parser) parseSimpleKey(b []byte) (key, rest []byte, err error) { + //simple-key = quoted-key / unquoted-key + //unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _ + //quoted-key = basic-string / literal-string + + if len(b) == 0 { + return nil, nil, unexpectedCharacter{b: b} + } + + if b[0] == '\'' { + key, rest, err = scanLiteralString(b) + } else if b[0] == '"' { + key, rest, err = p.parseBasicString(b) + } else if isUnquotedKeyChar(b[0]) { + key, rest, err = scanUnquotedKey(b) + } else { + err = unexpectedCharacter{b: b} + } + return +} + +func (p *parser) parseBasicString(b []byte) ([]byte, []byte, error) { + //basic-string = quotation-mark *basic-char quotation-mark + //quotation-mark = %x22 ; " + //basic-char = basic-unescaped / escaped + //basic-unescaped = wschar / %x21 / %x23-5B / %x5D-7E / non-ascii + //escaped = escape escape-seq-char + //escape-seq-char = %x22 ; " quotation mark U+0022 + //escape-seq-char =/ %x5C ; \ reverse solidus U+005C + //escape-seq-char =/ %x62 ; b backspace U+0008 + //escape-seq-char =/ %x66 ; f form feed U+000C + //escape-seq-char =/ %x6E ; n line feed U+000A + //escape-seq-char =/ %x72 ; r carriage return U+000D + //escape-seq-char =/ %x74 ; t tab U+0009 + //escape-seq-char =/ %x75 4HEXDIG ; uXXXX U+XXXX + //escape-seq-char =/ %x55 8HEXDIG ; UXXXXXXXX U+XXXXXXXX + + token, rest, err := scanBasicString(b) + if err != nil { + return nil, nil, err + } + var builder bytes.Buffer + + // The scanner ensures that the token starts and ends with quotes and that + // escapes are balanced. + for i := 1; i < len(token)-1; i++ { + c := token[i] + if c == '\\' { + i++ + c = token[i] + switch c { + case '"', '\\': + builder.WriteByte(c) + case 'b': + builder.WriteByte('\b') + case 'f': + builder.WriteByte('\f') + case 'n': + builder.WriteByte('\n') + case 'r': + builder.WriteByte('\r') + case 't': + builder.WriteByte('\t') + case 'u': + x, err := hexToString(token[i+1:len(token)-1], 4) + if err != nil { + return nil, nil, err + } + builder.WriteString(x) + i += 4 + case 'U': + x, err := hexToString(token[i+1:len(token)-1], 8) + if err != nil { + return nil, nil, err + } + builder.WriteString(x) + i += 8 + default: + return nil, nil, fmt.Errorf("invalid escaped character: %#U", c) + } + } else { + builder.WriteByte(c) + } + } + + return builder.Bytes(), rest, nil +} + +func hexToString(b []byte, length int) (string, error) { + if len(b) < length { + return "", fmt.Errorf("unicode point needs %d hex characters", length) + } + // TODO: slow + b, err := hex.DecodeString(string(b[:length])) + if err != nil { + return "", err + } + return string(b), nil +} + +func (p *parser) parseWhitespace(b []byte) []byte { + //ws = *wschar + //wschar = %x20 ; Space + //wschar =/ %x09 ; Horizontal tab + + _, rest := scanWhitespace(b) + return rest +} + +func (p *parser) parseIntOrFloatOrDateTime(b []byte) ([]byte, error) { + switch b[0] { + case 'i': + if !scanFollowsInf(b) { + return nil, fmt.Errorf("expected 'inf'") + } + //p.builder.FloatValue(math.Inf(1)) + // TODO + return b[3:], nil + case 'n': + if !scanFollowsNan(b) { + return nil, fmt.Errorf("expected 'nan'") + } + //p.builder.FloatValue(math.NaN()) + // TODO + return b[3:], nil + case '+', '-': + return p.parseIntOrFloat(b) + } + + if len(b) < 3 { + return p.parseIntOrFloat(b) + } + s := 5 + if len(b) < s { + s = len(b) + } + for idx, c := range b[:s] { + if c >= '0' && c <= '9' { + continue + } + if idx == 2 && c == ':' { + return p.parseDateTime(b) + } + if idx == 4 && c == '-' { + return p.parseDateTime(b) + } + } + return p.parseIntOrFloat(b) +} + +func digitsToInt(b []byte) int { + x := 0 + for _, d := range b { + x *= 10 + x += int(d - '0') + } + return x +} + +func (p *parser) parseDateTime(b []byte) ([]byte, error) { + // we know the first 2 ar digits. + if b[2] == ':' { + return p.parseTime(b) + } + // This state accepts an offset date-time, a local date-time, or a local date. + // + // v--- cursor + // 1979-05-27T07:32:00Z + // 1979-05-27T00:32:00-07:00 + // 1979-05-27T00:32:00.999999-07:00 + // 1979-05-27 07:32:00Z + // 1979-05-27 00:32:00-07:00 + // 1979-05-27 00:32:00.999999-07:00 + // 1979-05-27T07:32:00 + // 1979-05-27T00:32:00.999999 + // 1979-05-27 07:32:00 + // 1979-05-27 00:32:00.999999 + // 1979-05-27 + + // date + + idx := 4 + + localDate := toml.LocalDate{ + Year: digitsToInt(b[:idx]), + } + + for i := 0; i < 2; i++ { + // month + idx++ + if !isDigit(b[idx]) { + return nil, fmt.Errorf("invalid month digit in date: %c", b[idx]) + } + localDate.Month *= 10 + localDate.Month += time.Month(b[idx] - '0') + } + + idx++ + if b[idx] != '-' { + return nil, fmt.Errorf("expected - to separate month of a date, not %c", b[idx]) + } + + for i := 0; i < 2; i++ { + // day + idx++ + if !isDigit(b[idx]) { + return nil, fmt.Errorf("invalid day digit in date: %c", b[idx]) + } + localDate.Day *= 10 + localDate.Day += int(b[idx] - '0') + } + + idx++ + + if idx >= len(b) { + //p.builder.LocalDateValue(localDate) + // TODO + return nil, nil + } else if b[idx] != ' ' && b[idx] != 'T' { + //p.builder.LocalDateValue(localDate) + // TODO + return b[idx:], nil + } + + // check if there is a chance there is anything useful after + if b[idx] == ' ' && (((idx + 2) >= len(b)) || !isDigit(b[idx+1]) || !isDigit(b[idx+2])) { + //p.builder.LocalDateValue(localDate) + // TODO + return b[idx:], nil + } + + //idx++ // skip the T or ' ' + + // time + localTime := toml.LocalTime{} + + for i := 0; i < 2; i++ { + idx++ + if !isDigit(b[idx]) { + return nil, fmt.Errorf("invalid hour digit in time: %c", b[idx]) + } + localTime.Hour *= 10 + localTime.Hour += int(b[idx] - '0') + } + + idx++ + if b[idx] != ':' { + return nil, fmt.Errorf("time hour/minute separator should be :, not %c", b[idx]) + } + + for i := 0; i < 2; i++ { + idx++ + if !isDigit(b[idx]) { + return nil, fmt.Errorf("invalid minute digit in time: %c", b[idx]) + } + localTime.Minute *= 10 + localTime.Minute += int(b[idx] - '0') + } + + idx++ + if b[idx] != ':' { + return nil, fmt.Errorf("time minute/second separator should be :, not %c", b[idx]) + } + + for i := 0; i < 2; i++ { + idx++ + if !isDigit(b[idx]) { + return nil, fmt.Errorf("invalid second digit in time: %c", b[idx]) + } + localTime.Second *= 10 + localTime.Second += int(b[idx] - '0') + } + + idx++ + if idx < len(b) && b[idx] == '.' { + idx++ + idx++ + if !isDigit(b[idx]) { + return nil, fmt.Errorf("expected at least one digit in time's fraction, not %c", b[idx]) + } + + for { + localTime.Nanosecond *= 10 + localTime.Nanosecond += int(b[idx] - '0') + idx++ + + if idx < len(b) { + break + } + + if !isDigit(b[idx]) { + break + } + } + } + + if idx >= len(b) || (b[idx] != 'Z' && b[idx] != '+' && b[idx] != '-') { + dt := toml.LocalDateTime{ + Date: localDate, + Time: localTime, + } + //p.builder.LocalDateTimeValue(dt) + // TODO + dt = dt + return b[idx:], nil + } + + loc := time.UTC + + if b[idx] == 'Z' { + idx++ + } else { + start := idx + sign := 1 + if b[idx] == '-' { + sign = -1 + } + + hours := 0 + for i := 0; i < 2; i++ { + idx++ + if !isDigit(b[idx]) { + return nil, fmt.Errorf("invalid hour digit in time offset: %c", b[idx]) + } + hours *= 10 + hours += int(b[idx] - '0') + } + offset := hours * 60 * 60 + + idx++ + if b[idx] != ':' { + return nil, fmt.Errorf("time offset hour/minute separator should be :, not %c", b[idx]) + } + + minutes := 0 + for i := 0; i < 2; i++ { + idx++ + if !isDigit(b[idx]) { + return nil, fmt.Errorf("invalid minute digit in time offset: %c", b[idx]) + } + minutes *= 10 + minutes += int(b[idx] - '0') + } + offset += minutes * 60 + offset *= sign + idx++ + loc = time.FixedZone(string(b[start:idx]), offset) + } + dt := time.Date(localDate.Year, localDate.Month, localDate.Day, localTime.Hour, localTime.Minute, localTime.Second, localTime.Nanosecond, loc) + //p.builder.DateTimeValue(dt) + // TODO + dt = dt + return b[idx:], nil +} + +func (p *parser) parseTime(b []byte) ([]byte, error) { + localTime := toml.LocalTime{} + + idx := 0 + + for i := 0; i < 2; i++ { + idx++ + if !isDigit(b[idx]) { + return nil, fmt.Errorf("invalid hour digit in time: %c", b[idx]) + } + localTime.Hour *= 10 + localTime.Hour += int(b[idx] - '0') + } + + idx++ + if b[idx] != ':' { + return nil, fmt.Errorf("time hour/minute separator should be :, not %c", b[idx]) + } + + for i := 0; i < 2; i++ { + idx++ + if !isDigit(b[idx]) { + return nil, fmt.Errorf("invalid minute digit in time: %c", b[idx]) + } + localTime.Minute *= 10 + localTime.Minute += int(b[idx] - '0') + } + + idx++ + if b[idx] != ':' { + return nil, fmt.Errorf("time minute/second separator should be :, not %c", b[idx]) + } + + for i := 0; i < 2; i++ { + idx++ + if !isDigit(b[idx]) { + return nil, fmt.Errorf("invalid second digit in time: %c", b[idx]) + } + localTime.Second *= 10 + localTime.Second += int(b[idx] - '0') + } + + idx++ + if idx < len(b) && b[idx] == '.' { + idx++ + idx++ + if !isDigit(b[idx]) { + return nil, fmt.Errorf("expected at least one digit in time's fraction, not %c", b[idx]) + } + + for { + localTime.Nanosecond *= 10 + localTime.Nanosecond += int(b[idx] - '0') + idx++ + if !isDigit(b[idx]) { + break + } + } + } + + //p.builder.LocalTimeValue(localTime) + // TODO + return b[idx:], nil +} + +func (p *parser) parseIntOrFloat(b []byte) ([]byte, error) { + i := 0 + r := b[0] + if r == '0' { + if len(b) >= 2 { + var isValidRune validRuneFn + var parseFn func([]byte) (int64, error) + switch b[1] { + case 'x': + isValidRune = isValidHexRune + parseFn = parseIntHex + case 'o': + isValidRune = isValidOctalRune + parseFn = parseIntOct + case 'b': + isValidRune = isValidBinaryRune + parseFn = parseIntBin + default: + if b[1] >= 'a' && b[1] <= 'z' || b[1] >= 'A' && b[1] <= 'Z' { + return nil, fmt.Errorf("unknown number base: %s. possible options are x (hex) o (octal) b (binary)", string(b[1])) + } + parseFn = parseIntDec + } + + if isValidRune != nil { + i = 2 + digitSeen := false + for { + if !isValidRune(b[i]) { + break + } + digitSeen = true + i++ + } + + if !digitSeen { + return nil, fmt.Errorf("number needs at least one digit") + } + + v, err := parseFn(b[:i]) + if err != nil { + return nil, err + } + //p.builder.IntValue(v) + // TODO + v = v + return b[i:], nil + } + } + } + + if r == '+' || r == '-' { + b = b[1:] + if scanFollowsInf(b) { + if r == '+' { + //p.builder.FloatValue(plusInf) + // TODO + } else { + //p.builder.FloatValue(minusInf) + // TODO + } + return b, nil + } + if scanFollowsNan(b) { + //p.builder.FloatValue(nan) + // TODO + return b, nil + } + } + + pointSeen := false + expSeen := false + digitSeen := false + for i < len(b) { + next := b[i] + if next == '.' { + if pointSeen { + return nil, fmt.Errorf("cannot have two dots in one float") + } + i++ + if i < len(b) && !isDigit(b[i]) { + return nil, fmt.Errorf("float cannot end with a dot") + } + pointSeen = true + } else if next == 'e' || next == 'E' { + expSeen = true + i++ + if i >= len(b) { + break + } + if b[i] == '+' || b[i] == '-' { + i++ + } + } else if isDigit(next) { + digitSeen = true + i++ + } else if next == '_' { + i++ + } else { + break + } + if pointSeen && !digitSeen { + return nil, fmt.Errorf("cannot start float with a dot") + } + } + + if !digitSeen { + return nil, fmt.Errorf("no digit in that number") + } + if pointSeen || expSeen { + f, err := parseFloat(b[:i]) + if err != nil { + return nil, err + } + //p.builder.FloatValue(f) + // TODO + f = f + } else { + v, err := parseIntDec(b[:i]) + if err != nil { + return nil, err + } + //p.builder.IntValue(v) + // TODO + v = v + } + return b[i:], nil +} + +func parseFloat(b []byte) (float64, error) { + // TODO: inefficient + tok := string(b) + err := numberContainsInvalidUnderscore(tok) + if err != nil { + return 0, err + } + cleanedVal := cleanupNumberToken(tok) + return strconv.ParseFloat(cleanedVal, 64) +} + +func parseIntHex(b []byte) (int64, error) { + tok := string(b) + cleanedVal := cleanupNumberToken(tok) + err := hexNumberContainsInvalidUnderscore(cleanedVal) + if err != nil { + return 0, nil + } + return strconv.ParseInt(cleanedVal[2:], 16, 64) +} + +func parseIntOct(b []byte) (int64, error) { + tok := string(b) + cleanedVal := cleanupNumberToken(tok) + err := numberContainsInvalidUnderscore(cleanedVal) + if err != nil { + return 0, err + } + return strconv.ParseInt(cleanedVal[2:], 8, 64) +} + +func parseIntBin(b []byte) (int64, error) { + tok := string(b) + cleanedVal := cleanupNumberToken(tok) + err := numberContainsInvalidUnderscore(cleanedVal) + if err != nil { + return 0, err + } + return strconv.ParseInt(cleanedVal[2:], 2, 64) +} + +func parseIntDec(b []byte) (int64, error) { + tok := string(b) + cleanedVal := cleanupNumberToken(tok) + err := numberContainsInvalidUnderscore(cleanedVal) + if err != nil { + return 0, err + } + return strconv.ParseInt(cleanedVal, 10, 64) +} + +func numberContainsInvalidUnderscore(value string) error { + // For large numbers, you may use underscores between digits to enhance + // readability. Each underscore must be surrounded by at least one digit on + // each side. + + hasBefore := false + for idx, r := range value { + if r == '_' { + if !hasBefore || idx+1 >= len(value) { + // can't end with an underscore + return errInvalidUnderscore + } + } + hasBefore = isDigitRune(r) + } + return nil +} + +func hexNumberContainsInvalidUnderscore(value string) error { + hasBefore := false + for idx, r := range value { + if r == '_' { + if !hasBefore || idx+1 >= len(value) { + // can't end with an underscore + return errInvalidUnderscoreHex + } + } + hasBefore = isHexDigit(r) + } + return nil +} + +func cleanupNumberToken(value string) string { + cleanedVal := strings.Replace(value, "_", "", -1) + return cleanedVal +} + +func isDigit(r byte) bool { + return r >= '0' && r <= '9' +} + +func isDigitRune(r rune) bool { + return r >= '0' && r <= '9' +} + +var plusInf = math.Inf(1) +var minusInf = math.Inf(-1) +var nan = math.NaN() + +type validRuneFn func(r byte) bool + +func isValidHexRune(r byte) bool { + return r >= 'a' && r <= 'f' || + r >= 'A' && r <= 'F' || + r >= '0' && r <= '9' || + r == '_' +} + +func isHexDigit(r rune) bool { + return isDigitRune(r) || + (r >= 'a' && r <= 'f') || + (r >= 'A' && r <= 'F') +} + +func isValidOctalRune(r byte) bool { + return r >= '0' && r <= '7' || r == '_' +} + +func isValidBinaryRune(r byte) bool { + return r == '0' || r == '1' || r == '_' +} + +func expect(x byte, b []byte) ([]byte, error) { + if len(b) == 0 || b[0] != x { + return nil, unexpectedCharacter{r: x, b: b} + } + return b[1:], nil +} + +type unexpectedCharacter struct { + r byte + b []byte +} + +func (u unexpectedCharacter) Error() string { + if len(u.b) == 0 { + return fmt.Sprintf("expected %#U, not EOF", u.r) + + } + return fmt.Sprintf("expected %#U, not %#U", u.r, u.b[0]) +} + +var errInvalidUnderscore = errors.New("invalid use of _ in number") +var errInvalidUnderscoreHex = errors.New("invalid use of _ in hex number") diff --git a/internal/unmarshaler/parser_test.go b/internal/unmarshaler/parser_test.go new file mode 100644 index 00000000..f4be26b0 --- /dev/null +++ b/internal/unmarshaler/parser_test.go @@ -0,0 +1,50 @@ +package unmarshaler + +import ( + "testing" + + "github.com/pelletier/go-toml/v2/internal/ast" + "github.com/stretchr/testify/require" +) + +func TestParser_Simple(t *testing.T) { + examples := []struct { + desc string + input string + ast ast.Root + err bool + }{ + { + desc: "simple string assignment", + input: `A = "hello"`, + ast: ast.Root{ + ast.Node{ + Kind: ast.KeyValue, + Children: []ast.Node{ + { + Kind: ast.Key, + Data: []byte(`A`), + }, + { + Kind: ast.String, + Data: []byte(`hello`), + }, + }, + }, + }, + }, + } + + for _, e := range examples { + t.Run(e.desc, func(t *testing.T) { + p := parser{} + err := p.parse([]byte(e.input)) + if e.err { + require.Error(t, err) + } else { + require.NoError(t, err) + require.Equal(t, e.ast, p.tree) + } + }) + } +} diff --git a/internal/unmarshaler/scanner.go b/internal/unmarshaler/scanner.go new file mode 100644 index 00000000..a13e6b68 --- /dev/null +++ b/internal/unmarshaler/scanner.go @@ -0,0 +1,168 @@ +package unmarshaler + +import "fmt" + +func scanFollows(pattern []byte) func(b []byte) bool { + return func(b []byte) bool { + if len(b) < len(pattern) { + return false + } + for i, c := range pattern { + if b[i] != c { + return false + } + } + return true + } +} + +var scanFollowsMultilineBasicStringDelimiter = scanFollows([]byte{'"', '"', '"'}) +var scanFollowsMultilineLiteralStringDelimiter = scanFollows([]byte{'\'', '\'', '\''}) +var scanFollowsTrue = scanFollows([]byte{'t', 'r', 'u', 'e'}) +var scanFollowsFalse = scanFollows([]byte{'f', 'a', 'l', 's', 'e'}) +var scanFollowsInf = scanFollows([]byte{'i', 'n', 'f'}) +var scanFollowsNan = scanFollows([]byte{'n', 'a', 'n'}) + +func scanUnquotedKey(b []byte) ([]byte, []byte, error) { + //unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _ + for i := 0; i < len(b); i++ { + if !isUnquotedKeyChar(b[i]) { + return b[:i], b[i:], nil + } + } + return b, nil, nil +} + +func isUnquotedKeyChar(r byte) bool { + return (r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '-' || r == '_' +} + +func scanLiteralString(b []byte) ([]byte, []byte, error) { + //literal-string = apostrophe *literal-char apostrophe + //apostrophe = %x27 ; ' apostrophe + //literal-char = %x09 / %x20-26 / %x28-7E / non-ascii + for i := 1; i < len(b); i++ { + switch b[i] { + case '\'': + return b[:i+1], b[i+1:], nil + case '\n': + return nil, nil, fmt.Errorf("literal strings cannot have new lines") + } + } + return nil, nil, fmt.Errorf("unterminated literal string") +} + +func scanMultilineLiteralString(b []byte) ([]byte, []byte, error) { + //ml-literal-string = ml-literal-string-delim [ newline ] ml-literal-body + //ml-literal-string-delim + //ml-literal-string-delim = 3apostrophe + //ml-literal-body = *mll-content *( mll-quotes 1*mll-content ) [ mll-quotes ] + // + //mll-content = mll-char / newline + //mll-char = %x09 / %x20-26 / %x28-7E / non-ascii + //mll-quotes = 1*2apostrophe + for i := 3; i < len(b); i++ { + switch b[i] { + case '\'': + if scanFollowsMultilineLiteralStringDelimiter(b[i:]) { + return b[:i+3], b[:i+3], nil + } + } + } + + return nil, nil, fmt.Errorf(`multiline literal string not terminated by '''`) +} + +func scanWindowsNewline(b []byte) ([]byte, []byte, error) { + if len(b) < 2 { + return nil, nil, fmt.Errorf(`windows new line missing \n`) + } + if b[1] != '\n' { + return nil, nil, fmt.Errorf(`windows new line should be \r\n`) + } + return b[:2], b[2:], nil +} + +func scanWhitespace(b []byte) ([]byte, []byte) { + for i := 0; i < len(b); i++ { + switch b[i] { + case ' ', '\t': + continue + default: + return b[:i], b[i:] + } + } + return b, nil +} + +func scanComment(b []byte) ([]byte, []byte, error) { + //;; Comment + // + //comment-start-symbol = %x23 ; # + //non-ascii = %x80-D7FF / %xE000-10FFFF + //non-eol = %x09 / %x20-7F / non-ascii + // + //comment = comment-start-symbol *non-eol + + for i := 1; i < len(b); i++ { + switch b[i] { + case '\n': + return b[:i], b[i:], nil + } + } + return b, nil, nil +} + +// TODO perform validation on the string? +func scanBasicString(b []byte) ([]byte, []byte, error) { + //basic-string = quotation-mark *basic-char quotation-mark + //quotation-mark = %x22 ; " + //basic-char = basic-unescaped / escaped + //basic-unescaped = wschar / %x21 / %x23-5B / %x5D-7E / non-ascii + //escaped = escape escape-seq-char + for i := 1; i < len(b); i++ { + switch b[i] { + case '"': + return b[:i+1], b[i+1:], nil + case '\n': + return nil, nil, fmt.Errorf("basic strings cannot have new lines") + case '\\': + if len(b) < i+2 { + return nil, nil, fmt.Errorf("need a character after \\") + } + i++ // skip the next character + } + } + + return nil, nil, fmt.Errorf(`basic string not terminated by "`) +} + +// TODO perform validation on the string? +func scanMultilineBasicString(b []byte) ([]byte, []byte, error) { + //ml-basic-string = ml-basic-string-delim [ newline ] ml-basic-body + //ml-basic-string-delim + //ml-basic-string-delim = 3quotation-mark + //ml-basic-body = *mlb-content *( mlb-quotes 1*mlb-content ) [ mlb-quotes ] + // + //mlb-content = mlb-char / newline / mlb-escaped-nl + //mlb-char = mlb-unescaped / escaped + //mlb-quotes = 1*2quotation-mark + //mlb-unescaped = wschar / %x21 / %x23-5B / %x5D-7E / non-ascii + //mlb-escaped-nl = escape ws newline *( wschar / newline ) + + for i := 3; i < len(b); i++ { + switch b[i] { + case '"': + if scanFollowsMultilineBasicStringDelimiter(b[i:]) { + return b[:i+3], b[i+3:], nil + } + case '\\': + if len(b) < i+2 { + return nil, nil, fmt.Errorf("need a character after \\") + } + i++ // skip the next character + } + } + + return nil, nil, fmt.Errorf(`multiline basic string not terminated by """`) +} diff --git a/internal/unmarshaler/targets.go b/internal/unmarshaler/targets.go new file mode 100644 index 00000000..1c9fd259 --- /dev/null +++ b/internal/unmarshaler/targets.go @@ -0,0 +1,94 @@ +package unmarshaler + +import ( + "fmt" + "reflect" +) + +type target interface { + // Ensure the target's reflect value is not nil. + ensure() + + // Store a string at the target. + setString(v string) error + + // Appends an arbitrary value to the container. + pushValue(v reflect.Value) error + + // Dereferences the target. + get() reflect.Value +} + +// struct target just contain the reflect.Value of the target field. +type structTarget reflect.Value + +func (t structTarget) get() reflect.Value { + return reflect.Value(t) +} + +func (t structTarget) ensure() { + f := t.get() + if !f.IsNil() { + return + } + + switch f.Kind() { + case reflect.Slice: + f.Set(reflect.MakeSlice(f.Type(), 0, 0)) + default: + panic(fmt.Errorf("don't know how to ensure %s", f.Kind())) + } +} + +func (t structTarget) setString(v string) error { + f := t.get() + if f.Kind() != reflect.String { + return fmt.Errorf("cannot assign string to a %s", f.String()) + } + f.SetString(v) + return nil +} + +func (t structTarget) pushValue(v reflect.Value) error { + f := t.get() + + switch f.Kind() { + case reflect.Slice: + t.ensure() + f.Set(reflect.Append(f, v)) + default: + return fmt.Errorf("cannot push %s on a %s", v.Kind(), f.Kind()) + } + + return nil +} + +func scope(v reflect.Value, name string) (target, error) { + switch v.Kind() { + case reflect.Struct: + return scopeStruct(v, name) + default: + panic(fmt.Errorf("can't scope on a %s", v.Kind())) + } +} + +func scopeStruct(v reflect.Value, name string) (target, error) { + // TODO: cache this + t := v.Type() + for i := 0; i < t.NumField(); i++ { + f := t.Field(i) + if f.PkgPath != "" { + // only consider exported fields + continue + } + if f.Anonymous { + // TODO: handle embedded structs + } else { + // TODO: handle names variations + if f.Name == name { + return structTarget(v.Field(i)), nil + } + } + } + return nil, fmt.Errorf("field '%s' not found on %s", name, v.Type()) +} diff --git a/internal/unmarshaler/targets_test.go b/internal/unmarshaler/targets_test.go new file mode 100644 index 00000000..7e14d252 --- /dev/null +++ b/internal/unmarshaler/targets_test.go @@ -0,0 +1,166 @@ +package unmarshaler + +import ( + "reflect" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestStructTarget_Ensure(t *testing.T) { + examples := []struct { + desc string + input reflect.Value + name string + test func(v reflect.Value) + }{ + { + desc: "handle a nil slice of string", + input: reflect.ValueOf(&struct{ A []string }{}).Elem(), + name: "A", + test: func(v reflect.Value) { + assert.False(t, v.IsNil()) + }, + }, + { + desc: "handle an existing slice of string", + input: reflect.ValueOf(&struct{ A []string }{A: []string{"foo"}}).Elem(), + name: "A", + test: func(v reflect.Value) { + require.False(t, v.IsNil()) + s := v.Interface().([]string) + assert.Equal(t, []string{"foo"}, s) + }, + }, + } + + for _, e := range examples { + t.Run(e.desc, func(t *testing.T) { + target, err := scope(e.input, e.name) + require.NoError(t, err) + target.ensure() + v := target.get() + e.test(v) + }) + } +} + +func TestStructTarget_SetString(t *testing.T) { + str := "value" + + examples := []struct { + desc string + input reflect.Value + name string + test func(v reflect.Value, err error) + }{ + { + desc: "sets a string", + input: reflect.ValueOf(&struct{ A string }{}).Elem(), + name: "A", + test: func(v reflect.Value, err error) { + assert.NoError(t, err) + assert.Equal(t, str, v.String()) + }, + }, + { + desc: "fails on a float", + input: reflect.ValueOf(&struct{ A float64 }{}).Elem(), + name: "A", + test: func(v reflect.Value, err error) { + assert.Error(t, err) + }, + }, + { + desc: "fails on a slice", + input: reflect.ValueOf(&struct{ A []string }{}).Elem(), + name: "A", + test: func(v reflect.Value, err error) { + assert.Error(t, err) + }, + }, + } + + for _, e := range examples { + t.Run(e.desc, func(t *testing.T) { + target, err := scope(e.input, e.name) + require.NoError(t, err) + err = target.setString(str) + v := target.get() + e.test(v, err) + }) + } +} + +func TestPushValue_Struct(t *testing.T) { + examples := []struct { + desc string + input reflect.Value + expected []string + error bool + }{ + { + desc: "push to nil slice", + input: reflect.ValueOf(&struct{ A []string }{}).Elem(), + expected: []string{"hello"}, + }, + { + desc: "push to string", + input: reflect.ValueOf(&struct{ A string }{}).Elem(), + error: true, + }, + } + + for _, e := range examples { + t.Run(e.desc, func(t *testing.T) { + target, err := scope(e.input, "A") + require.NoError(t, err) + v := reflect.ValueOf("hello") + err = target.pushValue(v) + if e.error { + require.Error(t, err) + } else { + require.NoError(t, err) + x := target.get().Interface().([]string) + assert.Equal(t, e.expected, x) + } + }) + } +} + +func TestScope_Struct(t *testing.T) { + examples := []struct { + desc string + input reflect.Value + name string + err bool + idx []int + }{ + { + desc: "simple field", + input: reflect.ValueOf(&struct{ A string }{}).Elem(), + name: "A", + idx: []int{0}, + }, + { + desc: "fails not-exported field", + input: reflect.ValueOf(&struct{ a string }{}).Elem(), + name: "a", + err: true, + }, + } + + for _, e := range examples { + t.Run(e.desc, func(t *testing.T) { + x, err := scope(e.input, e.name) + if e.err { + require.Error(t, err) + } else { + x2, ok := x.(structTarget) + require.True(t, ok) + x2.get() + } + }) + } +} diff --git a/internal/unmarshaler/unmarshaler.go b/internal/unmarshaler/unmarshaler.go new file mode 100644 index 00000000..82227b39 --- /dev/null +++ b/internal/unmarshaler/unmarshaler.go @@ -0,0 +1,69 @@ +package unmarshaler + +import ( + "fmt" + "reflect" + + "github.com/pelletier/go-toml/v2/internal/ast" +) + +func FromAst(tree ast.Root, target interface{}) error { + x := reflect.ValueOf(target) + if x.Kind() != reflect.Ptr { + return fmt.Errorf("need to target a pointer, not %s", x.Kind()) + } + if x.IsNil() { + return fmt.Errorf("target pointer must be non-nil") + } + + for _, node := range tree { + err := topLevelNode(x, &node) + if err != nil { + return err + } + } + + return nil +} + +func topLevelNode(x reflect.Value, node *ast.Node) error { + if x.Kind() != reflect.Ptr { + panic("topLevelNode should receive target, which should be a pointer") + } + if x.IsNil() { + panic("topLevelNode should receive target, which should not be a nil pointer") + } + + switch node.Kind { + case ast.Table: + panic("TODO") + case ast.ArrayTable: + panic("TODO") + case ast.KeyValue: + return keyValue(x, node) + default: + panic(fmt.Errorf("this should not be a top level node type: %s", node.Kind)) + } +} + +func keyValue(x reflect.Value, node *ast.Node) error { + assertNode(ast.KeyValue, node) + assertPtr(x) + + key := node.Key() + key = key + // TODO + return nil +} + +func assertNode(expected ast.Kind, node *ast.Node) { + if node.Kind != expected { + panic(fmt.Errorf("expected node of kind %s, not %s", expected, node.Kind)) + } +} + +func assertPtr(x reflect.Value) { + if x.Kind() != reflect.Ptr { + panic(fmt.Errorf("should be a pointer, not a %s", x.Kind())) + } +} diff --git a/internal/unmarshaler/unmarshaler_test.go b/internal/unmarshaler/unmarshaler_test.go new file mode 100644 index 00000000..da71743c --- /dev/null +++ b/internal/unmarshaler/unmarshaler_test.go @@ -0,0 +1,39 @@ +package unmarshaler_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/pelletier/go-toml/v2/internal/ast" + "github.com/pelletier/go-toml/v2/internal/unmarshaler" +) + +func TestFromAst_KV(t *testing.T) { + t.Skipf("later") + root := ast.Root{ + ast.Node{ + Kind: ast.KeyValue, + Children: []ast.Node{ + { + Kind: ast.Key, + Data: []byte(`Foo`), + }, + { + Kind: ast.String, + Data: []byte(`hello`), + }, + }, + }, + } + + type Doc struct { + Foo string + } + + x := Doc{} + err := unmarshaler.FromAst(root, &x) + require.NoError(t, err) + assert.Equal(t, Doc{Foo: "hello"}, x) +} diff --git a/unmarshal.go b/unmarshal.go index 84ee28d9..80efa521 100644 --- a/unmarshal.go +++ b/unmarshal.go @@ -1,6 +1,7 @@ package toml import ( + "fmt" "reflect" "time" @@ -62,6 +63,7 @@ func (u *unmarshaler) Assignation() { return } u.assign = true + fmt.Println("ASSIGN: TRUE!") } func (u *unmarshaler) ArrayBegin() { @@ -73,11 +75,12 @@ func (u *unmarshaler) ArrayBegin() { if u.err != nil { return } - if u.assign { - u.assign = false - } else { - u.err = u.builder.SliceNewElem() + fmt.Println("ARRAY BEGIN ASSIGN =", u.assign) + if !u.assign { + //u.err = u.builder.SliceNewSlice() + // TODO } + u.assign = false } func (u *unmarshaler) ArrayEnd() { @@ -126,8 +129,15 @@ func (u *unmarshaler) InlineTableBegin() { return } - // TODO + u.builder.Save() + if u.builder.IsSliceOrPtr() { + u.err = u.builder.SliceNewElem() + } else { + u.err = u.builder.EnsureStructOrMap() + } + + u.assign = false } func (u *unmarshaler) InlineTableEnd() { @@ -135,7 +145,7 @@ func (u *unmarshaler) InlineTableEnd() { return } - // TODO + u.builder.Load() } func (u *unmarshaler) KeyValBegin() { @@ -176,6 +186,7 @@ func (u *unmarshaler) StringValue(v []byte) { s := string(v) u.err = u.builder.Set(reflect.ValueOf(&s)) } + u.assign = false } func (u *unmarshaler) BoolValue(b bool) { @@ -192,6 +203,7 @@ func (u *unmarshaler) BoolValue(b bool) { } else { u.err = u.builder.SetBool(b) } + u.assign = false } func (u *unmarshaler) FloatValue(n float64) { @@ -209,6 +221,7 @@ func (u *unmarshaler) FloatValue(n float64) { u.err = u.builder.Set(reflect.ValueOf(&n)) //u.err = u.builder.SetFloat(n) } + u.assign = false } func (u *unmarshaler) IntValue(n int64) { @@ -225,6 +238,7 @@ func (u *unmarshaler) IntValue(n int64) { } else { u.err = u.builder.Set(reflect.ValueOf(&n)) } + u.assign = false } func (u *unmarshaler) LocalDateValue(date LocalDate) { @@ -241,6 +255,7 @@ func (u *unmarshaler) LocalDateValue(date LocalDate) { } else { u.err = u.builder.Set(reflect.ValueOf(&date)) } + u.assign = false } func (u *unmarshaler) LocalDateTimeValue(dt LocalDateTime) { @@ -257,6 +272,7 @@ func (u *unmarshaler) LocalDateTimeValue(dt LocalDateTime) { } else { u.err = u.builder.Set(reflect.ValueOf(&dt)) } + u.assign = false } func (u *unmarshaler) DateTimeValue(dt time.Time) { @@ -273,6 +289,7 @@ func (u *unmarshaler) DateTimeValue(dt time.Time) { } else { u.err = u.builder.Set(reflect.ValueOf(&dt)) } + u.assign = false } func (u *unmarshaler) LocalTimeValue(localTime LocalTime) { @@ -289,6 +306,7 @@ func (u *unmarshaler) LocalTimeValue(localTime LocalTime) { } else { u.err = u.builder.Set(reflect.ValueOf(&localTime)) } + u.assign = false } func (u *unmarshaler) SimpleKey(v []byte) { @@ -337,5 +355,5 @@ func (u *unmarshaler) StandardTableEnd() { return } - u.builder.EnsureStructOrMap() + u.builder.EnsureStructOrMap() // TODO: handle error } From d8be04d4a8493db2ba744178ec68b7d6c7b643bd Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Sat, 13 Mar 2021 18:45:03 -0500 Subject: [PATCH 063/228] Handle simple string slice --- internal/unmarshaler/targets.go | 39 +++++++++--- internal/unmarshaler/targets_test.go | 24 +++++++- internal/unmarshaler/unmarshaler.go | 78 ++++++++++++++++-------- internal/unmarshaler/unmarshaler_test.go | 37 ++++++++++- 4 files changed, 145 insertions(+), 33 deletions(-) diff --git a/internal/unmarshaler/targets.go b/internal/unmarshaler/targets.go index 1c9fd259..17c4ad91 100644 --- a/internal/unmarshaler/targets.go +++ b/internal/unmarshaler/targets.go @@ -15,18 +15,23 @@ type target interface { // Appends an arbitrary value to the container. pushValue(v reflect.Value) error + // Creates a new value of the container's element type, and returns a + // target to it. + pushNew() (target, error) + // Dereferences the target. get() reflect.Value } -// struct target just contain the reflect.Value of the target field. -type structTarget reflect.Value +// valueTarget just contains a reflect.Value that can be set. +// It is used for struct fields. +type valueTarget reflect.Value -func (t structTarget) get() reflect.Value { +func (t valueTarget) get() reflect.Value { return reflect.Value(t) } -func (t structTarget) ensure() { +func (t valueTarget) ensure() { f := t.get() if !f.IsNil() { return @@ -40,7 +45,7 @@ func (t structTarget) ensure() { } } -func (t structTarget) setString(v string) error { +func (t valueTarget) setString(v string) error { f := t.get() if f.Kind() != reflect.String { return fmt.Errorf("cannot assign string to a %s", f.String()) @@ -49,7 +54,7 @@ func (t structTarget) setString(v string) error { return nil } -func (t structTarget) pushValue(v reflect.Value) error { +func (t valueTarget) pushValue(v reflect.Value) error { f := t.get() switch f.Kind() { @@ -63,6 +68,26 @@ func (t structTarget) pushValue(v reflect.Value) error { return nil } +func (t valueTarget) pushNew() (target, error) { + f := t.get() + + switch f.Kind() { + case reflect.Slice: + t.ensure() + f = t.get() + idx := f.Len() + f.Set(reflect.Append(f, reflect.New(f.Type().Elem()).Elem())) + return valueTarget(f.Index(idx)), nil + default: + return nil, fmt.Errorf("cannot pushNew on a %s", f.Kind()) + } +} + +func scopeTarget(t target, name string) (target, error) { + x := t.get() + return scope(x, name) +} + func scope(v reflect.Value, name string) (target, error) { switch v.Kind() { case reflect.Struct: @@ -86,7 +111,7 @@ func scopeStruct(v reflect.Value, name string) (target, error) { } else { // TODO: handle names variations if f.Name == name { - return structTarget(v.Field(i)), nil + return valueTarget(v.Field(i)), nil } } } diff --git a/internal/unmarshaler/targets_test.go b/internal/unmarshaler/targets_test.go index 7e14d252..598384bf 100644 --- a/internal/unmarshaler/targets_test.go +++ b/internal/unmarshaler/targets_test.go @@ -129,6 +129,28 @@ func TestPushValue_Struct(t *testing.T) { } } +func TestPushNew(t *testing.T) { + t.Run("slice of strings", func(t *testing.T) { + type Doc struct { + A []string + } + d := Doc{} + + x, err := scope(reflect.ValueOf(&d).Elem(), "A") + require.NoError(t, err) + + n, err := x.pushNew() + require.NoError(t, err) + require.NoError(t, n.setString("hello")) + require.Equal(t, []string{"hello"}, d.A) + + n, err = x.pushNew() + require.NoError(t, err) + require.NoError(t, n.setString("world")) + require.Equal(t, []string{"hello", "world"}, d.A) + }) +} + func TestScope_Struct(t *testing.T) { examples := []struct { desc string @@ -157,7 +179,7 @@ func TestScope_Struct(t *testing.T) { if e.err { require.Error(t, err) } else { - x2, ok := x.(structTarget) + x2, ok := x.(valueTarget) require.True(t, ok) x2.get() } diff --git a/internal/unmarshaler/unmarshaler.go b/internal/unmarshaler/unmarshaler.go index 82227b39..d2a35f64 100644 --- a/internal/unmarshaler/unmarshaler.go +++ b/internal/unmarshaler/unmarshaler.go @@ -8,16 +8,18 @@ import ( ) func FromAst(tree ast.Root, target interface{}) error { - x := reflect.ValueOf(target) - if x.Kind() != reflect.Ptr { - return fmt.Errorf("need to target a pointer, not %s", x.Kind()) + v := reflect.ValueOf(target) + if v.Kind() != reflect.Ptr { + return fmt.Errorf("need to target a pointer, not %s", v.Kind()) } - if x.IsNil() { + if v.IsNil() { return fmt.Errorf("target pointer must be non-nil") } + x := valueTarget(v.Elem()) + for _, node := range tree { - err := topLevelNode(x, &node) + err := unmarshalTopLevelNode(x, &node) if err != nil { return err } @@ -26,33 +28,67 @@ func FromAst(tree ast.Root, target interface{}) error { return nil } -func topLevelNode(x reflect.Value, node *ast.Node) error { - if x.Kind() != reflect.Ptr { - panic("topLevelNode should receive target, which should be a pointer") - } - if x.IsNil() { - panic("topLevelNode should receive target, which should not be a nil pointer") - } - +func unmarshalTopLevelNode(x target, node *ast.Node) error { switch node.Kind { case ast.Table: panic("TODO") case ast.ArrayTable: panic("TODO") case ast.KeyValue: - return keyValue(x, node) + return unmarshalKeyValue(x, node) default: panic(fmt.Errorf("this should not be a top level node type: %s", node.Kind)) } } -func keyValue(x reflect.Value, node *ast.Node) error { +func unmarshalKeyValue(x target, node *ast.Node) error { assertNode(ast.KeyValue, node) - assertPtr(x) key := node.Key() - key = key - // TODO + + var err error + for _, n := range key { + x, err = scopeTarget(x, string(n.Data)) + if err != nil { + return err + } + } + + return unmarshalValue(x, node.Value()) +} + +func unmarshalValue(x target, node *ast.Node) error { + switch node.Kind { + case ast.String: + return unmarshalString(x, node) + case ast.Array: + return unmarshalArray(x, node) + default: + panic(fmt.Errorf("unhandled unmarshalValue kind %s", node.Kind)) + } +} + +func unmarshalString(x target, node *ast.Node) error { + assertNode(ast.String, node) + + return x.setString(string(node.Data)) +} + +func unmarshalArray(x target, node *ast.Node) error { + assertNode(ast.Array, node) + + x.ensure() + + for _, n := range node.Children { + v, err := x.pushNew() + if err != nil { + return err + } + err = unmarshalValue(v, &n) + if err != nil { + return err + } + } return nil } @@ -61,9 +97,3 @@ func assertNode(expected ast.Kind, node *ast.Node) { panic(fmt.Errorf("expected node of kind %s, not %s", expected, node.Kind)) } } - -func assertPtr(x reflect.Value) { - if x.Kind() != reflect.Ptr { - panic(fmt.Errorf("should be a pointer, not a %s", x.Kind())) - } -} diff --git a/internal/unmarshaler/unmarshaler_test.go b/internal/unmarshaler/unmarshaler_test.go index da71743c..d99b62c3 100644 --- a/internal/unmarshaler/unmarshaler_test.go +++ b/internal/unmarshaler/unmarshaler_test.go @@ -11,7 +11,6 @@ import ( ) func TestFromAst_KV(t *testing.T) { - t.Skipf("later") root := ast.Root{ ast.Node{ Kind: ast.KeyValue, @@ -37,3 +36,39 @@ func TestFromAst_KV(t *testing.T) { require.NoError(t, err) assert.Equal(t, Doc{Foo: "hello"}, x) } + +func TestFromAst_Slice(t *testing.T) { + root := ast.Root{ + ast.Node{ + Kind: ast.KeyValue, + Children: []ast.Node{ + { + Kind: ast.Key, + Data: []byte(`Foo`), + }, + { + Kind: ast.Array, + Children: []ast.Node{ + { + Kind: ast.String, + Data: []byte(`hello`), + }, + { + Kind: ast.String, + Data: []byte(`world`), + }, + }, + }, + }, + }, + } + + type Doc struct { + Foo []string + } + + x := Doc{} + err := unmarshaler.FromAst(root, &x) + require.NoError(t, err) + assert.Equal(t, Doc{Foo: []string{"hello", "world"}}, x) +} From 1fafb71fd901ccb95057c78f768f2578fddd3365 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Sat, 13 Mar 2021 18:51:45 -0500 Subject: [PATCH 064/228] LF --- go.sum | 1 - 1 file changed, 1 deletion(-) diff --git a/go.sum b/go.sum index 26500d5d..acb88a48 100644 --- a/go.sum +++ b/go.sum @@ -2,7 +2,6 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= From a0548e793cf6d662f4c19f81391d05c7ce84fe0f Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Sat, 13 Mar 2021 22:07:36 -0500 Subject: [PATCH 065/228] Unmarshal slices of strings --- internal/unmarshaler/parser.go | 86 ++++++++------- internal/unmarshaler/parser_test.go | 67 +++++++++++- internal/unmarshaler/targets.go | 62 ++++++----- internal/unmarshaler/targets_test.go | 68 +++++------- internal/unmarshaler/unmarshaler.go | 5 +- internal/unmarshaler/unmarshaler_test.go | 133 ++++++++++++++++++----- 6 files changed, 284 insertions(+), 137 deletions(-) diff --git a/internal/unmarshaler/parser.go b/internal/unmarshaler/parser.go index 9a1fb8df..4bdb2d7d 100644 --- a/internal/unmarshaler/parser.go +++ b/internal/unmarshaler/parser.go @@ -218,8 +218,9 @@ func (p *parser) parseVal(b []byte) (ast.Node, []byte, error) { // TODO return node, b[5:], nil case '[': - // TODO - //return p.parseValArray(b) + node.Kind = ast.Array + b, err := p.parseValArray(&node, b) + return node, b, err case '{': // TODO //return p.parseInlineTable(b) @@ -275,7 +276,7 @@ func (p *parser) parseInlineTable(b []byte) ([]byte, error) { return expect('}', b) } -func (p *parser) parseValArray(b []byte) ([]byte, error) { +func (p *parser) parseValArray(node *ast.Node, b []byte) ([]byte, error) { //array = array-open [ array-values ] ws-comment-newline array-close //array-open = %x5B ; [ //array-close = %x5D ; ] @@ -284,45 +285,46 @@ func (p *parser) parseValArray(b []byte) ([]byte, error) { //array-sep = %x2C ; , Comma //ws-comment-newline = *( wschar / [ comment ] newline ) - // TODO - //b = b[1:] - // - //first := true - //var err error - //for len(b) > 0 { - // b, err = p.parseOptionalWhitespaceCommentNewline(b) - // if err != nil { - // return nil, err - // } - // - // if len(b) == 0 { - // return nil, unexpectedCharacter{b: b} - // } - // - // if b[0] == ']' { - // break - // } - // if b[0] == ',' { - // if first { - // return nil, fmt.Errorf("array cannot start with comma") - // } - // b = b[1:] - // b, err = p.parseOptionalWhitespaceCommentNewline(b) - // if err != nil { - // return nil, err - // } - // } - // - // b, err = p.parseVal(b) - // if err != nil { - // return nil, err - // } - // b, err = p.parseOptionalWhitespaceCommentNewline(b) - // if err != nil { - // return nil, err - // } - // first = false - //} + b = b[1:] + + first := true + var err error + for len(b) > 0 { + b, err = p.parseOptionalWhitespaceCommentNewline(b) + if err != nil { + return nil, err + } + + if len(b) == 0 { + return nil, unexpectedCharacter{b: b} + } + + if b[0] == ']' { + break + } + if b[0] == ',' { + if first { + return nil, fmt.Errorf("array cannot start with comma") + } + b = b[1:] + b, err = p.parseOptionalWhitespaceCommentNewline(b) + if err != nil { + return nil, err + } + } + + var valueNode ast.Node + valueNode, b, err = p.parseVal(b) + if err != nil { + return nil, err + } + node.Children = append(node.Children, valueNode) + b, err = p.parseOptionalWhitespaceCommentNewline(b) + if err != nil { + return nil, err + } + first = false + } return expect(']', b) } diff --git a/internal/unmarshaler/parser_test.go b/internal/unmarshaler/parser_test.go index f4be26b0..e3e22db3 100644 --- a/internal/unmarshaler/parser_test.go +++ b/internal/unmarshaler/parser_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/require" ) -func TestParser_Simple(t *testing.T) { +func TestParser_AST(t *testing.T) { examples := []struct { desc string input string @@ -33,6 +33,71 @@ func TestParser_Simple(t *testing.T) { }, }, }, + { + desc: "array of strings", + input: `A = ["hello", ["world", "again"]]`, + ast: ast.Root{ + ast.Node{ + Kind: ast.KeyValue, + Children: []ast.Node{ + { + Kind: ast.Key, + Data: []byte(`A`), + }, + { + Kind: ast.Array, + Children: []ast.Node{ + { + Kind: ast.String, + Data: []byte(`hello`), + }, + { + Kind: ast.Array, + Children: []ast.Node{ + { + Kind: ast.String, + Data: []byte(`world`), + }, + { + Kind: ast.String, + Data: []byte(`again`), + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + desc: "array of arrays of strings", + input: `A = ["hello", "world"]`, + ast: ast.Root{ + ast.Node{ + Kind: ast.KeyValue, + Children: []ast.Node{ + { + Kind: ast.Key, + Data: []byte(`A`), + }, + { + Kind: ast.Array, + Children: []ast.Node{ + { + Kind: ast.String, + Data: []byte(`hello`), + }, + { + Kind: ast.String, + Data: []byte(`world`), + }, + }, + }, + }, + }, + }, + }, } for _, e := range examples { diff --git a/internal/unmarshaler/targets.go b/internal/unmarshaler/targets.go index 17c4ad91..daff6bbe 100644 --- a/internal/unmarshaler/targets.go +++ b/internal/unmarshaler/targets.go @@ -6,15 +6,12 @@ import ( ) type target interface { - // Ensure the target's reflect value is not nil. - ensure() + // Ensure the target's value is compatible with a slice and initialized. + ensureSlice() error // Store a string at the target. setString(v string) error - // Appends an arbitrary value to the container. - pushValue(v reflect.Value) error - // Creates a new value of the container's element type, and returns a // target to it. pushNew() (target, error) @@ -31,38 +28,38 @@ func (t valueTarget) get() reflect.Value { return reflect.Value(t) } -func (t valueTarget) ensure() { +func (t valueTarget) ensureSlice() error { f := t.get() - if !f.IsNil() { - return - } - switch f.Kind() { + switch f.Type().Kind() { case reflect.Slice: - f.Set(reflect.MakeSlice(f.Type(), 0, 0)) + if f.IsNil() { + f.Set(reflect.MakeSlice(f.Type(), 0, 0)) + } + case reflect.Interface: + if f.IsNil() { + f.Set(reflect.MakeSlice(reflect.TypeOf([]interface{}{}), 0, 0)) + } else { + if f.Type().Elem().Kind() != reflect.Slice { + return fmt.Errorf("interface is pointing to a %s, not a slice", f.Kind()) + } + } default: - panic(fmt.Errorf("don't know how to ensure %s", f.Kind())) - } -} - -func (t valueTarget) setString(v string) error { - f := t.get() - if f.Kind() != reflect.String { - return fmt.Errorf("cannot assign string to a %s", f.String()) + return fmt.Errorf("cannot initialize a slice in %s", f.Kind()) } - f.SetString(v) return nil } -func (t valueTarget) pushValue(v reflect.Value) error { +func (t valueTarget) setString(v string) error { f := t.get() switch f.Kind() { - case reflect.Slice: - t.ensure() - f.Set(reflect.Append(f, v)) + case reflect.String: + f.SetString(v) + case reflect.Interface: + f.Set(reflect.ValueOf(v)) default: - return fmt.Errorf("cannot push %s on a %s", v.Kind(), f.Kind()) + return fmt.Errorf("cannot assign string to a %s", f.String()) } return nil @@ -73,11 +70,22 @@ func (t valueTarget) pushNew() (target, error) { switch f.Kind() { case reflect.Slice: - t.ensure() - f = t.get() idx := f.Len() f.Set(reflect.Append(f, reflect.New(f.Type().Elem()).Elem())) return valueTarget(f.Index(idx)), nil + case reflect.Interface: + if f.IsNil() { + panic("interface should have been initialized") + } + ifaceElem := f.Elem() + if ifaceElem.Kind() != reflect.Slice { + return nil, fmt.Errorf("cannot pushNew on a %s", f.Kind()) + } + idx := ifaceElem.Len() + newElem := reflect.New(ifaceElem.Type().Elem()).Elem() + newSlice := reflect.Append(ifaceElem, newElem) + f.Set(newSlice) + return valueTarget(f.Elem().Index(idx)), nil default: return nil, fmt.Errorf("cannot pushNew on a %s", f.Kind()) } diff --git a/internal/unmarshaler/targets_test.go b/internal/unmarshaler/targets_test.go index 598384bf..7db9993c 100644 --- a/internal/unmarshaler/targets_test.go +++ b/internal/unmarshaler/targets_test.go @@ -13,13 +13,14 @@ func TestStructTarget_Ensure(t *testing.T) { desc string input reflect.Value name string - test func(v reflect.Value) + test func(v reflect.Value, err error) }{ { desc: "handle a nil slice of string", input: reflect.ValueOf(&struct{ A []string }{}).Elem(), name: "A", - test: func(v reflect.Value) { + test: func(v reflect.Value, err error) { + assert.NoError(t, err) assert.False(t, v.IsNil()) }, }, @@ -27,7 +28,8 @@ func TestStructTarget_Ensure(t *testing.T) { desc: "handle an existing slice of string", input: reflect.ValueOf(&struct{ A []string }{A: []string{"foo"}}).Elem(), name: "A", - test: func(v reflect.Value) { + test: func(v reflect.Value, err error) { + assert.NoError(t, err) require.False(t, v.IsNil()) s := v.Interface().([]string) assert.Equal(t, []string{"foo"}, s) @@ -39,9 +41,9 @@ func TestStructTarget_Ensure(t *testing.T) { t.Run(e.desc, func(t *testing.T) { target, err := scope(e.input, e.name) require.NoError(t, err) - target.ensure() + err = target.ensureSlice() v := target.get() - e.test(v) + e.test(v, err) }) } } @@ -93,42 +95,6 @@ func TestStructTarget_SetString(t *testing.T) { } } -func TestPushValue_Struct(t *testing.T) { - examples := []struct { - desc string - input reflect.Value - expected []string - error bool - }{ - { - desc: "push to nil slice", - input: reflect.ValueOf(&struct{ A []string }{}).Elem(), - expected: []string{"hello"}, - }, - { - desc: "push to string", - input: reflect.ValueOf(&struct{ A string }{}).Elem(), - error: true, - }, - } - - for _, e := range examples { - t.Run(e.desc, func(t *testing.T) { - target, err := scope(e.input, "A") - require.NoError(t, err) - v := reflect.ValueOf("hello") - err = target.pushValue(v) - if e.error { - require.Error(t, err) - } else { - require.NoError(t, err) - x := target.get().Interface().([]string) - assert.Equal(t, e.expected, x) - } - }) - } -} - func TestPushNew(t *testing.T) { t.Run("slice of strings", func(t *testing.T) { type Doc struct { @@ -149,6 +115,26 @@ func TestPushNew(t *testing.T) { require.NoError(t, n.setString("world")) require.Equal(t, []string{"hello", "world"}, d.A) }) + + t.Run("slice of interfaces", func(t *testing.T) { + type Doc struct { + A []interface{} + } + d := Doc{} + + x, err := scope(reflect.ValueOf(&d).Elem(), "A") + require.NoError(t, err) + + n, err := x.pushNew() + require.NoError(t, err) + require.NoError(t, n.setString("hello")) + require.Equal(t, []interface{}{"hello"}, d.A) + + n, err = x.pushNew() + require.NoError(t, err) + require.NoError(t, n.setString("world")) + require.Equal(t, []interface{}{"hello", "world"}, d.A) + }) } func TestScope_Struct(t *testing.T) { diff --git a/internal/unmarshaler/unmarshaler.go b/internal/unmarshaler/unmarshaler.go index d2a35f64..00bdae45 100644 --- a/internal/unmarshaler/unmarshaler.go +++ b/internal/unmarshaler/unmarshaler.go @@ -77,7 +77,10 @@ func unmarshalString(x target, node *ast.Node) error { func unmarshalArray(x target, node *ast.Node) error { assertNode(ast.Array, node) - x.ensure() + err := x.ensureSlice() + if err != nil { + return err + } for _, n := range node.Children { v, err := x.pushNew() diff --git a/internal/unmarshaler/unmarshaler_test.go b/internal/unmarshaler/unmarshaler_test.go index d99b62c3..06cf0eb8 100644 --- a/internal/unmarshaler/unmarshaler_test.go +++ b/internal/unmarshaler/unmarshaler_test.go @@ -38,37 +38,120 @@ func TestFromAst_KV(t *testing.T) { } func TestFromAst_Slice(t *testing.T) { - root := ast.Root{ - ast.Node{ - Kind: ast.KeyValue, - Children: []ast.Node{ - { - Kind: ast.Key, - Data: []byte(`Foo`), + t.Run("slice of string", func(t *testing.T) { + root := ast.Root{ + ast.Node{ + Kind: ast.KeyValue, + Children: []ast.Node{ + { + Kind: ast.Key, + Data: []byte(`Foo`), + }, + { + Kind: ast.Array, + Children: []ast.Node{ + { + Kind: ast.String, + Data: []byte(`hello`), + }, + { + Kind: ast.String, + Data: []byte(`world`), + }, + }, + }, }, - { - Kind: ast.Array, - Children: []ast.Node{ - { - Kind: ast.String, - Data: []byte(`hello`), + }, + } + + type Doc struct { + Foo []string + } + + x := Doc{} + err := unmarshaler.FromAst(root, &x) + require.NoError(t, err) + assert.Equal(t, Doc{Foo: []string{"hello", "world"}}, x) + }) + + t.Run("slice of interfaces for strings", func(t *testing.T) { + root := ast.Root{ + ast.Node{ + Kind: ast.KeyValue, + Children: []ast.Node{ + { + Kind: ast.Key, + Data: []byte(`Foo`), + }, + { + Kind: ast.Array, + Children: []ast.Node{ + { + Kind: ast.String, + Data: []byte(`hello`), + }, + { + Kind: ast.String, + Data: []byte(`world`), + }, }, - { - Kind: ast.String, - Data: []byte(`world`), + }, + }, + }, + } + + type Doc struct { + Foo []interface{} + } + + x := Doc{} + err := unmarshaler.FromAst(root, &x) + require.NoError(t, err) + assert.Equal(t, Doc{Foo: []interface{}{"hello", "world"}}, x) + }) + + t.Run("slice of interfaces with slices", func(t *testing.T) { + root := ast.Root{ + ast.Node{ + Kind: ast.KeyValue, + Children: []ast.Node{ + { + Kind: ast.Key, + Data: []byte(`Foo`), + }, + { + Kind: ast.Array, + Children: []ast.Node{ + { + Kind: ast.String, + Data: []byte(`hello`), + }, + { + Kind: ast.Array, + Children: []ast.Node{ + { + Kind: ast.String, + Data: []byte(`inner1`), + }, + { + Kind: ast.String, + Data: []byte(`inner2`), + }, + }, + }, }, }, }, }, - }, - } + } - type Doc struct { - Foo []string - } + type Doc struct { + Foo []interface{} + } - x := Doc{} - err := unmarshaler.FromAst(root, &x) - require.NoError(t, err) - assert.Equal(t, Doc{Foo: []string{"hello", "world"}}, x) + x := Doc{} + err := unmarshaler.FromAst(root, &x) + require.NoError(t, err) + assert.Equal(t, Doc{Foo: []interface{}{"hello", []interface{}{"inner1", "inner2"}}}, x) + }) } From fbf01f768397e1c4791bb277467cd254de5d1977 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Sat, 13 Mar 2021 22:48:31 -0500 Subject: [PATCH 066/228] Handle Table --- internal/ast/ast.go | 22 +++-- internal/unmarshaler/unmarshaler.go | 51 +++++++---- internal/unmarshaler/unmarshaler_test.go | 112 +++++++++++++++++++++-- 3 files changed, 154 insertions(+), 31 deletions(-) diff --git a/internal/ast/ast.go b/internal/ast/ast.go index c0748f6e..25a89be7 100644 --- a/internal/ast/ast.go +++ b/internal/ast/ast.go @@ -129,23 +129,29 @@ type Node struct { // InlineTables have one child per key-value pair in the table. // KeyValues have at least two children. The last one is the value. The // rest make a potentially dotted key. + // Table and Array table have one child per element of the key they + // represent (same as KeyValue, but without the last node being the value). Children []Node } var NoNode = Node{} -// Key returns the nodes making the Key of a KeyValue. +// Key returns the child nodes making the Key on a supported node. Panics +// otherwise. // They are guaranteed to be all be of the Kind Key. A simple key would return // just one element. -// Panics if not called on a KeyValue node, or if the Children are malformed. func (n *Node) Key() []Node { - if n.Kind != KeyValue { - panic(fmt.Errorf("Key() should only be called on on a KeyValue, not %s", n.Kind)) - } - if len(n.Children) < 2 { - panic(fmt.Errorf("KeyValue should have at least two children, not %d", len(n.Children))) + switch n.Kind { + case KeyValue: + if len(n.Children) < 2 { + panic(fmt.Errorf("KeyValue should have at least two children, not %d", len(n.Children))) + } + return n.Children[:len(n.Children)-1] + case Table: + return n.Children + default: + panic(fmt.Errorf("Key() is not supported on a %s", n.Kind)) } - return n.Children[:len(n.Children)-1] } // Value returns a pointer to the value node of a KeyValue. diff --git a/internal/unmarshaler/unmarshaler.go b/internal/unmarshaler/unmarshaler.go index 00bdae45..4e79a4e7 100644 --- a/internal/unmarshaler/unmarshaler.go +++ b/internal/unmarshaler/unmarshaler.go @@ -7,19 +7,28 @@ import ( "github.com/pelletier/go-toml/v2/internal/ast" ) -func FromAst(tree ast.Root, target interface{}) error { - v := reflect.ValueOf(target) - if v.Kind() != reflect.Ptr { - return fmt.Errorf("need to target a pointer, not %s", v.Kind()) +func Unmarshal(data []byte, v interface{}) error { + p := parser{} + err := p.parse(data) + if err != nil { + return err + } + return fromAst(p.tree, v) +} + +func fromAst(tree ast.Root, v interface{}) error { + r := reflect.ValueOf(v) + if r.Kind() != reflect.Ptr { + return fmt.Errorf("need to target a pointer, not %s", r.Kind()) } - if v.IsNil() { + if r.IsNil() { return fmt.Errorf("target pointer must be non-nil") } - x := valueTarget(v.Elem()) - + var x target = valueTarget(r.Elem()) + var err error for _, node := range tree { - err := unmarshalTopLevelNode(x, &node) + x, err = unmarshalTopLevelNode(x, &node) if err != nil { return err } @@ -28,31 +37,39 @@ func FromAst(tree ast.Root, target interface{}) error { return nil } -func unmarshalTopLevelNode(x target, node *ast.Node) error { +// The target return value is the target for the next top-level node. Mostly +// unchanged, except by table and array table. +func unmarshalTopLevelNode(x target, node *ast.Node) (target, error) { switch node.Kind { case ast.Table: - panic("TODO") + return scopeWithKey(x, node.Key()) case ast.ArrayTable: panic("TODO") case ast.KeyValue: - return unmarshalKeyValue(x, node) + return x, unmarshalKeyValue(x, node) default: panic(fmt.Errorf("this should not be a top level node type: %s", node.Kind)) } } -func unmarshalKeyValue(x target, node *ast.Node) error { - assertNode(ast.KeyValue, node) - - key := node.Key() - +func scopeWithKey(x target, key []ast.Node) (target, error) { var err error for _, n := range key { x, err = scopeTarget(x, string(n.Data)) if err != nil { - return err + return nil, err } } + return x, nil +} + +func unmarshalKeyValue(x target, node *ast.Node) error { + assertNode(ast.KeyValue, node) + + x, err := scopeWithKey(x, node.Key()) + if err != nil { + return err + } return unmarshalValue(x, node.Value()) } diff --git a/internal/unmarshaler/unmarshaler_test.go b/internal/unmarshaler/unmarshaler_test.go index 06cf0eb8..33b331d3 100644 --- a/internal/unmarshaler/unmarshaler_test.go +++ b/internal/unmarshaler/unmarshaler_test.go @@ -1,4 +1,4 @@ -package unmarshaler_test +package unmarshaler import ( "testing" @@ -7,7 +7,6 @@ import ( "github.com/stretchr/testify/require" "github.com/pelletier/go-toml/v2/internal/ast" - "github.com/pelletier/go-toml/v2/internal/unmarshaler" ) func TestFromAst_KV(t *testing.T) { @@ -32,11 +31,112 @@ func TestFromAst_KV(t *testing.T) { } x := Doc{} - err := unmarshaler.FromAst(root, &x) + err := fromAst(root, &x) require.NoError(t, err) assert.Equal(t, Doc{Foo: "hello"}, x) } +func TestFromAst_Table(t *testing.T) { + t.Run("one level table on struct", func(t *testing.T) { + root := ast.Root{ + ast.Node{ + Kind: ast.Table, + Children: []ast.Node{ + {Kind: ast.Key, Data: []byte(`Level1`)}, + }, + }, + ast.Node{ + Kind: ast.KeyValue, + Children: []ast.Node{ + { + Kind: ast.Key, + Data: []byte(`A`), + }, + { + Kind: ast.String, + Data: []byte(`hello`), + }, + }, + }, + ast.Node{ + Kind: ast.KeyValue, + Children: []ast.Node{ + { + Kind: ast.Key, + Data: []byte(`B`), + }, + { + Kind: ast.String, + Data: []byte(`world`), + }, + }, + }, + } + + type Level1 struct { + A string + B string + } + + type Doc struct { + Level1 Level1 + } + + x := Doc{} + err := fromAst(root, &x) + require.NoError(t, err) + assert.Equal(t, Doc{ + Level1: Level1{ + A: "hello", + B: "world", + }, + }, x) + }) + t.Run("one level table on struct", func(t *testing.T) { + root := ast.Root{ + ast.Node{ + Kind: ast.Table, + Children: []ast.Node{ + {Kind: ast.Key, Data: []byte(`A`)}, + {Kind: ast.Key, Data: []byte(`B`)}, + }, + }, + ast.Node{ + Kind: ast.KeyValue, + Children: []ast.Node{ + { + Kind: ast.Key, + Data: []byte(`C`), + }, + { + Kind: ast.String, + Data: []byte(`value`), + }, + }, + }, + } + + type B struct { + C string + } + + type A struct { + B B + } + + type Doc struct { + A A + } + + x := Doc{} + err := fromAst(root, &x) + require.NoError(t, err) + assert.Equal(t, Doc{ + A: A{B: B{C: "value"}}, + }, x) + }) +} + func TestFromAst_Slice(t *testing.T) { t.Run("slice of string", func(t *testing.T) { root := ast.Root{ @@ -69,7 +169,7 @@ func TestFromAst_Slice(t *testing.T) { } x := Doc{} - err := unmarshaler.FromAst(root, &x) + err := fromAst(root, &x) require.NoError(t, err) assert.Equal(t, Doc{Foo: []string{"hello", "world"}}, x) }) @@ -105,7 +205,7 @@ func TestFromAst_Slice(t *testing.T) { } x := Doc{} - err := unmarshaler.FromAst(root, &x) + err := fromAst(root, &x) require.NoError(t, err) assert.Equal(t, Doc{Foo: []interface{}{"hello", "world"}}, x) }) @@ -150,7 +250,7 @@ func TestFromAst_Slice(t *testing.T) { } x := Doc{} - err := unmarshaler.FromAst(root, &x) + err := fromAst(root, &x) require.NoError(t, err) assert.Equal(t, Doc{Foo: []interface{}{"hello", []interface{}{"inner1", "inner2"}}}, x) }) From fa7ee6461a69c0231cf7f7cb3db1354235cc2ba0 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Sat, 13 Mar 2021 23:06:16 -0500 Subject: [PATCH 067/228] Inline tables --- internal/unmarshaler/parser.go | 58 ++++++++++++------------ internal/unmarshaler/parser_test.go | 34 ++++++++++++++ internal/unmarshaler/unmarshaler.go | 15 +++++- internal/unmarshaler/unmarshaler_test.go | 56 +++++++++++++++++++++++ 4 files changed, 134 insertions(+), 29 deletions(-) diff --git a/internal/unmarshaler/parser.go b/internal/unmarshaler/parser.go index 4bdb2d7d..9d127f18 100644 --- a/internal/unmarshaler/parser.go +++ b/internal/unmarshaler/parser.go @@ -222,8 +222,9 @@ func (p *parser) parseVal(b []byte) (ast.Node, []byte, error) { b, err := p.parseValArray(&node, b) return node, b, err case '{': - // TODO - //return p.parseInlineTable(b) + node.Kind = ast.InlineTable + b, err := p.parseInlineTable(&node, b) + return node, b, err default: // TODO //return p.parseIntOrFloatOrDateTime(b) @@ -240,38 +241,39 @@ func (p *parser) parseLiteralString(b []byte) ([]byte, []byte, error) { return v[1 : len(v)-1], rest, nil } -func (p *parser) parseInlineTable(b []byte) ([]byte, error) { +func (p *parser) parseInlineTable(node *ast.Node, b []byte) ([]byte, error) { //inline-table = inline-table-open [ inline-table-keyvals ] inline-table-close //inline-table-open = %x7B ws ; { //inline-table-close = ws %x7D ; } //inline-table-sep = ws %x2C ws ; , Comma //inline-table-keyvals = keyval [ inline-table-sep inline-table-keyvals ] - // TODO - //b = b[1:] - // - //first := true - //var err error - //for len(b) > 0 { - // b = p.parseWhitespace(b) - // if b[0] == '}' { - // break - // } - // - // if !first { - // b, err = expect(',', b) - // if err != nil { - // return nil, err - // } - // b = p.parseWhitespace(b) - // } - // b, err = p.parseKeyval(b) - // if err != nil { - // return nil, err - // } - // - // first = false - //} + b = b[1:] + + first := true + var err error + for len(b) > 0 { + b = p.parseWhitespace(b) + if b[0] == '}' { + break + } + + if !first { + b, err = expect(',', b) + if err != nil { + return nil, err + } + b = p.parseWhitespace(b) + } + var kv ast.Node + kv, b, err = p.parseKeyval(b) + if err != nil { + return nil, err + } + node.Children = append(node.Children, kv) + + first = false + } return expect('}', b) } diff --git a/internal/unmarshaler/parser_test.go b/internal/unmarshaler/parser_test.go index e3e22db3..0f2a71cc 100644 --- a/internal/unmarshaler/parser_test.go +++ b/internal/unmarshaler/parser_test.go @@ -98,6 +98,40 @@ func TestParser_AST(t *testing.T) { }, }, }, + { + desc: "inline table", + input: `name = { first = "Tom", last = "Preston-Werner" }`, + ast: ast.Root{ + ast.Node{ + Kind: ast.KeyValue, + Children: []ast.Node{ + { + Kind: ast.Key, + Data: []byte(`name`), + }, + { + Kind: ast.InlineTable, + Children: []ast.Node{ + { + Kind: ast.KeyValue, + Children: []ast.Node{ + {Kind: ast.Key, Data: []byte(`first`)}, + {Kind: ast.String, Data: []byte(`Tom`)}, + }, + }, + { + Kind: ast.KeyValue, + Children: []ast.Node{ + {Kind: ast.Key, Data: []byte(`last`)}, + {Kind: ast.String, Data: []byte(`Preston-Werner`)}, + }, + }, + }, + }, + }, + }, + }, + }, } for _, e := range examples { diff --git a/internal/unmarshaler/unmarshaler.go b/internal/unmarshaler/unmarshaler.go index 4e79a4e7..2f8c2abf 100644 --- a/internal/unmarshaler/unmarshaler.go +++ b/internal/unmarshaler/unmarshaler.go @@ -80,6 +80,8 @@ func unmarshalValue(x target, node *ast.Node) error { return unmarshalString(x, node) case ast.Array: return unmarshalArray(x, node) + case ast.InlineTable: + return unmarshalInlineTable(x, node) default: panic(fmt.Errorf("unhandled unmarshalValue kind %s", node.Kind)) } @@ -87,10 +89,21 @@ func unmarshalValue(x target, node *ast.Node) error { func unmarshalString(x target, node *ast.Node) error { assertNode(ast.String, node) - return x.setString(string(node.Data)) } +func unmarshalInlineTable(x target, node *ast.Node) error { + assertNode(ast.InlineTable, node) + + for _, kv := range node.Children { + err := unmarshalKeyValue(x, &kv) + if err != nil { + return err + } + } + return nil +} + func unmarshalArray(x target, node *ast.Node) error { assertNode(ast.Array, node) diff --git a/internal/unmarshaler/unmarshaler_test.go b/internal/unmarshaler/unmarshaler_test.go index 33b331d3..1759299f 100644 --- a/internal/unmarshaler/unmarshaler_test.go +++ b/internal/unmarshaler/unmarshaler_test.go @@ -137,6 +137,62 @@ func TestFromAst_Table(t *testing.T) { }) } +func TestFromAst_InlineTable(t *testing.T) { + t.Run("one level of strings", func(t *testing.T) { + // name = { first = "Tom", last = "Preston-Werner" } + + root := ast.Root{ + ast.Node{ + Kind: ast.KeyValue, + Children: []ast.Node{ + { + Kind: ast.Key, + Data: []byte(`Name`)}, + { + Kind: ast.InlineTable, + Children: []ast.Node{ + { + Kind: ast.KeyValue, + Children: []ast.Node{ + {Kind: ast.Key, Data: []byte(`First`)}, + {Kind: ast.String, Data: []byte(`Tom`)}, + }, + }, + { + Kind: ast.KeyValue, + Children: []ast.Node{ + {Kind: ast.Key, Data: []byte(`Last`)}, + {Kind: ast.String, Data: []byte(`Preston-Werner`)}, + }, + }, + }, + }, + }, + }, + } + + type Name struct { + First string + Last string + } + + type Doc struct { + Name Name + } + + x := Doc{} + err := fromAst(root, &x) + require.NoError(t, err) + assert.Equal(t, Doc{ + Name: Name{ + First: "Tom", + Last: "Preston-Werner", + }, + }, x) + + }) +} + func TestFromAst_Slice(t *testing.T) { t.Run("slice of string", func(t *testing.T) { root := ast.Root{ From 3760527218e88facbdec630f834b184f3c17a45c Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Sat, 13 Mar 2021 23:42:38 -0500 Subject: [PATCH 068/228] Unmarshal tests --- internal/unmarshaler/targets.go | 6 ++ internal/unmarshaler/unmarshaler_test.go | 96 ++++++++++++++++++++++++ 2 files changed, 102 insertions(+) diff --git a/internal/unmarshaler/targets.go b/internal/unmarshaler/targets.go index daff6bbe..a0e2fd15 100644 --- a/internal/unmarshaler/targets.go +++ b/internal/unmarshaler/targets.go @@ -100,6 +100,12 @@ func scope(v reflect.Value, name string) (target, error) { switch v.Kind() { case reflect.Struct: return scopeStruct(v, name) + case reflect.Interface: + if v.IsNil() { + panic("not implemented") // TODO + } else { + return scope(v.Elem(), name) + } default: panic(fmt.Errorf("can't scope on a %s", v.Kind())) } diff --git a/internal/unmarshaler/unmarshaler_test.go b/internal/unmarshaler/unmarshaler_test.go index 1759299f..900514c5 100644 --- a/internal/unmarshaler/unmarshaler_test.go +++ b/internal/unmarshaler/unmarshaler_test.go @@ -9,6 +9,102 @@ import ( "github.com/pelletier/go-toml/v2/internal/ast" ) +func TestUnmarshal(t *testing.T) { + type test struct { + target interface{} + expected interface{} + } + examples := []struct { + desc string + input string + gen func() test + }{ + { + desc: "kv string", + input: `A = "foo"`, + gen: func() test { + type doc struct { + A string + } + return test{ + &doc{}, + &doc{A: "foo"}, + } + }, + }, + { + desc: "string array", + input: `A = ["foo", "bar"]`, + gen: func() test { + type doc struct { + A []string + } + return test{ + &doc{}, + &doc{A: []string{"foo", "bar"}}, + } + }, + }, + { + desc: "inline table", + input: `Name = {First = "hello", Last = "world"}`, + gen: func() test { + type name struct { + First string + Last string + } + type doc struct { + Name name + } + return test{ + &doc{}, + &doc{Name: name{ + First: "hello", + Last: "world", + }}, + } + }, + }, + { + desc: "inline table inside array", + input: `Names = [{First = "hello", Last = "world"}, {First = "ab", Last = "cd"}]`, + gen: func() test { + type name struct { + First string + Last string + } + type doc struct { + Names []name + } + return test{ + &doc{}, + &doc{ + Names: []name{ + { + First: "hello", + Last: "world", + }, + { + First: "ab", + Last: "cd", + }, + }, + }, + } + }, + }, + } + + for _, e := range examples { + t.Run(e.desc, func(t *testing.T) { + test := e.gen() + err := Unmarshal([]byte(e.input), test.target) + require.NoError(t, err) + assert.Equal(t, test.expected, test.target) + }) + } +} + func TestFromAst_KV(t *testing.T) { root := ast.Root{ ast.Node{ From 04925e48825d10988742538ab8c0595c71e209b7 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Sun, 14 Mar 2021 15:52:22 -0400 Subject: [PATCH 069/228] Handle bools --- internal/unmarshaler/parser.go | 6 ++++-- internal/unmarshaler/parser_test.go | 19 +++++++++++++++++ internal/unmarshaler/targets.go | 18 ++++++++++++++++ internal/unmarshaler/unmarshaler.go | 8 ++++++++ internal/unmarshaler/unmarshaler_test.go | 26 ++++++++++++++++++++++++ 5 files changed, 75 insertions(+), 2 deletions(-) diff --git a/internal/unmarshaler/parser.go b/internal/unmarshaler/parser.go index 9d127f18..a2ba7f22 100644 --- a/internal/unmarshaler/parser.go +++ b/internal/unmarshaler/parser.go @@ -209,13 +209,15 @@ func (p *parser) parseVal(b []byte) (ast.Node, []byte, error) { if !scanFollowsTrue(b) { return node, nil, fmt.Errorf("expected 'true'") } - // TODO + node.Kind = ast.Bool + node.Data = b[:4] return node, b[4:], nil case 'f': if !scanFollowsFalse(b) { return node, nil, fmt.Errorf("expected 'false'") } - // TODO + node.Kind = ast.Bool + node.Data = b[:5] return node, b[5:], nil case '[': node.Kind = ast.Array diff --git a/internal/unmarshaler/parser_test.go b/internal/unmarshaler/parser_test.go index 0f2a71cc..3052d9ad 100644 --- a/internal/unmarshaler/parser_test.go +++ b/internal/unmarshaler/parser_test.go @@ -33,6 +33,25 @@ func TestParser_AST(t *testing.T) { }, }, }, + { + desc: "simple bool assignment", + input: `A = true`, + ast: ast.Root{ + ast.Node{ + Kind: ast.KeyValue, + Children: []ast.Node{ + { + Kind: ast.Key, + Data: []byte(`A`), + }, + { + Kind: ast.Bool, + Data: []byte(`true`), + }, + }, + }, + }, + }, { desc: "array of strings", input: `A = ["hello", ["world", "again"]]`, diff --git a/internal/unmarshaler/targets.go b/internal/unmarshaler/targets.go index a0e2fd15..6adaed69 100644 --- a/internal/unmarshaler/targets.go +++ b/internal/unmarshaler/targets.go @@ -12,6 +12,9 @@ type target interface { // Store a string at the target. setString(v string) error + // Store a boolean at the target + setBool(v bool) error + // Creates a new value of the container's element type, and returns a // target to it. pushNew() (target, error) @@ -65,6 +68,21 @@ func (t valueTarget) setString(v string) error { return nil } +func (t valueTarget) setBool(v bool) error { + f := t.get() + + switch f.Kind() { + case reflect.Bool: + f.SetBool(v) + case reflect.Interface: + f.Set(reflect.ValueOf(v)) + default: + return fmt.Errorf("cannot assign bool to a %s", f.String()) + } + + return nil +} + func (t valueTarget) pushNew() (target, error) { f := t.get() diff --git a/internal/unmarshaler/unmarshaler.go b/internal/unmarshaler/unmarshaler.go index 2f8c2abf..6b2a6650 100644 --- a/internal/unmarshaler/unmarshaler.go +++ b/internal/unmarshaler/unmarshaler.go @@ -78,6 +78,8 @@ func unmarshalValue(x target, node *ast.Node) error { switch node.Kind { case ast.String: return unmarshalString(x, node) + case ast.Bool: + return unmarshalBool(x, node) case ast.Array: return unmarshalArray(x, node) case ast.InlineTable: @@ -92,6 +94,12 @@ func unmarshalString(x target, node *ast.Node) error { return x.setString(string(node.Data)) } +func unmarshalBool(x target, node *ast.Node) error { + assertNode(ast.Bool, node) + v := node.Data[0] == 't' + return x.setBool(v) +} + func unmarshalInlineTable(x target, node *ast.Node) error { assertNode(ast.InlineTable, node) diff --git a/internal/unmarshaler/unmarshaler_test.go b/internal/unmarshaler/unmarshaler_test.go index 900514c5..79030e9f 100644 --- a/internal/unmarshaler/unmarshaler_test.go +++ b/internal/unmarshaler/unmarshaler_test.go @@ -32,6 +32,32 @@ func TestUnmarshal(t *testing.T) { } }, }, + { + desc: "kv bool true", + input: `A = true`, + gen: func() test { + type doc struct { + A bool + } + return test{ + &doc{}, + &doc{A: true}, + } + }, + }, + { + desc: "kv bool false", + input: `A = false`, + gen: func() test { + type doc struct { + A bool + } + return test{ + &doc{A: true}, + &doc{A: false}, + } + }, + }, { desc: "string array", input: `A = ["foo", "bar"]`, From de035f0fed50236dc7301b79e815f3989593daf6 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Sun, 14 Mar 2021 16:11:23 -0400 Subject: [PATCH 070/228] Standard tables in parser --- internal/unmarshaler/parser.go | 36 +++++++++++++----------- internal/unmarshaler/unmarshaler_test.go | 17 +++++++++++ 2 files changed, 37 insertions(+), 16 deletions(-) diff --git a/internal/unmarshaler/parser.go b/internal/unmarshaler/parser.go index a2ba7f22..50151664 100644 --- a/internal/unmarshaler/parser.go +++ b/internal/unmarshaler/parser.go @@ -71,7 +71,7 @@ func (p *parser) parseExpression(b []byte) ([]byte, error) { var err error var node ast.Node if b[0] == '[' { - b, err = p.parseTable(b) + node, b, err = p.parseTable(b) } else { node, b, err = p.parseKeyval(b) } @@ -91,7 +91,7 @@ func (p *parser) parseExpression(b []byte) ([]byte, error) { return b, nil } -func (p *parser) parseTable(b []byte) ([]byte, error) { +func (p *parser) parseTable(b []byte) (ast.Node, []byte, error) { //table = std-table / array-table if len(b) > 1 && b[1] == '[' { return p.parseArrayTable(b) @@ -99,7 +99,7 @@ func (p *parser) parseTable(b []byte) ([]byte, error) { return p.parseStdTable(b) } -func (p *parser) parseArrayTable(b []byte) ([]byte, error) { +func (p *parser) parseArrayTable(b []byte) (ast.Node, []byte, error) { //array-table = array-table-open key array-table-close //array-table-open = %x5B.5B ws ; [[ Double left square bracket //array-table-close = ws %x5D.5D ; ]] Double right square bracket @@ -118,26 +118,30 @@ func (p *parser) parseArrayTable(b []byte) ([]byte, error) { //} //return expect(']', b) - return nil, nil + return ast.NoNode, nil, nil } -func (p *parser) parseStdTable(b []byte) ([]byte, error) { +func (p *parser) parseStdTable(b []byte) (ast.Node, []byte, error) { //std-table = std-table-open key std-table-close //std-table-open = %x5B ws ; [ Left square bracket //std-table-close = ws %x5D ; ] Right square bracket - // TODO - //b = b[1:] - //b = p.parseWhitespace(b) - //b, err := p.parseKey(b) - //if err != nil { - // return nil, err - //} - //b = p.parseWhitespace(b) - // - //return expect(']', b) + node := ast.Node{ + Kind: ast.Table, + } - return nil, nil + b = b[1:] + b = p.parseWhitespace(b) + key, b, err := p.parseKey(b) + if err != nil { + return ast.NoNode, nil, err + } + node.Children = key + b = p.parseWhitespace(b) + + b, err = expect(']', b) + + return node, b, err } func (p *parser) parseKeyval(b []byte) (ast.Node, []byte, error) { diff --git a/internal/unmarshaler/unmarshaler_test.go b/internal/unmarshaler/unmarshaler_test.go index 79030e9f..d8212e79 100644 --- a/internal/unmarshaler/unmarshaler_test.go +++ b/internal/unmarshaler/unmarshaler_test.go @@ -71,6 +71,23 @@ func TestUnmarshal(t *testing.T) { } }, }, + { + desc: "standard table", + input: `[A] +B = "data"`, + gen: func() test { + type A struct { + B string + } + type doc struct { + A A + } + return test{ + &doc{}, + &doc{A: A{B: "data"}}, + } + }, + }, { desc: "inline table", input: `Name = {First = "hello", Last = "world"}`, From 590d7faf6508a5cc901f0012be6b7bf47fa57d73 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Sun, 14 Mar 2021 16:16:29 -0400 Subject: [PATCH 071/228] Parser emits AST node for all kinds of strings --- internal/unmarshaler/parser.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/internal/unmarshaler/parser.go b/internal/unmarshaler/parser.go index 50151664..323adee4 100644 --- a/internal/unmarshaler/parser.go +++ b/internal/unmarshaler/parser.go @@ -184,7 +184,6 @@ func (p *parser) parseVal(b []byte) (ast.Node, []byte, error) { c := b[0] switch c { - // strings case '"': var v []byte if scanFollowsMultilineBasicStringDelimiter(b) { @@ -205,8 +204,8 @@ func (p *parser) parseVal(b []byte) (ast.Node, []byte, error) { v, b, err = p.parseLiteralString(b) } if err == nil { - // TODO - v = v + node.Kind = ast.String + node.Data = v } return node, b, err case 't': From 9a1cfcdd8e465c0f38d67dabdf5cc4824c0dfc7a Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Sun, 14 Mar 2021 17:22:53 -0400 Subject: [PATCH 072/228] Replace parser's int or float code with scanner --- internal/unmarshaler/parser.go | 305 +++++++++++++++++----------- internal/unmarshaler/parser_test.go | 134 ++++++++++++ 2 files changed, 321 insertions(+), 118 deletions(-) diff --git a/internal/unmarshaler/parser.go b/internal/unmarshaler/parser.go index 323adee4..a9708bb6 100644 --- a/internal/unmarshaler/parser.go +++ b/internal/unmarshaler/parser.go @@ -231,8 +231,8 @@ func (p *parser) parseVal(b []byte) (ast.Node, []byte, error) { b, err := p.parseInlineTable(&node, b) return node, b, err default: - // TODO - //return p.parseIntOrFloatOrDateTime(b) + b, err = p.parseIntOrFloatOrDateTime(&node, b) + return node, b, err } panic("parseVal not finished yet") return ast.Node{}, nil, nil @@ -614,28 +614,28 @@ func (p *parser) parseWhitespace(b []byte) []byte { return rest } -func (p *parser) parseIntOrFloatOrDateTime(b []byte) ([]byte, error) { +func (p *parser) parseIntOrFloatOrDateTime(node *ast.Node, b []byte) ([]byte, error) { switch b[0] { case 'i': if !scanFollowsInf(b) { return nil, fmt.Errorf("expected 'inf'") } - //p.builder.FloatValue(math.Inf(1)) - // TODO + node.Kind = ast.Float + node.Data = b[:3] return b[3:], nil case 'n': if !scanFollowsNan(b) { return nil, fmt.Errorf("expected 'nan'") } - //p.builder.FloatValue(math.NaN()) - // TODO + node.Kind = ast.Float + node.Data = b[:3] return b[3:], nil case '+', '-': - return p.parseIntOrFloat(b) + return p.scanIntOrFloat(node, b) } if len(b) < 3 { - return p.parseIntOrFloat(b) + return p.scanIntOrFloat(node, b) } s := 5 if len(b) < s { @@ -652,7 +652,7 @@ func (p *parser) parseIntOrFloatOrDateTime(b []byte) ([]byte, error) { return p.parseDateTime(b) } } - return p.parseIntOrFloat(b) + return p.scanIntOrFloat(node, b) } func digitsToInt(b []byte) int { @@ -925,135 +925,204 @@ func (p *parser) parseTime(b []byte) ([]byte, error) { return b[idx:], nil } -func (p *parser) parseIntOrFloat(b []byte) ([]byte, error) { +func (p *parser) scanIntOrFloat(node *ast.Node, b []byte) ([]byte, error) { i := 0 - r := b[0] - if r == '0' { - if len(b) >= 2 { - var isValidRune validRuneFn - var parseFn func([]byte) (int64, error) - switch b[1] { - case 'x': - isValidRune = isValidHexRune - parseFn = parseIntHex - case 'o': - isValidRune = isValidOctalRune - parseFn = parseIntOct - case 'b': - isValidRune = isValidBinaryRune - parseFn = parseIntBin - default: - if b[1] >= 'a' && b[1] <= 'z' || b[1] >= 'A' && b[1] <= 'Z' { - return nil, fmt.Errorf("unknown number base: %s. possible options are x (hex) o (octal) b (binary)", string(b[1])) - } - parseFn = parseIntDec - } - - if isValidRune != nil { - i = 2 - digitSeen := false - for { - if !isValidRune(b[i]) { - break - } - digitSeen = true - i++ - } - if !digitSeen { - return nil, fmt.Errorf("number needs at least one digit") - } + if len(b) > 2 && b[0] == '0' { + var isValidRune validRuneFn + switch b[1] { + case 'x': + isValidRune = isValidHexRune + case 'o': + isValidRune = isValidOctalRune + case 'b': + isValidRune = isValidBinaryRune + default: + return b, fmt.Errorf("unknown number base: %c. possible options are x (hex) o (octal) b (binary)", b[1]) + } - v, err := parseFn(b[:i]) - if err != nil { - return nil, err - } - //p.builder.IntValue(v) - // TODO - v = v + i += 2 + for ; i < len(b); i++ { + if !isValidRune(b[i]) { + node.Kind = ast.Integer + node.Data = b[:i] return b[i:], nil } } } - if r == '+' || r == '-' { - b = b[1:] - if scanFollowsInf(b) { - if r == '+' { - //p.builder.FloatValue(plusInf) - // TODO - } else { - //p.builder.FloatValue(minusInf) - // TODO - } - return b, nil + isFloat := false + + for ; i < len(b); i++ { + c := b[i] + + if c >= '0' && c <= '9' || c == '+' || c == '-' || c == '_' { + continue } - if scanFollowsNan(b) { - //p.builder.FloatValue(nan) - // TODO - return b, nil + + if c == '.' || c == 'e' || c == 'E' { + isFloat = true + continue } - } - pointSeen := false - expSeen := false - digitSeen := false - for i < len(b) { - next := b[i] - if next == '.' { - if pointSeen { - return nil, fmt.Errorf("cannot have two dots in one float") - } - i++ - if i < len(b) && !isDigit(b[i]) { - return nil, fmt.Errorf("float cannot end with a dot") - } - pointSeen = true - } else if next == 'e' || next == 'E' { - expSeen = true - i++ - if i >= len(b) { - break + if c == 'i' { + if scanFollowsInf(b[i:]) { + node.Kind = ast.Float + node.Data = b[:i+3] + return b[i+3:], nil } - if b[i] == '+' || b[i] == '-' { - i++ - } - } else if isDigit(next) { - digitSeen = true - i++ - } else if next == '_' { - i++ - } else { - break + return nil, fmt.Errorf("unexpected character i while scanning for a number") } - if pointSeen && !digitSeen { - return nil, fmt.Errorf("cannot start float with a dot") + if c == 'n' { + if scanFollowsNan(b[i:]) { + node.Kind = ast.Float + node.Data = b[:i+3] + return b[i+3:], nil + } + return nil, fmt.Errorf("unexpected character n while scanning for a number") } - } - if !digitSeen { - return nil, fmt.Errorf("no digit in that number") + break } - if pointSeen || expSeen { - f, err := parseFloat(b[:i]) - if err != nil { - return nil, err - } - //p.builder.FloatValue(f) - // TODO - f = f + + if isFloat { + node.Kind = ast.Float } else { - v, err := parseIntDec(b[:i]) - if err != nil { - return nil, err - } - //p.builder.IntValue(v) - // TODO - v = v + node.Kind = ast.Integer } + node.Data = b[:i] return b[i:], nil } +//func (p *parser) parseIntOrFloat(node *ast.Node, b []byte) ([]byte, error) { +// i := 0 +// r := b[0] +// if r == '0' { +// if len(b) >= 2 { +// var isValidRune validRuneFn +// var parseFn func([]byte) (int64, error) +// switch b[1] { +// case 'x': +// isValidRune = isValidHexRune +// parseFn = parseIntHex +// case 'o': +// isValidRune = isValidOctalRune +// parseFn = parseIntOct +// case 'b': +// isValidRune = isValidBinaryRune +// parseFn = parseIntBin +// default: +// if b[1] >= 'a' && b[1] <= 'z' || b[1] >= 'A' && b[1] <= 'Z' { +// return nil, fmt.Errorf("unknown number base: %s. possible options are x (hex) o (octal) b (binary)", string(b[1])) +// } +// parseFn = parseIntDec +// } +// +// if isValidRune != nil { +// i = 2 +// digitSeen := false +// for { +// if !isValidRune(b[i]) { +// break +// } +// digitSeen = true +// i++ +// } +// +// if !digitSeen { +// return nil, fmt.Errorf("number needs at least one digit") +// } +// +// v, err := parseFn(b[:i]) +// if err != nil { +// return nil, err +// } +// //p.builder.IntValue(v) +// // TODO +// v = v +// return b[i:], nil +// } +// } +// } +// +// if r == '+' || r == '-' { +// b = b[1:] +// if scanFollowsInf(b) { +// if r == '+' { +// //p.builder.FloatValue(plusInf) +// // TODO +// } else { +// //p.builder.FloatValue(minusInf) +// // TODO +// } +// return b, nil +// } +// if scanFollowsNan(b) { +// //p.builder.FloatValue(nan) +// // TODO +// return b, nil +// } +// } +// +// pointSeen := false +// expSeen := false +// digitSeen := false +// for i < len(b) { +// next := b[i] +// if next == '.' { +// if pointSeen { +// return nil, fmt.Errorf("cannot have two dots in one float") +// } +// i++ +// if i < len(b) && !isDigit(b[i]) { +// return nil, fmt.Errorf("float cannot end with a dot") +// } +// pointSeen = true +// } else if next == 'e' || next == 'E' { +// expSeen = true +// i++ +// if i >= len(b) { +// break +// } +// if b[i] == '+' || b[i] == '-' { +// i++ +// } +// } else if isDigit(next) { +// digitSeen = true +// i++ +// } else if next == '_' { +// i++ +// } else { +// break +// } +// if pointSeen && !digitSeen { +// return nil, fmt.Errorf("cannot start float with a dot") +// } +// } +// +// if !digitSeen { +// return nil, fmt.Errorf("no digit in that number") +// } +// if pointSeen || expSeen { +// f, err := parseFloat(b[:i]) +// if err != nil { +// return nil, err +// } +// //p.builder.FloatValue(f) +// // TODO +// f = f +// } else { +// v, err := parseIntDec(b[:i]) +// if err != nil { +// return nil, err +// } +// //p.builder.IntValue(v) +// // TODO +// v = v +// } +// return b[i:], nil +//} + func parseFloat(b []byte) (float64, error) { // TODO: inefficient tok := string(b) diff --git a/internal/unmarshaler/parser_test.go b/internal/unmarshaler/parser_test.go index 3052d9ad..250da3e5 100644 --- a/internal/unmarshaler/parser_test.go +++ b/internal/unmarshaler/parser_test.go @@ -7,6 +7,140 @@ import ( "github.com/stretchr/testify/require" ) +func TestParser_Numbers(t *testing.T) { + examples := []struct { + desc string + input string + kind ast.Kind + err bool + }{ + { + desc: "integer just digits", + input: `1234`, + kind: ast.Integer, + }, + { + desc: "integer zero", + input: `0`, + kind: ast.Integer, + }, + { + desc: "integer sign", + input: `+99`, + kind: ast.Integer, + }, + { + desc: "integer hex uppercase", + input: `0xDEADBEEF`, + kind: ast.Integer, + }, + { + desc: "integer hex lowercase", + input: `0xdead_beef`, + kind: ast.Integer, + }, + { + desc: "integer octal", + input: `0o01234567`, + kind: ast.Integer, + }, + { + desc: "integer binary", + input: `0b11010110`, + kind: ast.Integer, + }, + { + desc: "float pi", + input: `3.1415`, + kind: ast.Float, + }, + { + desc: "float negative", + input: `-0.01`, + kind: ast.Float, + }, + { + desc: "float signed exponent", + input: `5e+22`, + kind: ast.Float, + }, + { + desc: "float exponent lowercase", + input: `1e06`, + kind: ast.Float, + }, + { + desc: "float exponent uppercase", + input: `-2E-2`, + kind: ast.Float, + }, + { + desc: "float fractional with exponent", + input: `6.626e-34`, + kind: ast.Float, + }, + { + desc: "float underscores", + input: `224_617.445_991_228`, + kind: ast.Float, + }, + { + desc: "inf", + input: `inf`, + kind: ast.Float, + }, + { + desc: "inf negative", + input: `-inf`, + kind: ast.Float, + }, + { + desc: "inf positive", + input: `+inf`, + kind: ast.Float, + }, + { + desc: "nan", + input: `nan`, + kind: ast.Float, + }, + { + desc: "nan negative", + input: `-nan`, + kind: ast.Float, + }, + { + desc: "nan positive", + input: `+nan`, + kind: ast.Float, + }, + } + + for _, e := range examples { + t.Run(e.desc, func(t *testing.T) { + p := parser{} + err := p.parse([]byte(`A = ` + e.input)) + if e.err { + require.Error(t, err) + } else { + require.NoError(t, err) + + expected := ast.Root{ + ast.Node{ + Kind: ast.KeyValue, + Children: []ast.Node{ + {Kind: ast.Key, Data: []byte(`A`)}, + {Kind: e.kind, Data: []byte(e.input)}, + }, + }, + } + + require.Equal(t, expected, p.tree) + } + }) + } +} + func TestParser_AST(t *testing.T) { examples := []struct { desc string From 590d6741532edc0dbe5ff1380ae29741c8236646 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Sun, 14 Mar 2021 18:06:34 -0400 Subject: [PATCH 073/228] Unmarshal ints and floats --- internal/ast/ast.go | 38 +++- internal/ast/decode.go | 113 +++++++++++ internal/unmarshaler/parser.go | 241 ----------------------- internal/unmarshaler/parser_test.go | 2 +- internal/unmarshaler/targets.go | 38 ++++ internal/unmarshaler/unmarshaler.go | 22 +++ internal/unmarshaler/unmarshaler_test.go | 159 +++++++++++++++ 7 files changed, 368 insertions(+), 245 deletions(-) create mode 100644 internal/ast/decode.go diff --git a/internal/ast/ast.go b/internal/ast/ast.go index 25a89be7..eed8675a 100644 --- a/internal/ast/ast.go +++ b/internal/ast/ast.go @@ -158,11 +158,43 @@ func (n *Node) Key() []Node { // Guaranteed to be non-nil. // Panics if not called on a KeyValue node, or if the Children are malformed. func (n *Node) Value() *Node { - if n.Kind != KeyValue { - panic(fmt.Errorf("Key() should only be called on on a KeyValue, not %s", n.Kind)) - } + assertKind(KeyValue, n) if len(n.Children) < 2 { panic(fmt.Errorf("KeyValue should have at least two children, not %d", len(n.Children))) } return &n.Children[len(n.Children)-1] } + +// DecodeInteger parse the data of an Integer node and returns the represented +// int64, or an error. +// Panics if not called on an Integer node. +func (n *Node) DecodeInteger() (int64, error) { + assertKind(Integer, n) + if len(n.Data) > 2 && n.Data[0] == '0' { + switch n.Data[1] { + case 'x': + return parseIntHex(n.Data) + case 'b': + return parseIntBin(n.Data) + case 'o': + return parseIntOct(n.Data) + default: + return 0, fmt.Errorf("invalid base: '%c'", n.Data[1]) + } + } + return parseIntDec(n.Data) +} + +// DecodeFloat parse the data of a Float node and returns the represented +// float64, or an error. +// Panics if not called on an Float node. +func (n *Node) DecodeFloat() (float64, error) { + assertKind(Float, n) + return parseFloat(n.Data) +} + +func assertKind(k Kind, n *Node) { + if n.Kind != k { + panic(fmt.Errorf("method was expecting a %s, not a %s", k, n.Kind)) + } +} diff --git a/internal/ast/decode.go b/internal/ast/decode.go new file mode 100644 index 00000000..a27f04ce --- /dev/null +++ b/internal/ast/decode.go @@ -0,0 +1,113 @@ +package ast + +import ( + "errors" + "math" + "strconv" + "strings" +) + +func parseFloat(b []byte) (float64, error) { + // TODO: inefficient + if len(b) == 4 && (b[0] == '+' || b[0] == '-') && b[1] == 'n' && b[2] == 'a' && b[3] == 'n' { + return math.NaN(), nil + } + + tok := string(b) + err := numberContainsInvalidUnderscore(tok) + if err != nil { + return 0, err + } + cleanedVal := cleanupNumberToken(tok) + return strconv.ParseFloat(cleanedVal, 64) +} + +func parseIntHex(b []byte) (int64, error) { + tok := string(b) + cleanedVal := cleanupNumberToken(tok) + err := hexNumberContainsInvalidUnderscore(cleanedVal) + if err != nil { + return 0, nil + } + return strconv.ParseInt(cleanedVal[2:], 16, 64) +} + +func parseIntOct(b []byte) (int64, error) { + tok := string(b) + cleanedVal := cleanupNumberToken(tok) + err := numberContainsInvalidUnderscore(cleanedVal) + if err != nil { + return 0, err + } + return strconv.ParseInt(cleanedVal[2:], 8, 64) +} + +func parseIntBin(b []byte) (int64, error) { + tok := string(b) + cleanedVal := cleanupNumberToken(tok) + err := numberContainsInvalidUnderscore(cleanedVal) + if err != nil { + return 0, err + } + return strconv.ParseInt(cleanedVal[2:], 2, 64) +} + +func parseIntDec(b []byte) (int64, error) { + tok := string(b) + cleanedVal := cleanupNumberToken(tok) + err := numberContainsInvalidUnderscore(cleanedVal) + if err != nil { + return 0, err + } + return strconv.ParseInt(cleanedVal, 10, 64) +} + +func numberContainsInvalidUnderscore(value string) error { + // For large numbers, you may use underscores between digits to enhance + // readability. Each underscore must be surrounded by at least one digit on + // each side. + + hasBefore := false + for idx, r := range value { + if r == '_' { + if !hasBefore || idx+1 >= len(value) { + // can't end with an underscore + return errInvalidUnderscore + } + } + hasBefore = isDigitRune(r) + } + return nil +} + +func hexNumberContainsInvalidUnderscore(value string) error { + hasBefore := false + for idx, r := range value { + if r == '_' { + if !hasBefore || idx+1 >= len(value) { + // can't end with an underscore + return errInvalidUnderscoreHex + } + } + hasBefore = isHexDigit(r) + } + return nil +} + +func cleanupNumberToken(value string) string { + cleanedVal := strings.Replace(value, "_", "", -1) + return cleanedVal +} + +func isHexDigit(r rune) bool { + return isDigitRune(r) || + (r >= 'a' && r <= 'f') || + (r >= 'A' && r <= 'F') +} + +func isDigitRune(r rune) bool { + return r >= '0' && r <= '9' +} + +var errInvalidUnderscore = errors.New("invalid use of _ in number") +var errInvalidUnderscoreHex = errors.New("invalid use of _ in hex number") diff --git a/internal/unmarshaler/parser.go b/internal/unmarshaler/parser.go index a9708bb6..f59ecad4 100644 --- a/internal/unmarshaler/parser.go +++ b/internal/unmarshaler/parser.go @@ -3,15 +3,10 @@ package unmarshaler import ( "bytes" "encoding/hex" - "errors" "fmt" - "math" - "strconv" - "strings" "time" "github.com/pelletier/go-toml/v2" - "github.com/pelletier/go-toml/v2/internal/ast" ) @@ -234,8 +229,6 @@ func (p *parser) parseVal(b []byte) (ast.Node, []byte, error) { b, err = p.parseIntOrFloatOrDateTime(&node, b) return node, b, err } - panic("parseVal not finished yet") - return ast.Node{}, nil, nil } func (p *parser) parseLiteralString(b []byte) ([]byte, []byte, error) { @@ -994,235 +987,10 @@ func (p *parser) scanIntOrFloat(node *ast.Node, b []byte) ([]byte, error) { return b[i:], nil } -//func (p *parser) parseIntOrFloat(node *ast.Node, b []byte) ([]byte, error) { -// i := 0 -// r := b[0] -// if r == '0' { -// if len(b) >= 2 { -// var isValidRune validRuneFn -// var parseFn func([]byte) (int64, error) -// switch b[1] { -// case 'x': -// isValidRune = isValidHexRune -// parseFn = parseIntHex -// case 'o': -// isValidRune = isValidOctalRune -// parseFn = parseIntOct -// case 'b': -// isValidRune = isValidBinaryRune -// parseFn = parseIntBin -// default: -// if b[1] >= 'a' && b[1] <= 'z' || b[1] >= 'A' && b[1] <= 'Z' { -// return nil, fmt.Errorf("unknown number base: %s. possible options are x (hex) o (octal) b (binary)", string(b[1])) -// } -// parseFn = parseIntDec -// } -// -// if isValidRune != nil { -// i = 2 -// digitSeen := false -// for { -// if !isValidRune(b[i]) { -// break -// } -// digitSeen = true -// i++ -// } -// -// if !digitSeen { -// return nil, fmt.Errorf("number needs at least one digit") -// } -// -// v, err := parseFn(b[:i]) -// if err != nil { -// return nil, err -// } -// //p.builder.IntValue(v) -// // TODO -// v = v -// return b[i:], nil -// } -// } -// } -// -// if r == '+' || r == '-' { -// b = b[1:] -// if scanFollowsInf(b) { -// if r == '+' { -// //p.builder.FloatValue(plusInf) -// // TODO -// } else { -// //p.builder.FloatValue(minusInf) -// // TODO -// } -// return b, nil -// } -// if scanFollowsNan(b) { -// //p.builder.FloatValue(nan) -// // TODO -// return b, nil -// } -// } -// -// pointSeen := false -// expSeen := false -// digitSeen := false -// for i < len(b) { -// next := b[i] -// if next == '.' { -// if pointSeen { -// return nil, fmt.Errorf("cannot have two dots in one float") -// } -// i++ -// if i < len(b) && !isDigit(b[i]) { -// return nil, fmt.Errorf("float cannot end with a dot") -// } -// pointSeen = true -// } else if next == 'e' || next == 'E' { -// expSeen = true -// i++ -// if i >= len(b) { -// break -// } -// if b[i] == '+' || b[i] == '-' { -// i++ -// } -// } else if isDigit(next) { -// digitSeen = true -// i++ -// } else if next == '_' { -// i++ -// } else { -// break -// } -// if pointSeen && !digitSeen { -// return nil, fmt.Errorf("cannot start float with a dot") -// } -// } -// -// if !digitSeen { -// return nil, fmt.Errorf("no digit in that number") -// } -// if pointSeen || expSeen { -// f, err := parseFloat(b[:i]) -// if err != nil { -// return nil, err -// } -// //p.builder.FloatValue(f) -// // TODO -// f = f -// } else { -// v, err := parseIntDec(b[:i]) -// if err != nil { -// return nil, err -// } -// //p.builder.IntValue(v) -// // TODO -// v = v -// } -// return b[i:], nil -//} - -func parseFloat(b []byte) (float64, error) { - // TODO: inefficient - tok := string(b) - err := numberContainsInvalidUnderscore(tok) - if err != nil { - return 0, err - } - cleanedVal := cleanupNumberToken(tok) - return strconv.ParseFloat(cleanedVal, 64) -} - -func parseIntHex(b []byte) (int64, error) { - tok := string(b) - cleanedVal := cleanupNumberToken(tok) - err := hexNumberContainsInvalidUnderscore(cleanedVal) - if err != nil { - return 0, nil - } - return strconv.ParseInt(cleanedVal[2:], 16, 64) -} - -func parseIntOct(b []byte) (int64, error) { - tok := string(b) - cleanedVal := cleanupNumberToken(tok) - err := numberContainsInvalidUnderscore(cleanedVal) - if err != nil { - return 0, err - } - return strconv.ParseInt(cleanedVal[2:], 8, 64) -} - -func parseIntBin(b []byte) (int64, error) { - tok := string(b) - cleanedVal := cleanupNumberToken(tok) - err := numberContainsInvalidUnderscore(cleanedVal) - if err != nil { - return 0, err - } - return strconv.ParseInt(cleanedVal[2:], 2, 64) -} - -func parseIntDec(b []byte) (int64, error) { - tok := string(b) - cleanedVal := cleanupNumberToken(tok) - err := numberContainsInvalidUnderscore(cleanedVal) - if err != nil { - return 0, err - } - return strconv.ParseInt(cleanedVal, 10, 64) -} - -func numberContainsInvalidUnderscore(value string) error { - // For large numbers, you may use underscores between digits to enhance - // readability. Each underscore must be surrounded by at least one digit on - // each side. - - hasBefore := false - for idx, r := range value { - if r == '_' { - if !hasBefore || idx+1 >= len(value) { - // can't end with an underscore - return errInvalidUnderscore - } - } - hasBefore = isDigitRune(r) - } - return nil -} - -func hexNumberContainsInvalidUnderscore(value string) error { - hasBefore := false - for idx, r := range value { - if r == '_' { - if !hasBefore || idx+1 >= len(value) { - // can't end with an underscore - return errInvalidUnderscoreHex - } - } - hasBefore = isHexDigit(r) - } - return nil -} - -func cleanupNumberToken(value string) string { - cleanedVal := strings.Replace(value, "_", "", -1) - return cleanedVal -} - func isDigit(r byte) bool { return r >= '0' && r <= '9' } -func isDigitRune(r rune) bool { - return r >= '0' && r <= '9' -} - -var plusInf = math.Inf(1) -var minusInf = math.Inf(-1) -var nan = math.NaN() - type validRuneFn func(r byte) bool func isValidHexRune(r byte) bool { @@ -1232,12 +1000,6 @@ func isValidHexRune(r byte) bool { r == '_' } -func isHexDigit(r rune) bool { - return isDigitRune(r) || - (r >= 'a' && r <= 'f') || - (r >= 'A' && r <= 'F') -} - func isValidOctalRune(r byte) bool { return r >= '0' && r <= '7' || r == '_' } @@ -1265,6 +1027,3 @@ func (u unexpectedCharacter) Error() string { } return fmt.Sprintf("expected %#U, not %#U", u.r, u.b[0]) } - -var errInvalidUnderscore = errors.New("invalid use of _ in number") -var errInvalidUnderscoreHex = errors.New("invalid use of _ in hex number") diff --git a/internal/unmarshaler/parser_test.go b/internal/unmarshaler/parser_test.go index 250da3e5..b7aabfa7 100644 --- a/internal/unmarshaler/parser_test.go +++ b/internal/unmarshaler/parser_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/require" ) -func TestParser_Numbers(t *testing.T) { +func TestParser_AST_Numbers(t *testing.T) { examples := []struct { desc string input string diff --git a/internal/unmarshaler/targets.go b/internal/unmarshaler/targets.go index 6adaed69..af3c5c74 100644 --- a/internal/unmarshaler/targets.go +++ b/internal/unmarshaler/targets.go @@ -15,6 +15,12 @@ type target interface { // Store a boolean at the target setBool(v bool) error + // Store an int64 at the target + setInt64(v int64) error + + // Store a float64 at the target + setFloat64(v float64) error + // Creates a new value of the container's element type, and returns a // target to it. pushNew() (target, error) @@ -83,6 +89,38 @@ func (t valueTarget) setBool(v bool) error { return nil } +func (t valueTarget) setInt64(v int64) error { + f := t.get() + + switch f.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + // TODO: overflow checks + f.SetInt(v) + case reflect.Interface: + f.Set(reflect.ValueOf(v)) + default: + return fmt.Errorf("cannot assign int64 to a %s", f.String()) + } + + return nil +} + +func (t valueTarget) setFloat64(v float64) error { + f := t.get() + + switch f.Kind() { + case reflect.Float32, reflect.Float64: + // TODO: overflow checks + f.SetFloat(v) + case reflect.Interface: + f.Set(reflect.ValueOf(v)) + default: + return fmt.Errorf("cannot assign float64 to a %s", f.String()) + } + + return nil +} + func (t valueTarget) pushNew() (target, error) { f := t.get() diff --git a/internal/unmarshaler/unmarshaler.go b/internal/unmarshaler/unmarshaler.go index 6b2a6650..ab63b896 100644 --- a/internal/unmarshaler/unmarshaler.go +++ b/internal/unmarshaler/unmarshaler.go @@ -80,6 +80,10 @@ func unmarshalValue(x target, node *ast.Node) error { return unmarshalString(x, node) case ast.Bool: return unmarshalBool(x, node) + case ast.Integer: + return unmarshalInteger(x, node) + case ast.Float: + return unmarshalFloat(x, node) case ast.Array: return unmarshalArray(x, node) case ast.InlineTable: @@ -100,6 +104,24 @@ func unmarshalBool(x target, node *ast.Node) error { return x.setBool(v) } +func unmarshalInteger(x target, node *ast.Node) error { + assertNode(ast.Integer, node) + v, err := node.DecodeInteger() + if err != nil { + return err + } + return x.setInt64(v) +} + +func unmarshalFloat(x target, node *ast.Node) error { + assertNode(ast.Float, node) + v, err := node.DecodeFloat() + if err != nil { + return err + } + return x.setFloat64(v) +} + func unmarshalInlineTable(x target, node *ast.Node) error { assertNode(ast.InlineTable, node) diff --git a/internal/unmarshaler/unmarshaler_test.go b/internal/unmarshaler/unmarshaler_test.go index d8212e79..4c45e4ec 100644 --- a/internal/unmarshaler/unmarshaler_test.go +++ b/internal/unmarshaler/unmarshaler_test.go @@ -1,6 +1,7 @@ package unmarshaler import ( + "math" "testing" "github.com/stretchr/testify/assert" @@ -9,6 +10,164 @@ import ( "github.com/pelletier/go-toml/v2/internal/ast" ) +func TestUnmarshal_Integers(t *testing.T) { + examples := []struct { + desc string + input string + expected int64 + err bool + }{ + { + desc: "integer just digits", + input: `1234`, + expected: 1234, + }, + { + desc: "integer zero", + input: `0`, + expected: 0, + }, + { + desc: "integer sign", + input: `+99`, + expected: 99, + }, + { + desc: "integer hex uppercase", + input: `0xDEADBEEF`, + expected: 0xDEADBEEF, + }, + { + desc: "integer hex lowercase", + input: `0xdead_beef`, + expected: 0xDEADBEEF, + }, + { + desc: "integer octal", + input: `0o01234567`, + expected: 0o01234567, + }, + { + desc: "integer binary", + input: `0b11010110`, + expected: 0b11010110, + }, + } + + type doc struct { + A int64 + } + + for _, e := range examples { + t.Run(e.desc, func(t *testing.T) { + doc := doc{} + err := Unmarshal([]byte(`A = `+e.input), &doc) + require.NoError(t, err) + assert.Equal(t, e.expected, doc.A) + }) + } +} + +func TestUnmarshal_Floats(t *testing.T) { + examples := []struct { + desc string + input string + expected float64 + testFn func(t *testing.T, v float64) + err bool + }{ + + { + desc: "float pi", + input: `3.1415`, + expected: 3.1415, + }, + { + desc: "float negative", + input: `-0.01`, + expected: -0.01, + }, + { + desc: "float signed exponent", + input: `5e+22`, + expected: 5e+22, + }, + { + desc: "float exponent lowercase", + input: `1e06`, + expected: 1e06, + }, + { + desc: "float exponent uppercase", + input: `-2E-2`, + expected: -2e-2, + }, + { + desc: "float fractional with exponent", + input: `6.626e-34`, + expected: 6.626e-34, + }, + { + desc: "float underscores", + input: `224_617.445_991_228`, + expected: 224_617.445_991_228, + }, + { + desc: "inf", + input: `inf`, + expected: math.Inf(+1), + }, + { + desc: "inf negative", + input: `-inf`, + expected: math.Inf(-1), + }, + { + desc: "inf positive", + input: `+inf`, + expected: math.Inf(+1), + }, + { + desc: "nan", + input: `nan`, + testFn: func(t *testing.T, v float64) { + assert.True(t, math.IsNaN(v)) + }, + }, + { + desc: "nan negative", + input: `-nan`, + testFn: func(t *testing.T, v float64) { + assert.True(t, math.IsNaN(v)) + }, + }, + { + desc: "nan positive", + input: `+nan`, + testFn: func(t *testing.T, v float64) { + assert.True(t, math.IsNaN(v)) + }, + }, + } + + type doc struct { + A float64 + } + + for _, e := range examples { + t.Run(e.desc, func(t *testing.T) { + doc := doc{} + err := Unmarshal([]byte(`A = `+e.input), &doc) + require.NoError(t, err) + if e.testFn != nil { + e.testFn(t, doc.A) + } else { + assert.Equal(t, e.expected, doc.A) + } + }) + } +} + func TestUnmarshal(t *testing.T) { type test struct { target interface{} From 16a336b4f334a60b6260109e1b41b460514dde98 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Sun, 14 Mar 2021 18:10:59 -0400 Subject: [PATCH 074/228] Remove todos that don't make sense anymore --- scanner.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/scanner.go b/scanner.go index 9ee67faf..36a45917 100644 --- a/scanner.go +++ b/scanner.go @@ -113,7 +113,6 @@ func scanComment(b []byte) ([]byte, []byte, error) { return b, nil, nil } -// TODO perform validation on the string? func scanBasicString(b []byte) ([]byte, []byte, error) { //basic-string = quotation-mark *basic-char quotation-mark //quotation-mark = %x22 ; " @@ -137,7 +136,6 @@ func scanBasicString(b []byte) ([]byte, []byte, error) { return nil, nil, fmt.Errorf(`basic string not terminated by "`) } -// TODO perform validation on the string? func scanMultilineBasicString(b []byte) ([]byte, []byte, error) { //ml-basic-string = ml-basic-string-delim [ newline ] ml-basic-body //ml-basic-string-delim From b8df31de8474d1285eed285c0958daabdea96aa5 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Sun, 14 Mar 2021 18:13:57 -0400 Subject: [PATCH 075/228] Comment out date/time tests for now --- .../imported_tests/unmarshal_imported_test.go | 411 +++++++++--------- 1 file changed, 206 insertions(+), 205 deletions(-) diff --git a/internal/imported_tests/unmarshal_imported_test.go b/internal/imported_tests/unmarshal_imported_test.go index 6a268e76..ee817731 100644 --- a/internal/imported_tests/unmarshal_imported_test.go +++ b/internal/imported_tests/unmarshal_imported_test.go @@ -14,7 +14,7 @@ import ( "testing" "time" - "github.com/pelletier/go-toml/v2" + toml "github.com/pelletier/go-toml/v2/internal/unmarshaler" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -1379,210 +1379,211 @@ func TestUnmarshalPreservesUnexportedFields(t *testing.T) { }) } -func TestUnmarshalLocalDate(t *testing.T) { - t.Run("ToLocalDate", func(t *testing.T) { - type dateStruct struct { - Date toml.LocalDate - } - - doc := `date = 1979-05-27` - - var obj dateStruct - - err := toml.Unmarshal([]byte(doc), &obj) - - if err != nil { - t.Fatal(err) - } - - if obj.Date.Year != 1979 { - t.Errorf("expected year 1979, got %d", obj.Date.Year) - } - if obj.Date.Month != 5 { - t.Errorf("expected month 5, got %d", obj.Date.Month) - } - if obj.Date.Day != 27 { - t.Errorf("expected day 27, got %d", obj.Date.Day) - } - }) - - t.Run("ToLocalDate", func(t *testing.T) { - type dateStruct struct { - Date time.Time - } - - doc := `date = 1979-05-27` - - var obj dateStruct - - err := toml.Unmarshal([]byte(doc), &obj) - - if err != nil { - t.Fatal(err) - } - - if obj.Date.Year() != 1979 { - t.Errorf("expected year 1979, got %d", obj.Date.Year()) - } - if obj.Date.Month() != 5 { - t.Errorf("expected month 5, got %d", obj.Date.Month()) - } - if obj.Date.Day() != 27 { - t.Errorf("expected day 27, got %d", obj.Date.Day()) - } - }) -} - -func TestUnmarshalLocalDateTime(t *testing.T) { - examples := []struct { - name string - in string - out toml.LocalDateTime - }{ - { - name: "normal", - in: "1979-05-27T07:32:00", - out: toml.LocalDateTime{ - Date: toml.LocalDate{ - Year: 1979, - Month: 5, - Day: 27, - }, - Time: toml.LocalTime{ - Hour: 7, - Minute: 32, - Second: 0, - Nanosecond: 0, - }, - }}, - { - name: "with nanoseconds", - in: "1979-05-27T00:32:00.999999", - out: toml.LocalDateTime{ - Date: toml.LocalDate{ - Year: 1979, - Month: 5, - Day: 27, - }, - Time: toml.LocalTime{ - Hour: 0, - Minute: 32, - Second: 0, - Nanosecond: 999999000, - }, - }, - }, - } - - for i, example := range examples { - doc := fmt.Sprintf(`date = %s`, example.in) - - t.Run(fmt.Sprintf("ToLocalDateTime_%d_%s", i, example.name), func(t *testing.T) { - type dateStruct struct { - Date toml.LocalDateTime - } - - var obj dateStruct - - err := toml.Unmarshal([]byte(doc), &obj) - - if err != nil { - t.Fatal(err) - } - - if obj.Date != example.out { - t.Errorf("expected '%s', got '%s'", example.out, obj.Date) - } - }) - - t.Run(fmt.Sprintf("ToTime_%d_%s", i, example.name), func(t *testing.T) { - type dateStruct struct { - Date time.Time - } - - var obj dateStruct - - err := toml.Unmarshal([]byte(doc), &obj) - - if err != nil { - t.Fatal(err) - } - - if obj.Date.Year() != example.out.Date.Year { - t.Errorf("expected year %d, got %d", example.out.Date.Year, obj.Date.Year()) - } - if obj.Date.Month() != example.out.Date.Month { - t.Errorf("expected month %d, got %d", example.out.Date.Month, obj.Date.Month()) - } - if obj.Date.Day() != example.out.Date.Day { - t.Errorf("expected day %d, got %d", example.out.Date.Day, obj.Date.Day()) - } - if obj.Date.Hour() != example.out.Time.Hour { - t.Errorf("expected hour %d, got %d", example.out.Time.Hour, obj.Date.Hour()) - } - if obj.Date.Minute() != example.out.Time.Minute { - t.Errorf("expected minute %d, got %d", example.out.Time.Minute, obj.Date.Minute()) - } - if obj.Date.Second() != example.out.Time.Second { - t.Errorf("expected second %d, got %d", example.out.Time.Second, obj.Date.Second()) - } - if obj.Date.Nanosecond() != example.out.Time.Nanosecond { - t.Errorf("expected nanoseconds %d, got %d", example.out.Time.Nanosecond, obj.Date.Nanosecond()) - } - }) - } -} - -func TestUnmarshalLocalTime(t *testing.T) { - examples := []struct { - name string - in string - out toml.LocalTime - }{ - { - name: "normal", - in: "07:32:00", - out: toml.LocalTime{ - Hour: 7, - Minute: 32, - Second: 0, - Nanosecond: 0, - }, - }, - { - name: "with nanoseconds", - in: "00:32:00.999999", - out: toml.LocalTime{ - Hour: 0, - Minute: 32, - Second: 0, - Nanosecond: 999999000, - }, - }, - } - - for i, example := range examples { - doc := fmt.Sprintf(`Time = %s`, example.in) - - t.Run(fmt.Sprintf("ToLocalTime_%d_%s", i, example.name), func(t *testing.T) { - type dateStruct struct { - Time toml.LocalTime - } - - var obj dateStruct - - err := toml.Unmarshal([]byte(doc), &obj) - - if err != nil { - t.Fatal(err) - } - - if obj.Time != example.out { - t.Errorf("expected '%s', got '%s'", example.out, obj.Time) - } - }) - } -} +// +//func TestUnmarshalLocalDate(t *testing.T) { +// t.Run("ToLocalDate", func(t *testing.T) { +// type dateStruct struct { +// Date toml.LocalDate +// } +// +// doc := `date = 1979-05-27` +// +// var obj dateStruct +// +// err := toml.Unmarshal([]byte(doc), &obj) +// +// if err != nil { +// t.Fatal(err) +// } +// +// if obj.Date.Year != 1979 { +// t.Errorf("expected year 1979, got %d", obj.Date.Year) +// } +// if obj.Date.Month != 5 { +// t.Errorf("expected month 5, got %d", obj.Date.Month) +// } +// if obj.Date.Day != 27 { +// t.Errorf("expected day 27, got %d", obj.Date.Day) +// } +// }) +// +// t.Run("ToLocalDate", func(t *testing.T) { +// type dateStruct struct { +// Date time.Time +// } +// +// doc := `date = 1979-05-27` +// +// var obj dateStruct +// +// err := toml.Unmarshal([]byte(doc), &obj) +// +// if err != nil { +// t.Fatal(err) +// } +// +// if obj.Date.Year() != 1979 { +// t.Errorf("expected year 1979, got %d", obj.Date.Year()) +// } +// if obj.Date.Month() != 5 { +// t.Errorf("expected month 5, got %d", obj.Date.Month()) +// } +// if obj.Date.Day() != 27 { +// t.Errorf("expected day 27, got %d", obj.Date.Day()) +// } +// }) +//} +// +//func TestUnmarshalLocalDateTime(t *testing.T) { +// examples := []struct { +// name string +// in string +// out toml.LocalDateTime +// }{ +// { +// name: "normal", +// in: "1979-05-27T07:32:00", +// out: toml.LocalDateTime{ +// Date: toml.LocalDate{ +// Year: 1979, +// Month: 5, +// Day: 27, +// }, +// Time: toml.LocalTime{ +// Hour: 7, +// Minute: 32, +// Second: 0, +// Nanosecond: 0, +// }, +// }}, +// { +// name: "with nanoseconds", +// in: "1979-05-27T00:32:00.999999", +// out: toml.LocalDateTime{ +// Date: toml.LocalDate{ +// Year: 1979, +// Month: 5, +// Day: 27, +// }, +// Time: toml.LocalTime{ +// Hour: 0, +// Minute: 32, +// Second: 0, +// Nanosecond: 999999000, +// }, +// }, +// }, +// } +// +// for i, example := range examples { +// doc := fmt.Sprintf(`date = %s`, example.in) +// +// t.Run(fmt.Sprintf("ToLocalDateTime_%d_%s", i, example.name), func(t *testing.T) { +// type dateStruct struct { +// Date toml.LocalDateTime +// } +// +// var obj dateStruct +// +// err := toml.Unmarshal([]byte(doc), &obj) +// +// if err != nil { +// t.Fatal(err) +// } +// +// if obj.Date != example.out { +// t.Errorf("expected '%s', got '%s'", example.out, obj.Date) +// } +// }) +// +// t.Run(fmt.Sprintf("ToTime_%d_%s", i, example.name), func(t *testing.T) { +// type dateStruct struct { +// Date time.Time +// } +// +// var obj dateStruct +// +// err := toml.Unmarshal([]byte(doc), &obj) +// +// if err != nil { +// t.Fatal(err) +// } +// +// if obj.Date.Year() != example.out.Date.Year { +// t.Errorf("expected year %d, got %d", example.out.Date.Year, obj.Date.Year()) +// } +// if obj.Date.Month() != example.out.Date.Month { +// t.Errorf("expected month %d, got %d", example.out.Date.Month, obj.Date.Month()) +// } +// if obj.Date.Day() != example.out.Date.Day { +// t.Errorf("expected day %d, got %d", example.out.Date.Day, obj.Date.Day()) +// } +// if obj.Date.Hour() != example.out.Time.Hour { +// t.Errorf("expected hour %d, got %d", example.out.Time.Hour, obj.Date.Hour()) +// } +// if obj.Date.Minute() != example.out.Time.Minute { +// t.Errorf("expected minute %d, got %d", example.out.Time.Minute, obj.Date.Minute()) +// } +// if obj.Date.Second() != example.out.Time.Second { +// t.Errorf("expected second %d, got %d", example.out.Time.Second, obj.Date.Second()) +// } +// if obj.Date.Nanosecond() != example.out.Time.Nanosecond { +// t.Errorf("expected nanoseconds %d, got %d", example.out.Time.Nanosecond, obj.Date.Nanosecond()) +// } +// }) +// } +//} +// +//func TestUnmarshalLocalTime(t *testing.T) { +// examples := []struct { +// name string +// in string +// out toml.LocalTime +// }{ +// { +// name: "normal", +// in: "07:32:00", +// out: toml.LocalTime{ +// Hour: 7, +// Minute: 32, +// Second: 0, +// Nanosecond: 0, +// }, +// }, +// { +// name: "with nanoseconds", +// in: "00:32:00.999999", +// out: toml.LocalTime{ +// Hour: 0, +// Minute: 32, +// Second: 0, +// Nanosecond: 999999000, +// }, +// }, +// } +// +// for i, example := range examples { +// doc := fmt.Sprintf(`Time = %s`, example.in) +// +// t.Run(fmt.Sprintf("ToLocalTime_%d_%s", i, example.name), func(t *testing.T) { +// type dateStruct struct { +// Time toml.LocalTime +// } +// +// var obj dateStruct +// +// err := toml.Unmarshal([]byte(doc), &obj) +// +// if err != nil { +// t.Fatal(err) +// } +// +// if obj.Time != example.out { +// t.Errorf("expected '%s', got '%s'", example.out, obj.Time) +// } +// }) +// } +//} // test case for issue #339 func TestUnmarshalSameInnerField(t *testing.T) { From 00b2f776a9a07b3c81072893e342427e9c76b3e2 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Mon, 15 Mar 2021 08:46:35 -0400 Subject: [PATCH 076/228] Replace branch with AST version --- internal/unmarshaler/parser.go | 1029 ----------------- internal/unmarshaler/scanner.go | 168 --- parser.go | 517 +++------ .../parser_test.go => parser_test.go | 2 +- scanner.go | 2 + internal/unmarshaler/targets.go => targets.go | 2 +- .../targets_test.go => targets_test.go | 2 +- unmarshal.go | 359 ------ unmarshal_test.go | 361 ------ .../unmarshaler.go => unmarshaler.go | 2 +- ...unmarshaler_test.go => unmarshaler_test.go | 2 +- 11 files changed, 193 insertions(+), 2253 deletions(-) delete mode 100644 internal/unmarshaler/parser.go delete mode 100644 internal/unmarshaler/scanner.go rename internal/unmarshaler/parser_test.go => parser_test.go (99%) rename internal/unmarshaler/targets.go => targets.go (99%) rename internal/unmarshaler/targets_test.go => targets_test.go (99%) delete mode 100644 unmarshal.go delete mode 100644 unmarshal_test.go rename internal/unmarshaler/unmarshaler.go => unmarshaler.go (99%) rename internal/unmarshaler/unmarshaler_test.go => unmarshaler_test.go (99%) diff --git a/internal/unmarshaler/parser.go b/internal/unmarshaler/parser.go deleted file mode 100644 index f59ecad4..00000000 --- a/internal/unmarshaler/parser.go +++ /dev/null @@ -1,1029 +0,0 @@ -package unmarshaler - -import ( - "bytes" - "encoding/hex" - "fmt" - "time" - - "github.com/pelletier/go-toml/v2" - "github.com/pelletier/go-toml/v2/internal/ast" -) - -type parser struct { - tree ast.Root -} - -func (p *parser) parse(b []byte) error { - b, err := p.parseExpression(b) - if err != nil { - return err - } - for len(b) > 0 { - b, err = p.parseNewline(b) - if err != nil { - return err - } - - b, err = p.parseExpression(b) - if err != nil { - return err - } - } - return nil -} - -func (p *parser) parseNewline(b []byte) ([]byte, error) { - if b[0] == '\n' { - return b[1:], nil - } - if b[0] == '\r' { - _, rest, err := scanWindowsNewline(b) - return rest, err - } - return nil, fmt.Errorf("expected newline but got %#U", b[0]) -} - -func (p *parser) parseExpression(b []byte) ([]byte, error) { - //expression = ws [ comment ] - //expression =/ ws keyval ws [ comment ] - //expression =/ ws table ws [ comment ] - - b = p.parseWhitespace(b) - - if len(b) == 0 { - return b, nil - } - - if b[0] == '#' { - _, rest, err := scanComment(b) - return rest, err - } - if b[0] == '\n' || b[0] == '\r' { - return b, nil - } - - var err error - var node ast.Node - if b[0] == '[' { - node, b, err = p.parseTable(b) - } else { - node, b, err = p.parseKeyval(b) - } - if err != nil { - return nil, err - } - - b = p.parseWhitespace(b) - - if len(b) > 0 && b[0] == '#' { - _, rest, err := scanComment(b) - return rest, err - } - - p.tree = append(p.tree, node) - - return b, nil -} - -func (p *parser) parseTable(b []byte) (ast.Node, []byte, error) { - //table = std-table / array-table - if len(b) > 1 && b[1] == '[' { - return p.parseArrayTable(b) - } - return p.parseStdTable(b) -} - -func (p *parser) parseArrayTable(b []byte) (ast.Node, []byte, error) { - //array-table = array-table-open key array-table-close - //array-table-open = %x5B.5B ws ; [[ Double left square bracket - //array-table-close = ws %x5D.5D ; ]] Double right square bracket - - // TODO - //b = b[2:] - //b = p.parseWhitespace(b) - //b, err := p.parseKey(b) - //if err != nil { - // return nil, err - //} - //b = p.parseWhitespace(b) - //b, err = expect(']', b) - //if err != nil { - // return nil, err - //} - //return expect(']', b) - - return ast.NoNode, nil, nil -} - -func (p *parser) parseStdTable(b []byte) (ast.Node, []byte, error) { - //std-table = std-table-open key std-table-close - //std-table-open = %x5B ws ; [ Left square bracket - //std-table-close = ws %x5D ; ] Right square bracket - - node := ast.Node{ - Kind: ast.Table, - } - - b = b[1:] - b = p.parseWhitespace(b) - key, b, err := p.parseKey(b) - if err != nil { - return ast.NoNode, nil, err - } - node.Children = key - b = p.parseWhitespace(b) - - b, err = expect(']', b) - - return node, b, err -} - -func (p *parser) parseKeyval(b []byte) (ast.Node, []byte, error) { - //keyval = key keyval-sep val - - node := ast.Node{ - Kind: ast.KeyValue, - } - - key, b, err := p.parseKey(b) - if err != nil { - return ast.NoNode, nil, err - } - node.Children = append(node.Children, key...) - - //keyval-sep = ws %x3D ws ; = - - b = p.parseWhitespace(b) - b, err = expect('=', b) - if err != nil { - return ast.NoNode, nil, err - } - b = p.parseWhitespace(b) - - valNode, b, err := p.parseVal(b) - if err == nil { - node.Children = append(node.Children, valNode) - } - return node, b, err -} - -func (p *parser) parseVal(b []byte) (ast.Node, []byte, error) { - // val = string / boolean / array / inline-table / date-time / float / integer - if len(b) == 0 { - return ast.NoNode, nil, fmt.Errorf("expected value, not eof") - } - - node := ast.Node{} - var err error - c := b[0] - - switch c { - case '"': - var v []byte - if scanFollowsMultilineBasicStringDelimiter(b) { - v, b, err = p.parseMultilineBasicString(b) - } else { - v, b, err = p.parseBasicString(b) - } - if err == nil { - node.Kind = ast.String - node.Data = v - } - return node, b, err - case '\'': - var v []byte - if scanFollowsMultilineLiteralStringDelimiter(b) { - v, b, err = p.parseMultilineLiteralString(b) - } else { - v, b, err = p.parseLiteralString(b) - } - if err == nil { - node.Kind = ast.String - node.Data = v - } - return node, b, err - case 't': - if !scanFollowsTrue(b) { - return node, nil, fmt.Errorf("expected 'true'") - } - node.Kind = ast.Bool - node.Data = b[:4] - return node, b[4:], nil - case 'f': - if !scanFollowsFalse(b) { - return node, nil, fmt.Errorf("expected 'false'") - } - node.Kind = ast.Bool - node.Data = b[:5] - return node, b[5:], nil - case '[': - node.Kind = ast.Array - b, err := p.parseValArray(&node, b) - return node, b, err - case '{': - node.Kind = ast.InlineTable - b, err := p.parseInlineTable(&node, b) - return node, b, err - default: - b, err = p.parseIntOrFloatOrDateTime(&node, b) - return node, b, err - } -} - -func (p *parser) parseLiteralString(b []byte) ([]byte, []byte, error) { - v, rest, err := scanLiteralString(b) - if err != nil { - return nil, nil, err - } - return v[1 : len(v)-1], rest, nil -} - -func (p *parser) parseInlineTable(node *ast.Node, b []byte) ([]byte, error) { - //inline-table = inline-table-open [ inline-table-keyvals ] inline-table-close - //inline-table-open = %x7B ws ; { - //inline-table-close = ws %x7D ; } - //inline-table-sep = ws %x2C ws ; , Comma - //inline-table-keyvals = keyval [ inline-table-sep inline-table-keyvals ] - - b = b[1:] - - first := true - var err error - for len(b) > 0 { - b = p.parseWhitespace(b) - if b[0] == '}' { - break - } - - if !first { - b, err = expect(',', b) - if err != nil { - return nil, err - } - b = p.parseWhitespace(b) - } - var kv ast.Node - kv, b, err = p.parseKeyval(b) - if err != nil { - return nil, err - } - node.Children = append(node.Children, kv) - - first = false - } - - return expect('}', b) -} - -func (p *parser) parseValArray(node *ast.Node, b []byte) ([]byte, error) { - //array = array-open [ array-values ] ws-comment-newline array-close - //array-open = %x5B ; [ - //array-close = %x5D ; ] - //array-values = ws-comment-newline val ws-comment-newline array-sep array-values - //array-values =/ ws-comment-newline val ws-comment-newline [ array-sep ] - //array-sep = %x2C ; , Comma - //ws-comment-newline = *( wschar / [ comment ] newline ) - - b = b[1:] - - first := true - var err error - for len(b) > 0 { - b, err = p.parseOptionalWhitespaceCommentNewline(b) - if err != nil { - return nil, err - } - - if len(b) == 0 { - return nil, unexpectedCharacter{b: b} - } - - if b[0] == ']' { - break - } - if b[0] == ',' { - if first { - return nil, fmt.Errorf("array cannot start with comma") - } - b = b[1:] - b, err = p.parseOptionalWhitespaceCommentNewline(b) - if err != nil { - return nil, err - } - } - - var valueNode ast.Node - valueNode, b, err = p.parseVal(b) - if err != nil { - return nil, err - } - node.Children = append(node.Children, valueNode) - b, err = p.parseOptionalWhitespaceCommentNewline(b) - if err != nil { - return nil, err - } - first = false - } - - return expect(']', b) -} - -func (p *parser) parseOptionalWhitespaceCommentNewline(b []byte) ([]byte, error) { - var err error - b = p.parseWhitespace(b) - if len(b) > 0 && b[0] == '#' { - _, b, err = scanComment(b) - if err != nil { - return nil, err - } - } - if len(b) > 0 && (b[0] == '\n' || b[0] == '\r') { - b, err = p.parseNewline(b) - if err != nil { - return nil, err - } - } - return b, nil -} - -func (p *parser) parseMultilineLiteralString(b []byte) ([]byte, []byte, error) { - token, rest, err := scanMultilineLiteralString(b) - if err != nil { - return nil, nil, err - } - - i := 3 - - // skip the immediate new line - if token[i] == '\n' { - i++ - } else if token[i] == '\r' && token[i+1] == '\n' { - i += 2 - } - - return token[i : len(b)-3], rest, err -} - -func (p *parser) parseMultilineBasicString(b []byte) ([]byte, []byte, error) { - //ml-basic-string = ml-basic-string-delim [ newline ] ml-basic-body - //ml-basic-string-delim - //ml-basic-string-delim = 3quotation-mark - //ml-basic-body = *mlb-content *( mlb-quotes 1*mlb-content ) [ mlb-quotes ] - // - //mlb-content = mlb-char / newline / mlb-escaped-nl - //mlb-char = mlb-unescaped / escaped - //mlb-quotes = 1*2quotation-mark - //mlb-unescaped = wschar / %x21 / %x23-5B / %x5D-7E / non-ascii - //mlb-escaped-nl = escape ws newline *( wschar / newline ) - - token, rest, err := scanMultilineBasicString(b) - if err != nil { - return nil, nil, err - } - var builder bytes.Buffer - - i := 3 - - // skip the immediate new line - if token[i] == '\n' { - i++ - } else if token[i] == '\r' && token[i+1] == '\n' { - i += 2 - } - - // The scanner ensures that the token starts and ends with quotes and that - // escapes are balanced. - for ; i < len(token)-3; i++ { - c := token[i] - if c == '\\' { - // When the last non-whitespace character on a line is an unescaped \, - // it will be trimmed along with all whitespace (including newlines) up - // to the next non-whitespace character or closing delimiter. - if token[i+1] == '\n' || (token[i+1] == '\r' && token[i+2] == '\n') { - i++ // skip the \ - for ; i < len(token)-3; i++ { - c := token[i] - if !(c == '\n' || c == '\r' || c == ' ' || c == '\t') { - break - } - } - continue - } - - // handle escaping - i++ - c = token[i] - switch c { - case '"', '\\': - builder.WriteByte(c) - case 'b': - builder.WriteByte('\b') - case 'f': - builder.WriteByte('\f') - case 'n': - builder.WriteByte('\n') - case 'r': - builder.WriteByte('\r') - case 't': - builder.WriteByte('\t') - case 'u': - x, err := hexToString(token[i+3:len(token)-3], 4) - if err != nil { - return nil, nil, err - } - builder.WriteString(x) - i += 4 - case 'U': - x, err := hexToString(token[i+3:len(token)-3], 8) - if err != nil { - return nil, nil, err - } - builder.WriteString(x) - i += 8 - default: - return nil, nil, fmt.Errorf("invalid escaped character: %#U", c) - } - } else { - builder.WriteByte(c) - } - } - - return builder.Bytes(), rest, nil -} - -func (p *parser) parseKey(b []byte) ([]ast.Node, []byte, error) { - //key = simple-key / dotted-key - //simple-key = quoted-key / unquoted-key - // - //unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _ - //quoted-key = basic-string / literal-string - //dotted-key = simple-key 1*( dot-sep simple-key ) - // - //dot-sep = ws %x2E ws ; . Period - - var nodes []ast.Node - - key, b, err := p.parseSimpleKey(b) - if err != nil { - return nodes, nil, err - } - - nodes = append(nodes, ast.Node{ - Kind: ast.Key, - Data: key, - }) - - for { - b = p.parseWhitespace(b) - if len(b) > 0 && b[0] == '.' { - b, err = expect('.', b) - if err != nil { - return nodes, nil, err - } - b = p.parseWhitespace(b) - key, b, err = p.parseSimpleKey(b) - if err != nil { - return nodes, nil, err - } - nodes = append(nodes, ast.Node{ - Kind: ast.Key, - Data: key, - }) - } else { - break - } - } - - return nodes, b, nil -} - -func (p *parser) parseSimpleKey(b []byte) (key, rest []byte, err error) { - //simple-key = quoted-key / unquoted-key - //unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _ - //quoted-key = basic-string / literal-string - - if len(b) == 0 { - return nil, nil, unexpectedCharacter{b: b} - } - - if b[0] == '\'' { - key, rest, err = scanLiteralString(b) - } else if b[0] == '"' { - key, rest, err = p.parseBasicString(b) - } else if isUnquotedKeyChar(b[0]) { - key, rest, err = scanUnquotedKey(b) - } else { - err = unexpectedCharacter{b: b} - } - return -} - -func (p *parser) parseBasicString(b []byte) ([]byte, []byte, error) { - //basic-string = quotation-mark *basic-char quotation-mark - //quotation-mark = %x22 ; " - //basic-char = basic-unescaped / escaped - //basic-unescaped = wschar / %x21 / %x23-5B / %x5D-7E / non-ascii - //escaped = escape escape-seq-char - //escape-seq-char = %x22 ; " quotation mark U+0022 - //escape-seq-char =/ %x5C ; \ reverse solidus U+005C - //escape-seq-char =/ %x62 ; b backspace U+0008 - //escape-seq-char =/ %x66 ; f form feed U+000C - //escape-seq-char =/ %x6E ; n line feed U+000A - //escape-seq-char =/ %x72 ; r carriage return U+000D - //escape-seq-char =/ %x74 ; t tab U+0009 - //escape-seq-char =/ %x75 4HEXDIG ; uXXXX U+XXXX - //escape-seq-char =/ %x55 8HEXDIG ; UXXXXXXXX U+XXXXXXXX - - token, rest, err := scanBasicString(b) - if err != nil { - return nil, nil, err - } - var builder bytes.Buffer - - // The scanner ensures that the token starts and ends with quotes and that - // escapes are balanced. - for i := 1; i < len(token)-1; i++ { - c := token[i] - if c == '\\' { - i++ - c = token[i] - switch c { - case '"', '\\': - builder.WriteByte(c) - case 'b': - builder.WriteByte('\b') - case 'f': - builder.WriteByte('\f') - case 'n': - builder.WriteByte('\n') - case 'r': - builder.WriteByte('\r') - case 't': - builder.WriteByte('\t') - case 'u': - x, err := hexToString(token[i+1:len(token)-1], 4) - if err != nil { - return nil, nil, err - } - builder.WriteString(x) - i += 4 - case 'U': - x, err := hexToString(token[i+1:len(token)-1], 8) - if err != nil { - return nil, nil, err - } - builder.WriteString(x) - i += 8 - default: - return nil, nil, fmt.Errorf("invalid escaped character: %#U", c) - } - } else { - builder.WriteByte(c) - } - } - - return builder.Bytes(), rest, nil -} - -func hexToString(b []byte, length int) (string, error) { - if len(b) < length { - return "", fmt.Errorf("unicode point needs %d hex characters", length) - } - // TODO: slow - b, err := hex.DecodeString(string(b[:length])) - if err != nil { - return "", err - } - return string(b), nil -} - -func (p *parser) parseWhitespace(b []byte) []byte { - //ws = *wschar - //wschar = %x20 ; Space - //wschar =/ %x09 ; Horizontal tab - - _, rest := scanWhitespace(b) - return rest -} - -func (p *parser) parseIntOrFloatOrDateTime(node *ast.Node, b []byte) ([]byte, error) { - switch b[0] { - case 'i': - if !scanFollowsInf(b) { - return nil, fmt.Errorf("expected 'inf'") - } - node.Kind = ast.Float - node.Data = b[:3] - return b[3:], nil - case 'n': - if !scanFollowsNan(b) { - return nil, fmt.Errorf("expected 'nan'") - } - node.Kind = ast.Float - node.Data = b[:3] - return b[3:], nil - case '+', '-': - return p.scanIntOrFloat(node, b) - } - - if len(b) < 3 { - return p.scanIntOrFloat(node, b) - } - s := 5 - if len(b) < s { - s = len(b) - } - for idx, c := range b[:s] { - if c >= '0' && c <= '9' { - continue - } - if idx == 2 && c == ':' { - return p.parseDateTime(b) - } - if idx == 4 && c == '-' { - return p.parseDateTime(b) - } - } - return p.scanIntOrFloat(node, b) -} - -func digitsToInt(b []byte) int { - x := 0 - for _, d := range b { - x *= 10 - x += int(d - '0') - } - return x -} - -func (p *parser) parseDateTime(b []byte) ([]byte, error) { - // we know the first 2 ar digits. - if b[2] == ':' { - return p.parseTime(b) - } - // This state accepts an offset date-time, a local date-time, or a local date. - // - // v--- cursor - // 1979-05-27T07:32:00Z - // 1979-05-27T00:32:00-07:00 - // 1979-05-27T00:32:00.999999-07:00 - // 1979-05-27 07:32:00Z - // 1979-05-27 00:32:00-07:00 - // 1979-05-27 00:32:00.999999-07:00 - // 1979-05-27T07:32:00 - // 1979-05-27T00:32:00.999999 - // 1979-05-27 07:32:00 - // 1979-05-27 00:32:00.999999 - // 1979-05-27 - - // date - - idx := 4 - - localDate := toml.LocalDate{ - Year: digitsToInt(b[:idx]), - } - - for i := 0; i < 2; i++ { - // month - idx++ - if !isDigit(b[idx]) { - return nil, fmt.Errorf("invalid month digit in date: %c", b[idx]) - } - localDate.Month *= 10 - localDate.Month += time.Month(b[idx] - '0') - } - - idx++ - if b[idx] != '-' { - return nil, fmt.Errorf("expected - to separate month of a date, not %c", b[idx]) - } - - for i := 0; i < 2; i++ { - // day - idx++ - if !isDigit(b[idx]) { - return nil, fmt.Errorf("invalid day digit in date: %c", b[idx]) - } - localDate.Day *= 10 - localDate.Day += int(b[idx] - '0') - } - - idx++ - - if idx >= len(b) { - //p.builder.LocalDateValue(localDate) - // TODO - return nil, nil - } else if b[idx] != ' ' && b[idx] != 'T' { - //p.builder.LocalDateValue(localDate) - // TODO - return b[idx:], nil - } - - // check if there is a chance there is anything useful after - if b[idx] == ' ' && (((idx + 2) >= len(b)) || !isDigit(b[idx+1]) || !isDigit(b[idx+2])) { - //p.builder.LocalDateValue(localDate) - // TODO - return b[idx:], nil - } - - //idx++ // skip the T or ' ' - - // time - localTime := toml.LocalTime{} - - for i := 0; i < 2; i++ { - idx++ - if !isDigit(b[idx]) { - return nil, fmt.Errorf("invalid hour digit in time: %c", b[idx]) - } - localTime.Hour *= 10 - localTime.Hour += int(b[idx] - '0') - } - - idx++ - if b[idx] != ':' { - return nil, fmt.Errorf("time hour/minute separator should be :, not %c", b[idx]) - } - - for i := 0; i < 2; i++ { - idx++ - if !isDigit(b[idx]) { - return nil, fmt.Errorf("invalid minute digit in time: %c", b[idx]) - } - localTime.Minute *= 10 - localTime.Minute += int(b[idx] - '0') - } - - idx++ - if b[idx] != ':' { - return nil, fmt.Errorf("time minute/second separator should be :, not %c", b[idx]) - } - - for i := 0; i < 2; i++ { - idx++ - if !isDigit(b[idx]) { - return nil, fmt.Errorf("invalid second digit in time: %c", b[idx]) - } - localTime.Second *= 10 - localTime.Second += int(b[idx] - '0') - } - - idx++ - if idx < len(b) && b[idx] == '.' { - idx++ - idx++ - if !isDigit(b[idx]) { - return nil, fmt.Errorf("expected at least one digit in time's fraction, not %c", b[idx]) - } - - for { - localTime.Nanosecond *= 10 - localTime.Nanosecond += int(b[idx] - '0') - idx++ - - if idx < len(b) { - break - } - - if !isDigit(b[idx]) { - break - } - } - } - - if idx >= len(b) || (b[idx] != 'Z' && b[idx] != '+' && b[idx] != '-') { - dt := toml.LocalDateTime{ - Date: localDate, - Time: localTime, - } - //p.builder.LocalDateTimeValue(dt) - // TODO - dt = dt - return b[idx:], nil - } - - loc := time.UTC - - if b[idx] == 'Z' { - idx++ - } else { - start := idx - sign := 1 - if b[idx] == '-' { - sign = -1 - } - - hours := 0 - for i := 0; i < 2; i++ { - idx++ - if !isDigit(b[idx]) { - return nil, fmt.Errorf("invalid hour digit in time offset: %c", b[idx]) - } - hours *= 10 - hours += int(b[idx] - '0') - } - offset := hours * 60 * 60 - - idx++ - if b[idx] != ':' { - return nil, fmt.Errorf("time offset hour/minute separator should be :, not %c", b[idx]) - } - - minutes := 0 - for i := 0; i < 2; i++ { - idx++ - if !isDigit(b[idx]) { - return nil, fmt.Errorf("invalid minute digit in time offset: %c", b[idx]) - } - minutes *= 10 - minutes += int(b[idx] - '0') - } - offset += minutes * 60 - offset *= sign - idx++ - loc = time.FixedZone(string(b[start:idx]), offset) - } - dt := time.Date(localDate.Year, localDate.Month, localDate.Day, localTime.Hour, localTime.Minute, localTime.Second, localTime.Nanosecond, loc) - //p.builder.DateTimeValue(dt) - // TODO - dt = dt - return b[idx:], nil -} - -func (p *parser) parseTime(b []byte) ([]byte, error) { - localTime := toml.LocalTime{} - - idx := 0 - - for i := 0; i < 2; i++ { - idx++ - if !isDigit(b[idx]) { - return nil, fmt.Errorf("invalid hour digit in time: %c", b[idx]) - } - localTime.Hour *= 10 - localTime.Hour += int(b[idx] - '0') - } - - idx++ - if b[idx] != ':' { - return nil, fmt.Errorf("time hour/minute separator should be :, not %c", b[idx]) - } - - for i := 0; i < 2; i++ { - idx++ - if !isDigit(b[idx]) { - return nil, fmt.Errorf("invalid minute digit in time: %c", b[idx]) - } - localTime.Minute *= 10 - localTime.Minute += int(b[idx] - '0') - } - - idx++ - if b[idx] != ':' { - return nil, fmt.Errorf("time minute/second separator should be :, not %c", b[idx]) - } - - for i := 0; i < 2; i++ { - idx++ - if !isDigit(b[idx]) { - return nil, fmt.Errorf("invalid second digit in time: %c", b[idx]) - } - localTime.Second *= 10 - localTime.Second += int(b[idx] - '0') - } - - idx++ - if idx < len(b) && b[idx] == '.' { - idx++ - idx++ - if !isDigit(b[idx]) { - return nil, fmt.Errorf("expected at least one digit in time's fraction, not %c", b[idx]) - } - - for { - localTime.Nanosecond *= 10 - localTime.Nanosecond += int(b[idx] - '0') - idx++ - if !isDigit(b[idx]) { - break - } - } - } - - //p.builder.LocalTimeValue(localTime) - // TODO - return b[idx:], nil -} - -func (p *parser) scanIntOrFloat(node *ast.Node, b []byte) ([]byte, error) { - i := 0 - - if len(b) > 2 && b[0] == '0' { - var isValidRune validRuneFn - switch b[1] { - case 'x': - isValidRune = isValidHexRune - case 'o': - isValidRune = isValidOctalRune - case 'b': - isValidRune = isValidBinaryRune - default: - return b, fmt.Errorf("unknown number base: %c. possible options are x (hex) o (octal) b (binary)", b[1]) - } - - i += 2 - for ; i < len(b); i++ { - if !isValidRune(b[i]) { - node.Kind = ast.Integer - node.Data = b[:i] - return b[i:], nil - } - } - } - - isFloat := false - - for ; i < len(b); i++ { - c := b[i] - - if c >= '0' && c <= '9' || c == '+' || c == '-' || c == '_' { - continue - } - - if c == '.' || c == 'e' || c == 'E' { - isFloat = true - continue - } - - if c == 'i' { - if scanFollowsInf(b[i:]) { - node.Kind = ast.Float - node.Data = b[:i+3] - return b[i+3:], nil - } - return nil, fmt.Errorf("unexpected character i while scanning for a number") - } - if c == 'n' { - if scanFollowsNan(b[i:]) { - node.Kind = ast.Float - node.Data = b[:i+3] - return b[i+3:], nil - } - return nil, fmt.Errorf("unexpected character n while scanning for a number") - } - - break - } - - if isFloat { - node.Kind = ast.Float - } else { - node.Kind = ast.Integer - } - node.Data = b[:i] - return b[i:], nil -} - -func isDigit(r byte) bool { - return r >= '0' && r <= '9' -} - -type validRuneFn func(r byte) bool - -func isValidHexRune(r byte) bool { - return r >= 'a' && r <= 'f' || - r >= 'A' && r <= 'F' || - r >= '0' && r <= '9' || - r == '_' -} - -func isValidOctalRune(r byte) bool { - return r >= '0' && r <= '7' || r == '_' -} - -func isValidBinaryRune(r byte) bool { - return r == '0' || r == '1' || r == '_' -} - -func expect(x byte, b []byte) ([]byte, error) { - if len(b) == 0 || b[0] != x { - return nil, unexpectedCharacter{r: x, b: b} - } - return b[1:], nil -} - -type unexpectedCharacter struct { - r byte - b []byte -} - -func (u unexpectedCharacter) Error() string { - if len(u.b) == 0 { - return fmt.Sprintf("expected %#U, not EOF", u.r) - - } - return fmt.Sprintf("expected %#U, not %#U", u.r, u.b[0]) -} diff --git a/internal/unmarshaler/scanner.go b/internal/unmarshaler/scanner.go deleted file mode 100644 index a13e6b68..00000000 --- a/internal/unmarshaler/scanner.go +++ /dev/null @@ -1,168 +0,0 @@ -package unmarshaler - -import "fmt" - -func scanFollows(pattern []byte) func(b []byte) bool { - return func(b []byte) bool { - if len(b) < len(pattern) { - return false - } - for i, c := range pattern { - if b[i] != c { - return false - } - } - return true - } -} - -var scanFollowsMultilineBasicStringDelimiter = scanFollows([]byte{'"', '"', '"'}) -var scanFollowsMultilineLiteralStringDelimiter = scanFollows([]byte{'\'', '\'', '\''}) -var scanFollowsTrue = scanFollows([]byte{'t', 'r', 'u', 'e'}) -var scanFollowsFalse = scanFollows([]byte{'f', 'a', 'l', 's', 'e'}) -var scanFollowsInf = scanFollows([]byte{'i', 'n', 'f'}) -var scanFollowsNan = scanFollows([]byte{'n', 'a', 'n'}) - -func scanUnquotedKey(b []byte) ([]byte, []byte, error) { - //unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _ - for i := 0; i < len(b); i++ { - if !isUnquotedKeyChar(b[i]) { - return b[:i], b[i:], nil - } - } - return b, nil, nil -} - -func isUnquotedKeyChar(r byte) bool { - return (r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '-' || r == '_' -} - -func scanLiteralString(b []byte) ([]byte, []byte, error) { - //literal-string = apostrophe *literal-char apostrophe - //apostrophe = %x27 ; ' apostrophe - //literal-char = %x09 / %x20-26 / %x28-7E / non-ascii - for i := 1; i < len(b); i++ { - switch b[i] { - case '\'': - return b[:i+1], b[i+1:], nil - case '\n': - return nil, nil, fmt.Errorf("literal strings cannot have new lines") - } - } - return nil, nil, fmt.Errorf("unterminated literal string") -} - -func scanMultilineLiteralString(b []byte) ([]byte, []byte, error) { - //ml-literal-string = ml-literal-string-delim [ newline ] ml-literal-body - //ml-literal-string-delim - //ml-literal-string-delim = 3apostrophe - //ml-literal-body = *mll-content *( mll-quotes 1*mll-content ) [ mll-quotes ] - // - //mll-content = mll-char / newline - //mll-char = %x09 / %x20-26 / %x28-7E / non-ascii - //mll-quotes = 1*2apostrophe - for i := 3; i < len(b); i++ { - switch b[i] { - case '\'': - if scanFollowsMultilineLiteralStringDelimiter(b[i:]) { - return b[:i+3], b[:i+3], nil - } - } - } - - return nil, nil, fmt.Errorf(`multiline literal string not terminated by '''`) -} - -func scanWindowsNewline(b []byte) ([]byte, []byte, error) { - if len(b) < 2 { - return nil, nil, fmt.Errorf(`windows new line missing \n`) - } - if b[1] != '\n' { - return nil, nil, fmt.Errorf(`windows new line should be \r\n`) - } - return b[:2], b[2:], nil -} - -func scanWhitespace(b []byte) ([]byte, []byte) { - for i := 0; i < len(b); i++ { - switch b[i] { - case ' ', '\t': - continue - default: - return b[:i], b[i:] - } - } - return b, nil -} - -func scanComment(b []byte) ([]byte, []byte, error) { - //;; Comment - // - //comment-start-symbol = %x23 ; # - //non-ascii = %x80-D7FF / %xE000-10FFFF - //non-eol = %x09 / %x20-7F / non-ascii - // - //comment = comment-start-symbol *non-eol - - for i := 1; i < len(b); i++ { - switch b[i] { - case '\n': - return b[:i], b[i:], nil - } - } - return b, nil, nil -} - -// TODO perform validation on the string? -func scanBasicString(b []byte) ([]byte, []byte, error) { - //basic-string = quotation-mark *basic-char quotation-mark - //quotation-mark = %x22 ; " - //basic-char = basic-unescaped / escaped - //basic-unescaped = wschar / %x21 / %x23-5B / %x5D-7E / non-ascii - //escaped = escape escape-seq-char - for i := 1; i < len(b); i++ { - switch b[i] { - case '"': - return b[:i+1], b[i+1:], nil - case '\n': - return nil, nil, fmt.Errorf("basic strings cannot have new lines") - case '\\': - if len(b) < i+2 { - return nil, nil, fmt.Errorf("need a character after \\") - } - i++ // skip the next character - } - } - - return nil, nil, fmt.Errorf(`basic string not terminated by "`) -} - -// TODO perform validation on the string? -func scanMultilineBasicString(b []byte) ([]byte, []byte, error) { - //ml-basic-string = ml-basic-string-delim [ newline ] ml-basic-body - //ml-basic-string-delim - //ml-basic-string-delim = 3quotation-mark - //ml-basic-body = *mlb-content *( mlb-quotes 1*mlb-content ) [ mlb-quotes ] - // - //mlb-content = mlb-char / newline / mlb-escaped-nl - //mlb-char = mlb-unescaped / escaped - //mlb-quotes = 1*2quotation-mark - //mlb-unescaped = wschar / %x21 / %x23-5B / %x5D-7E / non-ascii - //mlb-escaped-nl = escape ws newline *( wschar / newline ) - - for i := 3; i < len(b); i++ { - switch b[i] { - case '"': - if scanFollowsMultilineBasicStringDelimiter(b[i:]) { - return b[:i+3], b[i+3:], nil - } - case '\\': - if len(b) < i+2 { - return nil, nil, fmt.Errorf("need a character after \\") - } - i++ // skip the next character - } - } - - return nil, nil, fmt.Errorf(`multiline basic string not terminated by """`) -} diff --git a/parser.go b/parser.go index 8ea8b119..34346c26 100644 --- a/parser.go +++ b/parser.go @@ -3,44 +3,17 @@ package toml import ( "bytes" "encoding/hex" - "errors" "fmt" - "math" - "strconv" - "strings" "time" -) -type builder interface { - SimpleKey(v []byte) - - StandardTableBegin() - StandardTableEnd() - ArrayTableBegin() - ArrayTableEnd() - KeyValBegin() - KeyValEnd() - ArrayBegin() - ArrayEnd() - Assignation() - InlineTableBegin() - InlineTableEnd() - - StringValue(v []byte) - BoolValue(b bool) - FloatValue(n float64) - IntValue(n int64) - LocalDateValue(date LocalDate) - LocalDateTimeValue(dt LocalDateTime) - DateTimeValue(dt time.Time) - LocalTimeValue(localTime LocalTime) -} + "github.com/pelletier/go-toml/v2/internal/ast" +) type parser struct { - builder builder + tree ast.Root } -func (p parser) parse(b []byte) error { +func (p *parser) parse(b []byte) error { b, err := p.parseExpression(b) if err != nil { return err @@ -59,7 +32,7 @@ func (p parser) parse(b []byte) error { return nil } -func (p parser) parseNewline(b []byte) ([]byte, error) { +func (p *parser) parseNewline(b []byte) ([]byte, error) { if b[0] == '\n' { return b[1:], nil } @@ -70,7 +43,7 @@ func (p parser) parseNewline(b []byte) ([]byte, error) { return nil, fmt.Errorf("expected newline but got %#U", b[0]) } -func (p parser) parseExpression(b []byte) ([]byte, error) { +func (p *parser) parseExpression(b []byte) ([]byte, error) { //expression = ws [ comment ] //expression =/ ws keyval ws [ comment ] //expression =/ ws table ws [ comment ] @@ -90,10 +63,11 @@ func (p parser) parseExpression(b []byte) ([]byte, error) { } var err error + var node ast.Node if b[0] == '[' { - b, err = p.parseTable(b) + node, b, err = p.parseTable(b) } else { - b, err = p.parseKeyval(b) + node, b, err = p.parseKeyval(b) } if err != nil { return nil, err @@ -106,10 +80,12 @@ func (p parser) parseExpression(b []byte) ([]byte, error) { return rest, err } + p.tree = append(p.tree, node) + return b, nil } -func (p parser) parseTable(b []byte) ([]byte, error) { +func (p *parser) parseTable(b []byte) (ast.Node, []byte, error) { //table = std-table / array-table if len(b) > 1 && b[1] == '[' { return p.parseArrayTable(b) @@ -117,82 +93,91 @@ func (p parser) parseTable(b []byte) ([]byte, error) { return p.parseStdTable(b) } -func (p parser) parseArrayTable(b []byte) ([]byte, error) { +func (p *parser) parseArrayTable(b []byte) (ast.Node, []byte, error) { //array-table = array-table-open key array-table-close //array-table-open = %x5B.5B ws ; [[ Double left square bracket //array-table-close = ws %x5D.5D ; ]] Double right square bracket - p.builder.ArrayTableBegin() - defer p.builder.ArrayTableEnd() - - b = b[2:] - b = p.parseWhitespace(b) - b, err := p.parseKey(b) - if err != nil { - return nil, err - } - b = p.parseWhitespace(b) - b, err = expect(']', b) - if err != nil { - return nil, err - } - return expect(']', b) + // TODO + //b = b[2:] + //b = p.parseWhitespace(b) + //b, err := p.parseKey(b) + //if err != nil { + // return nil, err + //} + //b = p.parseWhitespace(b) + //b, err = expect(']', b) + //if err != nil { + // return nil, err + //} + //return expect(']', b) + + return ast.NoNode, nil, nil } -func (p parser) parseStdTable(b []byte) ([]byte, error) { +func (p *parser) parseStdTable(b []byte) (ast.Node, []byte, error) { //std-table = std-table-open key std-table-close //std-table-open = %x5B ws ; [ Left square bracket //std-table-close = ws %x5D ; ] Right square bracket - p.builder.StandardTableBegin() - defer p.builder.StandardTableEnd() + node := ast.Node{ + Kind: ast.Table, + } b = b[1:] b = p.parseWhitespace(b) - b, err := p.parseKey(b) + key, b, err := p.parseKey(b) if err != nil { - return nil, err + return ast.NoNode, nil, err } + node.Children = key b = p.parseWhitespace(b) - return expect(']', b) + b, err = expect(']', b) + + return node, b, err } -func (p parser) parseKeyval(b []byte) ([]byte, error) { +func (p *parser) parseKeyval(b []byte) (ast.Node, []byte, error) { //keyval = key keyval-sep val - p.builder.KeyValBegin() - defer p.builder.KeyValEnd() + node := ast.Node{ + Kind: ast.KeyValue, + } - b, err := p.parseKey(b) + key, b, err := p.parseKey(b) if err != nil { - return nil, err + return ast.NoNode, nil, err } + node.Children = append(node.Children, key...) //keyval-sep = ws %x3D ws ; = b = p.parseWhitespace(b) b, err = expect('=', b) if err != nil { - return nil, err + return ast.NoNode, nil, err } - p.builder.Assignation() b = p.parseWhitespace(b) - return p.parseVal(b) + valNode, b, err := p.parseVal(b) + if err == nil { + node.Children = append(node.Children, valNode) + } + return node, b, err } -func (p parser) parseVal(b []byte) ([]byte, error) { +func (p *parser) parseVal(b []byte) (ast.Node, []byte, error) { // val = string / boolean / array / inline-table / date-time / float / integer if len(b) == 0 { - return nil, fmt.Errorf("expected value, not eof") + return ast.NoNode, nil, fmt.Errorf("expected value, not eof") } + node := ast.Node{} var err error c := b[0] switch c { - // strings case '"': var v []byte if scanFollowsMultilineBasicStringDelimiter(b) { @@ -201,9 +186,10 @@ func (p parser) parseVal(b []byte) ([]byte, error) { v, b, err = p.parseBasicString(b) } if err == nil { - p.builder.StringValue(v) + node.Kind = ast.String + node.Data = v } - return b, err + return node, b, err case '\'': var v []byte if scanFollowsMultilineLiteralStringDelimiter(b) { @@ -212,31 +198,39 @@ func (p parser) parseVal(b []byte) ([]byte, error) { v, b, err = p.parseLiteralString(b) } if err == nil { - p.builder.StringValue(v) + node.Kind = ast.String + node.Data = v } - return b, err + return node, b, err case 't': if !scanFollowsTrue(b) { - return nil, fmt.Errorf("expected 'true'") + return node, nil, fmt.Errorf("expected 'true'") } - p.builder.BoolValue(true) - return b[4:], nil + node.Kind = ast.Bool + node.Data = b[:4] + return node, b[4:], nil case 'f': if !scanFollowsFalse(b) { - return nil, fmt.Errorf("expected 'false'") + return node, nil, fmt.Errorf("expected 'false'") } - p.builder.BoolValue(false) - return b[5:], nil + node.Kind = ast.Bool + node.Data = b[:5] + return node, b[5:], nil case '[': - return p.parseValArray(b) + node.Kind = ast.Array + b, err := p.parseValArray(&node, b) + return node, b, err case '{': - return p.parseInlineTable(b) + node.Kind = ast.InlineTable + b, err := p.parseInlineTable(&node, b) + return node, b, err default: - return p.parseIntOrFloatOrDateTime(b) + b, err = p.parseIntOrFloatOrDateTime(&node, b) + return node, b, err } } -func (p parser) parseLiteralString(b []byte) ([]byte, []byte, error) { +func (p *parser) parseLiteralString(b []byte) ([]byte, []byte, error) { v, rest, err := scanLiteralString(b) if err != nil { return nil, nil, err @@ -244,16 +238,13 @@ func (p parser) parseLiteralString(b []byte) ([]byte, []byte, error) { return v[1 : len(v)-1], rest, nil } -func (p parser) parseInlineTable(b []byte) ([]byte, error) { +func (p *parser) parseInlineTable(node *ast.Node, b []byte) ([]byte, error) { //inline-table = inline-table-open [ inline-table-keyvals ] inline-table-close //inline-table-open = %x7B ws ; { //inline-table-close = ws %x7D ; } //inline-table-sep = ws %x2C ws ; , Comma //inline-table-keyvals = keyval [ inline-table-sep inline-table-keyvals ] - p.builder.InlineTableBegin() - defer p.builder.InlineTableEnd() - b = b[1:] first := true @@ -271,10 +262,12 @@ func (p parser) parseInlineTable(b []byte) ([]byte, error) { } b = p.parseWhitespace(b) } - b, err = p.parseKeyval(b) + var kv ast.Node + kv, b, err = p.parseKeyval(b) if err != nil { return nil, err } + node.Children = append(node.Children, kv) first = false } @@ -282,7 +275,7 @@ func (p parser) parseInlineTable(b []byte) ([]byte, error) { return expect('}', b) } -func (p parser) parseValArray(b []byte) ([]byte, error) { +func (p *parser) parseValArray(node *ast.Node, b []byte) ([]byte, error) { //array = array-open [ array-values ] ws-comment-newline array-close //array-open = %x5B ; [ //array-close = %x5D ; ] @@ -291,9 +284,6 @@ func (p parser) parseValArray(b []byte) ([]byte, error) { //array-sep = %x2C ; , Comma //ws-comment-newline = *( wschar / [ comment ] newline ) - p.builder.ArrayBegin() - defer p.builder.ArrayEnd() - b = b[1:] first := true @@ -322,10 +312,12 @@ func (p parser) parseValArray(b []byte) ([]byte, error) { } } - b, err = p.parseVal(b) + var valueNode ast.Node + valueNode, b, err = p.parseVal(b) if err != nil { return nil, err } + node.Children = append(node.Children, valueNode) b, err = p.parseOptionalWhitespaceCommentNewline(b) if err != nil { return nil, err @@ -336,7 +328,7 @@ func (p parser) parseValArray(b []byte) ([]byte, error) { return expect(']', b) } -func (p parser) parseOptionalWhitespaceCommentNewline(b []byte) ([]byte, error) { +func (p *parser) parseOptionalWhitespaceCommentNewline(b []byte) ([]byte, error) { var err error b = p.parseWhitespace(b) if len(b) > 0 && b[0] == '#' { @@ -354,7 +346,7 @@ func (p parser) parseOptionalWhitespaceCommentNewline(b []byte) ([]byte, error) return b, nil } -func (p parser) parseMultilineLiteralString(b []byte) ([]byte, []byte, error) { +func (p *parser) parseMultilineLiteralString(b []byte) ([]byte, []byte, error) { token, rest, err := scanMultilineLiteralString(b) if err != nil { return nil, nil, err @@ -372,7 +364,7 @@ func (p parser) parseMultilineLiteralString(b []byte) ([]byte, []byte, error) { return token[i : len(b)-3], rest, err } -func (p parser) parseMultilineBasicString(b []byte) ([]byte, []byte, error) { +func (p *parser) parseMultilineBasicString(b []byte) ([]byte, []byte, error) { //ml-basic-string = ml-basic-string-delim [ newline ] ml-basic-body //ml-basic-string-delim //ml-basic-string-delim = 3quotation-mark @@ -459,7 +451,7 @@ func (p parser) parseMultilineBasicString(b []byte) ([]byte, []byte, error) { return builder.Bytes(), rest, nil } -func (p parser) parseKey(b []byte) ([]byte, error) { +func (p *parser) parseKey(b []byte) ([]ast.Node, []byte, error) { //key = simple-key / dotted-key //simple-key = quoted-key / unquoted-key // @@ -469,55 +461,64 @@ func (p parser) parseKey(b []byte) ([]byte, error) { // //dot-sep = ws %x2E ws ; . Period - b, err := p.parseSimpleKey(b) + var nodes []ast.Node + + key, b, err := p.parseSimpleKey(b) if err != nil { - return nil, err + return nodes, nil, err } + nodes = append(nodes, ast.Node{ + Kind: ast.Key, + Data: key, + }) + for { b = p.parseWhitespace(b) if len(b) > 0 && b[0] == '.' { b, err = expect('.', b) if err != nil { - return nil, err + return nodes, nil, err } b = p.parseWhitespace(b) - b, err = p.parseSimpleKey(b) + key, b, err = p.parseSimpleKey(b) if err != nil { - return nil, err + return nodes, nil, err } + nodes = append(nodes, ast.Node{ + Kind: ast.Key, + Data: key, + }) } else { break } } - return b, nil + return nodes, b, nil } -func (p parser) parseSimpleKey(b []byte) (rest []byte, err error) { +func (p *parser) parseSimpleKey(b []byte) (key, rest []byte, err error) { //simple-key = quoted-key / unquoted-key //unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _ //quoted-key = basic-string / literal-string if len(b) == 0 { - return nil, unexpectedCharacter{b: b} + return nil, nil, unexpectedCharacter{b: b} } - var v []byte if b[0] == '\'' { - v, rest, err = scanLiteralString(b) + key, rest, err = scanLiteralString(b) } else if b[0] == '"' { - v, rest, err = p.parseBasicString(b) + key, rest, err = p.parseBasicString(b) } else if isUnquotedKeyChar(b[0]) { - v, rest, err = scanUnquotedKey(b) + key, rest, err = scanUnquotedKey(b) } else { - return nil, unexpectedCharacter{b: b} + err = unexpectedCharacter{b: b} } - p.builder.SimpleKey(v) return } -func (p parser) parseBasicString(b []byte) ([]byte, []byte, error) { +func (p *parser) parseBasicString(b []byte) ([]byte, []byte, error) { //basic-string = quotation-mark *basic-char quotation-mark //quotation-mark = %x22 ; " //basic-char = basic-unescaped / escaped @@ -596,7 +597,7 @@ func hexToString(b []byte, length int) (string, error) { return string(b), nil } -func (p parser) parseWhitespace(b []byte) []byte { +func (p *parser) parseWhitespace(b []byte) []byte { //ws = *wschar //wschar = %x20 ; Space //wschar =/ %x09 ; Horizontal tab @@ -605,26 +606,28 @@ func (p parser) parseWhitespace(b []byte) []byte { return rest } -func (p parser) parseIntOrFloatOrDateTime(b []byte) ([]byte, error) { +func (p *parser) parseIntOrFloatOrDateTime(node *ast.Node, b []byte) ([]byte, error) { switch b[0] { case 'i': if !scanFollowsInf(b) { return nil, fmt.Errorf("expected 'inf'") } - p.builder.FloatValue(math.Inf(1)) + node.Kind = ast.Float + node.Data = b[:3] return b[3:], nil case 'n': if !scanFollowsNan(b) { return nil, fmt.Errorf("expected 'nan'") } - p.builder.FloatValue(math.NaN()) + node.Kind = ast.Float + node.Data = b[:3] return b[3:], nil case '+', '-': - return p.parseIntOrFloat(b) + return p.scanIntOrFloat(node, b) } if len(b) < 3 { - return p.parseIntOrFloat(b) + return p.scanIntOrFloat(node, b) } s := 5 if len(b) < s { @@ -641,7 +644,7 @@ func (p parser) parseIntOrFloatOrDateTime(b []byte) ([]byte, error) { return p.parseDateTime(b) } } - return p.parseIntOrFloat(b) + return p.scanIntOrFloat(node, b) } func digitsToInt(b []byte) int { @@ -653,7 +656,7 @@ func digitsToInt(b []byte) int { return x } -func (p parser) parseDateTime(b []byte) ([]byte, error) { +func (p *parser) parseDateTime(b []byte) ([]byte, error) { // we know the first 2 ar digits. if b[2] == ':' { return p.parseTime(b) @@ -709,16 +712,19 @@ func (p parser) parseDateTime(b []byte) ([]byte, error) { idx++ if idx >= len(b) { - p.builder.LocalDateValue(localDate) + //p.builder.LocalDateValue(localDate) + // TODO return nil, nil } else if b[idx] != ' ' && b[idx] != 'T' { - p.builder.LocalDateValue(localDate) + //p.builder.LocalDateValue(localDate) + // TODO return b[idx:], nil } // check if there is a chance there is anything useful after if b[idx] == ' ' && (((idx + 2) >= len(b)) || !isDigit(b[idx+1]) || !isDigit(b[idx+2])) { - p.builder.LocalDateValue(localDate) + //p.builder.LocalDateValue(localDate) + // TODO return b[idx:], nil } @@ -792,7 +798,9 @@ func (p parser) parseDateTime(b []byte) ([]byte, error) { Date: localDate, Time: localTime, } - p.builder.LocalDateTimeValue(dt) + //p.builder.LocalDateTimeValue(dt) + // TODO + dt = dt return b[idx:], nil } @@ -838,11 +846,13 @@ func (p parser) parseDateTime(b []byte) ([]byte, error) { loc = time.FixedZone(string(b[start:idx]), offset) } dt := time.Date(localDate.Year, localDate.Month, localDate.Day, localTime.Hour, localTime.Minute, localTime.Second, localTime.Nanosecond, loc) - p.builder.DateTimeValue(dt) + //p.builder.DateTimeValue(dt) + // TODO + dt = dt return b[idx:], nil } -func (p parser) parseTime(b []byte) ([]byte, error) { +func (p *parser) parseTime(b []byte) ([]byte, error) { localTime := LocalTime{} idx := 0 @@ -902,230 +912,84 @@ func (p parser) parseTime(b []byte) ([]byte, error) { } } - p.builder.LocalTimeValue(localTime) + //p.builder.LocalTimeValue(localTime) + // TODO return b[idx:], nil } -func (p parser) parseIntOrFloat(b []byte) ([]byte, error) { +func (p *parser) scanIntOrFloat(node *ast.Node, b []byte) ([]byte, error) { i := 0 - r := b[0] - if r == '0' { - if len(b) >= 2 { - var isValidRune validRuneFn - var parseFn func([]byte) (int64, error) - switch b[1] { - case 'x': - isValidRune = isValidHexRune - parseFn = parseIntHex - case 'o': - isValidRune = isValidOctalRune - parseFn = parseIntOct - case 'b': - isValidRune = isValidBinaryRune - parseFn = parseIntBin - default: - if b[1] >= 'a' && b[1] <= 'z' || b[1] >= 'A' && b[1] <= 'Z' { - return nil, fmt.Errorf("unknown number base: %s. possible options are x (hex) o (octal) b (binary)", string(b[1])) - } - parseFn = parseIntDec - } - if isValidRune != nil { - i = 2 - digitSeen := false - for { - if !isValidRune(b[i]) { - break - } - digitSeen = true - i++ - } - - if !digitSeen { - return nil, fmt.Errorf("number needs at least one digit") - } + if len(b) > 2 && b[0] == '0' { + var isValidRune validRuneFn + switch b[1] { + case 'x': + isValidRune = isValidHexRune + case 'o': + isValidRune = isValidOctalRune + case 'b': + isValidRune = isValidBinaryRune + default: + return b, fmt.Errorf("unknown number base: %c. possible options are x (hex) o (octal) b (binary)", b[1]) + } - v, err := parseFn(b[:i]) - if err != nil { - return nil, err - } - p.builder.IntValue(v) + i += 2 + for ; i < len(b); i++ { + if !isValidRune(b[i]) { + node.Kind = ast.Integer + node.Data = b[:i] return b[i:], nil } } } - if r == '+' || r == '-' { - b = b[1:] - if scanFollowsInf(b) { - if r == '+' { - p.builder.FloatValue(plusInf) - } else { - p.builder.FloatValue(minusInf) - } - return b, nil - } - if scanFollowsNan(b) { - p.builder.FloatValue(nan) - return b, nil - } - } + isFloat := false - pointSeen := false - expSeen := false - digitSeen := false - for i < len(b) { - next := b[i] - if next == '.' { - if pointSeen { - return nil, fmt.Errorf("cannot have two dots in one float") - } - i++ - if i < len(b) && !isDigit(b[i]) { - return nil, fmt.Errorf("float cannot end with a dot") - } - pointSeen = true - } else if next == 'e' || next == 'E' { - expSeen = true - i++ - if i >= len(b) { - break - } - if b[i] == '+' || b[i] == '-' { - i++ - } - } else if isDigit(next) { - digitSeen = true - i++ - } else if next == '_' { - i++ - } else { - break - } - if pointSeen && !digitSeen { - return nil, fmt.Errorf("cannot start float with a dot") - } - } + for ; i < len(b); i++ { + c := b[i] - if !digitSeen { - return nil, fmt.Errorf("no digit in that number") - } - if pointSeen || expSeen { - f, err := parseFloat(b[:i]) - if err != nil { - return nil, err - } - p.builder.FloatValue(f) - } else { - v, err := parseIntDec(b[:i]) - if err != nil { - return nil, err + if c >= '0' && c <= '9' || c == '+' || c == '-' || c == '_' { + continue } - p.builder.IntValue(v) - } - return b[i:], nil -} - -func parseFloat(b []byte) (float64, error) { - // TODO: inefficient - tok := string(b) - err := numberContainsInvalidUnderscore(tok) - if err != nil { - return 0, err - } - cleanedVal := cleanupNumberToken(tok) - return strconv.ParseFloat(cleanedVal, 64) -} - -func parseIntHex(b []byte) (int64, error) { - tok := string(b) - cleanedVal := cleanupNumberToken(tok) - err := hexNumberContainsInvalidUnderscore(cleanedVal) - if err != nil { - return 0, nil - } - return strconv.ParseInt(cleanedVal[2:], 16, 64) -} - -func parseIntOct(b []byte) (int64, error) { - tok := string(b) - cleanedVal := cleanupNumberToken(tok) - err := numberContainsInvalidUnderscore(cleanedVal) - if err != nil { - return 0, err - } - return strconv.ParseInt(cleanedVal[2:], 8, 64) -} -func parseIntBin(b []byte) (int64, error) { - tok := string(b) - cleanedVal := cleanupNumberToken(tok) - err := numberContainsInvalidUnderscore(cleanedVal) - if err != nil { - return 0, err - } - return strconv.ParseInt(cleanedVal[2:], 2, 64) -} - -func parseIntDec(b []byte) (int64, error) { - tok := string(b) - cleanedVal := cleanupNumberToken(tok) - err := numberContainsInvalidUnderscore(cleanedVal) - if err != nil { - return 0, err - } - return strconv.ParseInt(cleanedVal, 10, 64) -} + if c == '.' || c == 'e' || c == 'E' { + isFloat = true + continue + } -func numberContainsInvalidUnderscore(value string) error { - // For large numbers, you may use underscores between digits to enhance - // readability. Each underscore must be surrounded by at least one digit on - // each side. - - hasBefore := false - for idx, r := range value { - if r == '_' { - if !hasBefore || idx+1 >= len(value) { - // can't end with an underscore - return errInvalidUnderscore + if c == 'i' { + if scanFollowsInf(b[i:]) { + node.Kind = ast.Float + node.Data = b[:i+3] + return b[i+3:], nil } + return nil, fmt.Errorf("unexpected character i while scanning for a number") } - hasBefore = isDigitRune(r) - } - return nil -} - -func hexNumberContainsInvalidUnderscore(value string) error { - hasBefore := false - for idx, r := range value { - if r == '_' { - if !hasBefore || idx+1 >= len(value) { - // can't end with an underscore - return errInvalidUnderscoreHex + if c == 'n' { + if scanFollowsNan(b[i:]) { + node.Kind = ast.Float + node.Data = b[:i+3] + return b[i+3:], nil } + return nil, fmt.Errorf("unexpected character n while scanning for a number") } - hasBefore = isHexDigit(r) + + break } - return nil -} -func cleanupNumberToken(value string) string { - cleanedVal := strings.Replace(value, "_", "", -1) - return cleanedVal + if isFloat { + node.Kind = ast.Float + } else { + node.Kind = ast.Integer + } + node.Data = b[:i] + return b[i:], nil } func isDigit(r byte) bool { return r >= '0' && r <= '9' } -func isDigitRune(r rune) bool { - return r >= '0' && r <= '9' -} - -var plusInf = math.Inf(1) -var minusInf = math.Inf(-1) -var nan = math.NaN() - type validRuneFn func(r byte) bool func isValidHexRune(r byte) bool { @@ -1135,12 +999,6 @@ func isValidHexRune(r byte) bool { r == '_' } -func isHexDigit(r rune) bool { - return isDigitRune(r) || - (r >= 'a' && r <= 'f') || - (r >= 'A' && r <= 'F') -} - func isValidOctalRune(r byte) bool { return r >= '0' && r <= '7' || r == '_' } @@ -1168,6 +1026,3 @@ func (u unexpectedCharacter) Error() string { } return fmt.Sprintf("expected %#U, not %#U", u.r, u.b[0]) } - -var errInvalidUnderscore = errors.New("invalid use of _ in number") -var errInvalidUnderscoreHex = errors.New("invalid use of _ in hex number") diff --git a/internal/unmarshaler/parser_test.go b/parser_test.go similarity index 99% rename from internal/unmarshaler/parser_test.go rename to parser_test.go index b7aabfa7..21826539 100644 --- a/internal/unmarshaler/parser_test.go +++ b/parser_test.go @@ -1,4 +1,4 @@ -package unmarshaler +package toml import ( "testing" diff --git a/scanner.go b/scanner.go index 36a45917..9ee67faf 100644 --- a/scanner.go +++ b/scanner.go @@ -113,6 +113,7 @@ func scanComment(b []byte) ([]byte, []byte, error) { return b, nil, nil } +// TODO perform validation on the string? func scanBasicString(b []byte) ([]byte, []byte, error) { //basic-string = quotation-mark *basic-char quotation-mark //quotation-mark = %x22 ; " @@ -136,6 +137,7 @@ func scanBasicString(b []byte) ([]byte, []byte, error) { return nil, nil, fmt.Errorf(`basic string not terminated by "`) } +// TODO perform validation on the string? func scanMultilineBasicString(b []byte) ([]byte, []byte, error) { //ml-basic-string = ml-basic-string-delim [ newline ] ml-basic-body //ml-basic-string-delim diff --git a/internal/unmarshaler/targets.go b/targets.go similarity index 99% rename from internal/unmarshaler/targets.go rename to targets.go index af3c5c74..11ccd3de 100644 --- a/internal/unmarshaler/targets.go +++ b/targets.go @@ -1,4 +1,4 @@ -package unmarshaler +package toml import ( "fmt" diff --git a/internal/unmarshaler/targets_test.go b/targets_test.go similarity index 99% rename from internal/unmarshaler/targets_test.go rename to targets_test.go index 7db9993c..25d63581 100644 --- a/internal/unmarshaler/targets_test.go +++ b/targets_test.go @@ -1,4 +1,4 @@ -package unmarshaler +package toml import ( "reflect" diff --git a/unmarshal.go b/unmarshal.go deleted file mode 100644 index 80efa521..00000000 --- a/unmarshal.go +++ /dev/null @@ -1,359 +0,0 @@ -package toml - -import ( - "fmt" - "reflect" - "time" - - "github.com/pelletier/go-toml/v2/internal/reflectbuild" -) - -func Unmarshal(data []byte, v interface{}) error { - u := &unmarshaler{} - u.builder, u.err = reflectbuild.NewBuilder("toml", v) - if u.err == nil { - parseErr := parser{builder: u}.parse(data) - if parseErr != nil { - return parseErr - } - } - return u.err -} - -type unmarshaler struct { - builder reflectbuild.Builder - - // First error that appeared during the construction of the object. - // When set all callbacks are no-ops. - err error - - // State that indicates the parser is processing a [[table-array]] name. - // If false keys are interpreted as part of a key-value or [table]. - parsingTableArray bool - - // Table Arrays need a buffer of keys because we need to know which one is - // the last one, as it may result in creating a new element in the array. - arrayTableKey [][]byte - - // Flag to indicate that the next value is an an assignment. - // Assignments are when the builder already points to the value, and should - // be directly assigned. This is used to distinguish between assigning or - // appending to arrays. - assign bool - - // State that indicates the parser is processing a [table] name. - // Used to know whether the whole table should be skipped or just the - // keyval if a field is missing. - parsingTable bool - - // Counters that indicate that we are skipping TOML expressions. It happens - // when the document contains values that are not in the target struct. - // TODO: signal the parser that it can just scan to avoid processing the - // unused data. - skipKeyValCount uint - skipTable bool -} - -func (u *unmarshaler) skipping() bool { - return u.skipTable || u.skipKeyValCount > 0 -} - -func (u *unmarshaler) Assignation() { - if u.skipping() || u.err != nil { - return - } - u.assign = true - fmt.Println("ASSIGN: TRUE!") -} - -func (u *unmarshaler) ArrayBegin() { - if u.skipping() || u.err != nil { - return - } - u.builder.Save() - u.err = u.builder.EnsureSlice() - if u.err != nil { - return - } - fmt.Println("ARRAY BEGIN ASSIGN =", u.assign) - if !u.assign { - //u.err = u.builder.SliceNewSlice() - // TODO - } - u.assign = false -} - -func (u *unmarshaler) ArrayEnd() { - if u.skipping() || u.err != nil { - return - } - u.builder.Load() -} - -func (u *unmarshaler) ArrayTableBegin() { - if u.skipping() || u.err != nil { - return - } - - u.parsingTableArray = true -} - -func (u *unmarshaler) ArrayTableEnd() { - if u.skipping() || u.err != nil { - return - } - - u.builder.Reset() - - for _, v := range u.arrayTableKey[:len(u.arrayTableKey)-1] { - u.err = u.builder.DigField(string(v)) - if u.err != nil { - return - } - u.err = u.builder.SliceLastOrCreate() - } - - v := u.arrayTableKey[len(u.arrayTableKey)-1] - u.err = u.builder.DigField(string(v)) - if u.err != nil { - return - } - u.err = u.builder.SliceNewElem() - - u.parsingTableArray = false - u.arrayTableKey = u.arrayTableKey[:0] -} - -func (u *unmarshaler) InlineTableBegin() { - if u.skipping() || u.err != nil { - return - } - - u.builder.Save() - - if u.builder.IsSliceOrPtr() { - u.err = u.builder.SliceNewElem() - } else { - u.err = u.builder.EnsureStructOrMap() - } - - u.assign = false -} - -func (u *unmarshaler) InlineTableEnd() { - if u.skipping() || u.err != nil { - return - } - - u.builder.Load() -} - -func (u *unmarshaler) KeyValBegin() { - if u.skipKeyValCount > 0 { - u.skipKeyValCount++ - return - } - if u.skipping() || u.err != nil { - return - } - u.builder.Save() -} - -func (u *unmarshaler) KeyValEnd() { - if u.skipKeyValCount > 0 { - u.skipKeyValCount-- - return - } - if u.skipping() || u.err != nil { - return - } - u.builder.Load() -} - -func (u *unmarshaler) StringValue(v []byte) { - if u.skipping() || u.err != nil { - return - } - if u.builder.IsSliceOrPtr() { - u.builder.Save() - s := string(v) - u.err = u.builder.SliceAppend(reflect.ValueOf(&s)) - if u.err != nil { - return - } - u.builder.Load() - } else { - s := string(v) - u.err = u.builder.Set(reflect.ValueOf(&s)) - } - u.assign = false -} - -func (u *unmarshaler) BoolValue(b bool) { - if u.skipping() || u.err != nil { - return - } - if u.builder.IsSliceOrPtr() { - u.builder.Save() - u.err = u.builder.SliceAppend(reflect.ValueOf(&b)) - if u.err != nil { - return - } - u.builder.Load() - } else { - u.err = u.builder.SetBool(b) - } - u.assign = false -} - -func (u *unmarshaler) FloatValue(n float64) { - if u.skipping() || u.err != nil { - return - } - if u.builder.IsSliceOrPtr() { - u.builder.Save() - u.err = u.builder.SliceAppend(reflect.ValueOf(&n)) - if u.err != nil { - return - } - u.builder.Load() - } else { - u.err = u.builder.Set(reflect.ValueOf(&n)) - //u.err = u.builder.SetFloat(n) - } - u.assign = false -} - -func (u *unmarshaler) IntValue(n int64) { - if u.skipping() || u.err != nil { - return - } - if u.builder.IsSliceOrPtr() { - u.builder.Save() - u.err = u.builder.SliceAppend(reflect.ValueOf(&n)) - if u.err != nil { - return - } - u.builder.Load() - } else { - u.err = u.builder.Set(reflect.ValueOf(&n)) - } - u.assign = false -} - -func (u *unmarshaler) LocalDateValue(date LocalDate) { - if u.skipping() || u.err != nil { - return - } - if u.builder.IsSliceOrPtr() { - u.builder.Save() - u.err = u.builder.SliceAppend(reflect.ValueOf(&date)) - if u.err != nil { - return - } - u.builder.Load() - } else { - u.err = u.builder.Set(reflect.ValueOf(&date)) - } - u.assign = false -} - -func (u *unmarshaler) LocalDateTimeValue(dt LocalDateTime) { - if u.skipping() || u.err != nil { - return - } - if u.builder.IsSliceOrPtr() { - u.builder.Save() - u.err = u.builder.SliceAppend(reflect.ValueOf(&dt)) - if u.err != nil { - return - } - u.builder.Load() - } else { - u.err = u.builder.Set(reflect.ValueOf(&dt)) - } - u.assign = false -} - -func (u *unmarshaler) DateTimeValue(dt time.Time) { - if u.skipping() || u.err != nil { - return - } - if u.builder.IsSliceOrPtr() { - u.builder.Save() - u.err = u.builder.SliceAppend(reflect.ValueOf(&dt)) - if u.err != nil { - return - } - u.builder.Load() - } else { - u.err = u.builder.Set(reflect.ValueOf(&dt)) - } - u.assign = false -} - -func (u *unmarshaler) LocalTimeValue(localTime LocalTime) { - if u.skipping() || u.err != nil { - return - } - if u.builder.IsSliceOrPtr() { - u.builder.Save() - u.err = u.builder.SliceAppend(reflect.ValueOf(&localTime)) - if u.err != nil { - return - } - u.builder.Load() - } else { - u.err = u.builder.Set(reflect.ValueOf(&localTime)) - } - u.assign = false -} - -func (u *unmarshaler) SimpleKey(v []byte) { - if u.skipping() || u.err != nil { - return - } - - if u.parsingTableArray { - u.arrayTableKey = append(u.arrayTableKey, v) - } else { - if u.builder.Cursor().Kind() == reflect.Slice { - u.err = u.builder.SliceLastOrCreate() - if u.err != nil { - return - } - } - u.err = u.builder.DigField(string(v)) - if u.err == nil { - return - } - if _, ok := u.err.(reflectbuild.FieldNotFoundError); ok { - u.err = nil - if u.parsingTable { - u.skipTable = true - } else { - u.skipKeyValCount = 1 - } - } - // TODO: figure out what to do with unexported fields - } -} - -func (u *unmarshaler) StandardTableBegin() { - u.skipTable = false - u.parsingTable = true - if u.skipping() || u.err != nil { - return - } - // tables are only top-level - u.builder.Reset() -} - -func (u *unmarshaler) StandardTableEnd() { - u.parsingTable = false - if u.skipping() || u.err != nil { - return - } - - u.builder.EnsureStructOrMap() // TODO: handle error -} diff --git a/unmarshal_test.go b/unmarshal_test.go deleted file mode 100644 index b5fd8f22..00000000 --- a/unmarshal_test.go +++ /dev/null @@ -1,361 +0,0 @@ -package toml_test - -import ( - "testing" - - "github.com/pelletier/go-toml/v2" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestUnmarshalSimple(t *testing.T) { - x := struct{ Foo string }{} - err := toml.Unmarshal([]byte(`Foo = "hello"`), &x) - require.NoError(t, err) - assert.Equal(t, "hello", x.Foo) -} - -func TestUnmarshalInt(t *testing.T) { - x := struct{ Foo int }{} - err := toml.Unmarshal([]byte(`Foo = 42`), &x) - require.NoError(t, err) - assert.Equal(t, 42, x.Foo) -} - -func TestUnmarshalNestedStructs(t *testing.T) { - x := struct{ Foo struct{ Bar string } }{} - err := toml.Unmarshal([]byte(`Foo.Bar = "hello"`), &x) - require.NoError(t, err) - assert.Equal(t, "hello", x.Foo.Bar) -} - -func TestUnmarshalNestedStructsMultipleExpressions(t *testing.T) { - x := struct { - A struct{ B string } - C string - }{} - err := toml.Unmarshal([]byte(`A.B = "hello" -C = "test"`), &x) - require.NoError(t, err) - assert.Equal(t, "hello", x.A.B) - assert.Equal(t, "test", x.C) -} - -func TestUnmarshalTable(t *testing.T) { - x := struct { - Foo struct { - A string - B string - C string - } - Bar struct { - D string - } - E string - }{} - err := toml.Unmarshal([]byte(` - -E = "E" -Foo.C = "C" - -[Foo] -A = "A" -B = 'B' - -[Bar] -D = "D" - -`), &x) - require.NoError(t, err) - assert.Equal(t, "A", x.Foo.A) - assert.Equal(t, "B", x.Foo.B) - assert.Equal(t, "C", x.Foo.C) - assert.Equal(t, "D", x.Bar.D) - assert.Equal(t, "E", x.E) -} - -func TestUnmarshalDoesNotEraseBaseStruct(t *testing.T) { - x := struct { - A string - B string - }{ - A: "preset", - } - err := toml.Unmarshal([]byte(`B = "data"`), &x) - - require.NoError(t, err) - assert.Equal(t, "preset", x.A) - assert.Equal(t, "data", x.B) -} - -func TestArrayTableSimple(t *testing.T) { - doc := ` -[[Products]] -Name = "Hammer" - -[[Products]] -Name = "Nail" -` - - type Product struct { - Name string - } - - type Data struct { - Products []Product - } - - x := Data{} - err := toml.Unmarshal([]byte(doc), &x) - - require.NoError(t, err) - - expected := Data{ - Products: []Product{ - { - Name: "Hammer", - }, - { - Name: "Nail", - }, - }, - } - - assert.Equal(t, expected, x) -} - -func TestUnmarshalArrayTablesMultiple(t *testing.T) { - doc := ` -[[Products]] -Name = "Hammer" -Sku = "738594937" - -[[Products]] # empty table within the array - -[[Products]] -Name = "Nail" -Sku = "284758393" - -Color = "gray" -` - - type Product struct { - Name string - Sku string - Color string - } - - type Data struct { - Products []Product - } - - x := Data{} - err := toml.Unmarshal([]byte(doc), &x) - - require.NoError(t, err) - - expected := Data{ - Products: []Product{ - { - Name: "Hammer", - Sku: "738594937", - }, - {}, - { - Name: "Nail", - Sku: "284758393", - Color: "gray", - }, - }, - } - - assert.Equal(t, expected, x) -} - -func TestUnmarshalArrayTablesNested(t *testing.T) { - doc := ` -[[Fruits]] -Name = "apple" - -[Fruits.Physical] # subtable -Color = "red" -Shape = "round" - -[[Fruits.Varieties]] # nested array of tables -Name = "red delicious" - -[[Fruits.Varieties]] -Name = "granny smith" - - -[[Fruits]] -Name = "banana" - -[[Fruits.Varieties]] -Name = "plantain" -` - type Variety struct { - Name string - } - - type Physical struct { - Color string - Shape string - } - - type Fruit struct { - Name string - Physical Physical - Varieties []Variety - } - - type Doc struct { - Fruits []Fruit - } - - x := Doc{} - err := toml.Unmarshal([]byte(doc), &x) - require.NoError(t, err) - - expected := Doc{ - Fruits: []Fruit{ - { - Name: "apple", - Physical: Physical{ - Color: "red", - Shape: "round", - }, - Varieties: []Variety{ - {Name: "red delicious"}, - {Name: "granny smith"}, - }, - }, - { - Name: "banana", - Varieties: []Variety{ - {Name: "plantain"}, - }, - }, - }, - } - - assert.Equal(t, expected, x) -} - -func TestUnmarshalArraySimple(t *testing.T) { - x := struct { - Foo []string - }{} - doc := `Foo = ["hello", "world"]` - err := toml.Unmarshal([]byte(doc), &x) - require.NoError(t, err) - assert.Equal(t, []string{"hello", "world"}, x.Foo) -} - -func TestUnmarshalArrayNestedInTable(t *testing.T) { - x := struct { - Wrapper struct { - Foo []string - } - }{} - doc := `[Wrapper] -Foo = ["hello", "world"]` - err := toml.Unmarshal([]byte(doc), &x) - require.NoError(t, err) - assert.Equal(t, []string{"hello", "world"}, x.Wrapper.Foo) -} - -func TestUnmarshalArrayMixed(t *testing.T) { - x := struct { - Wrapper struct { - Foo []interface{} - } - }{} - doc := `[Wrapper] -Foo = ["hello", true]` - err := toml.Unmarshal([]byte(doc), &x) - require.NoError(t, err) - assert.Equal(t, []interface{}{"hello", true}, x.Wrapper.Foo) -} - -func TestUnmarshalArrayNested(t *testing.T) { - x := struct { - Foo [][]string - }{} - doc := `Foo = [["hello", "world"], ["a"], []]` - err := toml.Unmarshal([]byte(doc), &x) - require.NoError(t, err) - assert.Equal(t, [][]string{{"hello", "world"}, {"a"}, nil}, x.Foo) -} - -func TestUnmarshalBool(t *testing.T) { - x := struct { - Truthy bool - Falsey bool - }{Falsey: true} - doc := `Truthy = true -Falsey = false` - err := toml.Unmarshal([]byte(doc), &x) - require.NoError(t, err) - assert.Equal(t, true, x.Truthy) - assert.Equal(t, false, x.Falsey) -} - -func TestUnmarshalBoolArray(t *testing.T) { - x := struct{ Bits []bool }{} - doc := `Bits = [true, false, true, true]` - err := toml.Unmarshal([]byte(doc), &x) - require.NoError(t, err) - assert.Equal(t, []bool{true, false, true, true}, x.Bits) -} - -func TestUnmarshalInlineTable(t *testing.T) { - doc := ` - Name = { First = "Tom", Last = "Preston-Werner" } - Point = { X = "1", Y = "2" } - Animal = { Type.Name = "pug" }` - - type Name struct { - First string - Last string - } - - type Point struct { - X string - Y string - } - - type Type struct { - Name string - } - - type Animal struct { - Type Type - } - - type Doc struct { - Name Name - Point Point - Animal Animal - } - x := Doc{} - err := toml.Unmarshal([]byte(doc), &x) - require.NoError(t, err) - - expected := Doc{ - Name: Name{ - First: "Tom", - Last: "Preston-Werner", - }, - Point: Point{ - X: "1", - Y: "2", - }, - Animal: Animal{ - Type: Type{ - Name: "pug", - }, - }, - } - assert.Equal(t, expected, x) -} diff --git a/internal/unmarshaler/unmarshaler.go b/unmarshaler.go similarity index 99% rename from internal/unmarshaler/unmarshaler.go rename to unmarshaler.go index ab63b896..e40449df 100644 --- a/internal/unmarshaler/unmarshaler.go +++ b/unmarshaler.go @@ -1,4 +1,4 @@ -package unmarshaler +package toml import ( "fmt" diff --git a/internal/unmarshaler/unmarshaler_test.go b/unmarshaler_test.go similarity index 99% rename from internal/unmarshaler/unmarshaler_test.go rename to unmarshaler_test.go index 4c45e4ec..f9df67e9 100644 --- a/internal/unmarshaler/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -1,4 +1,4 @@ -package unmarshaler +package toml import ( "math" From ad64e5d2e2576a30c2a9d753bd9e13496aaf3a35 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Mon, 15 Mar 2021 08:53:16 -0400 Subject: [PATCH 077/228] Update README for v2 work --- README.md | 156 +++++------------------------------------------------- 1 file changed, 13 insertions(+), 143 deletions(-) diff --git a/README.md b/README.md index a592d256..2d8dc3da 100644 --- a/README.md +++ b/README.md @@ -1,150 +1,20 @@ -# go-toml +# go-toml V2 -Go library for the [TOML](https://toml.io/) format. +Development branch. Probably does not work. -This library supports TOML version -[v1.0.0-rc.3](https://toml.io/en/v1.0.0-rc.3) +[👉 Discussion on github](https://github.com/pelletier/go-toml/discussions/471). -[![Go Reference](https://pkg.go.dev/badge/github.com/pelletier/go-toml.svg)](https://pkg.go.dev/github.com/pelletier/go-toml) -[![license](https://img.shields.io/github/license/pelletier/go-toml.svg)](https://github.com/pelletier/go-toml/blob/master/LICENSE) -[![Build Status](https://dev.azure.com/pelletierthomas/go-toml-ci/_apis/build/status/pelletier.go-toml?branchName=master)](https://dev.azure.com/pelletierthomas/go-toml-ci/_build/latest?definitionId=1&branchName=master) -[![codecov](https://codecov.io/gh/pelletier/go-toml/branch/master/graph/badge.svg)](https://codecov.io/gh/pelletier/go-toml) -[![Go Report Card](https://goreportcard.com/badge/github.com/pelletier/go-toml)](https://goreportcard.com/report/github.com/pelletier/go-toml) -[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fpelletier%2Fgo-toml.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fpelletier%2Fgo-toml?ref=badge_shield) +## Todo -## Features - -Go-toml provides the following features for using data parsed from TOML documents: - -* Load TOML documents from files and string data -* Easily navigate TOML structure using Tree -* Marshaling and unmarshaling to and from data structures -* Line & column position data for all parsed elements -* [Query support similar to JSON-Path](query/) -* Syntax errors contain line and column numbers - -## Import - -```go -import "github.com/pelletier/go-toml" -``` - -## Usage example - -Read a TOML document: - -```go -config, _ := toml.Load(` -[postgres] -user = "pelletier" -password = "mypassword"`) -// retrieve data directly -user := config.Get("postgres.user").(string) - -// or using an intermediate object -postgresConfig := config.Get("postgres").(*toml.Tree) -password := postgresConfig.Get("password").(string) -``` - -Or use Unmarshal: - -```go -type Postgres struct { - User string - Password string -} -type Config struct { - Postgres Postgres -} - -doc := []byte(` -[Postgres] -User = "pelletier" -Password = "mypassword"`) - -config := Config{} -toml.Unmarshal(doc, &config) -fmt.Println("user=", config.Postgres.User) -``` - -Or use a query: - -```go -// use a query to gather elements without walking the tree -q, _ := query.Compile("$..[user,password]") -results := q.Execute(config) -for ii, item := range results.Values() { - fmt.Printf("Query result %d: %v\n", ii, item) -} -``` - -## Documentation - -The documentation and additional examples are available at -[pkg.go.dev](https://pkg.go.dev/github.com/pelletier/go-toml). - -## Tools - -Go-toml provides three handy command line tools: - -* `tomll`: Reads TOML files and lints them. - - ``` - go install github.com/pelletier/go-toml/cmd/tomll - tomll --help - ``` -* `tomljson`: Reads a TOML file and outputs its JSON representation. - - ``` - go install github.com/pelletier/go-toml/cmd/tomljson - tomljson --help - ``` - - * `jsontoml`: Reads a JSON file and outputs a TOML representation. - - ``` - go install github.com/pelletier/go-toml/cmd/jsontoml - jsontoml --help - ``` - -### Docker image - -Those tools are also availble as a Docker image from -[dockerhub](https://hub.docker.com/r/pelletier/go-toml). For example, to -use `tomljson`: - -``` -docker run -v $PWD:/workdir pelletier/go-toml tomljson /workdir/example.toml -``` - -Only master (`latest`) and tagged versions are published to dockerhub. You -can build your own image as usual: - -``` -docker build -t go-toml . -``` - -## Contribute - -Feel free to report bugs and patches using GitHub's pull requests system on -[pelletier/go-toml](https://github.com/pelletier/go-toml). Any feedback would be -much appreciated! - -### Run tests - -`go test ./...` - -### Fuzzing - -The script `./fuzz.sh` is available to -run [go-fuzz](https://github.com/dvyukov/go-fuzz) on go-toml. - -## Versioning - -Go-toml follows [Semantic Versioning](http://semver.org/). The supported version -of [TOML](https://github.com/toml-lang/toml) is indicated at the beginning of -this document. The last two major versions of Go are supported -(see [Go Release Policy](https://golang.org/doc/devel/release.html#policy)). +- [ ] Unmarshal into maps. +- [ ] Attach comments to AST (gated by parser flag). +- [ ] Abstract AST. +- [ ] Rewrite AST to use a single array as storage instead of one allocation + per node. +- [ ] Support Date / times. +- [ ] Support Unmarshaler interface. +- [ ] Support struct tags annotations. +- [ ] Benchmark! ## License From 1718142ede119617ad09bb11e54390130a60a35c Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Mon, 15 Mar 2021 09:04:54 -0400 Subject: [PATCH 078/228] More todos in README --- README.md | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2d8dc3da..9f3678af 100644 --- a/README.md +++ b/README.md @@ -4,18 +4,29 @@ Development branch. Probably does not work. [👉 Discussion on github](https://github.com/pelletier/go-toml/discussions/471). -## Todo +## Must do - [ ] Unmarshal into maps. - [ ] Attach comments to AST (gated by parser flag). - [ ] Abstract AST. -- [ ] Rewrite AST to use a single array as storage instead of one allocation - per node. +- [ ] Support Array Tables - [ ] Support Date / times. - [ ] Support Unmarshaler interface. - [ ] Support struct tags annotations. - [ ] Benchmark! +## Further work + +- [ ] Rewrite AST to use a single array as storage instead of one allocation per + node. +- [ ] Provide "minimal allocations" option that uses `unsafe` to reuse the input + byte array as storage for strings. + +## Ideas + +- [ ] Allow types to implement a `ASTUnmarshaler` interface to unmarshal + straight from the AST? + ## License The MIT License (MIT). Read [LICENSE](LICENSE). From 37d06dabcf67055efa4598f04d506422d88db2c3 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Mon, 15 Mar 2021 09:49:10 -0400 Subject: [PATCH 079/228] Unmarshal into maps --- README.md | 2 +- targets.go | 150 ++++++++++++++++++++++++++++++++------------ targets_test.go | 16 ++--- unmarshaler.go | 12 ++-- unmarshaler_test.go | 107 +++++++++++++++++++++++++------ 5 files changed, 213 insertions(+), 74 deletions(-) diff --git a/README.md b/README.md index 9f3678af..75a11062 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Development branch. Probably does not work. ## Must do -- [ ] Unmarshal into maps. +- [x] Unmarshal into maps. - [ ] Attach comments to AST (gated by parser flag). - [ ] Abstract AST. - [ ] Support Array Tables diff --git a/targets.go b/targets.go index 11ccd3de..bff99704 100644 --- a/targets.go +++ b/targets.go @@ -6,8 +6,8 @@ import ( ) type target interface { - // Ensure the target's value is compatible with a slice and initialized. - ensureSlice() error + // Dereferences the target. + get() reflect.Value // Store a string at the target. setString(v string) error @@ -21,12 +21,8 @@ type target interface { // Store a float64 at the target setFloat64(v float64) error - // Creates a new value of the container's element type, and returns a - // target to it. - pushNew() (target, error) - - // Dereferences the target. - get() reflect.Value + // Stores any value at the target + set(v reflect.Value) error } // valueTarget just contains a reflect.Value that can be set. @@ -37,21 +33,76 @@ func (t valueTarget) get() reflect.Value { return reflect.Value(t) } -func (t valueTarget) ensureSlice() error { +func (t valueTarget) set(v reflect.Value) error { + reflect.Value(t).Set(v) + return nil +} + +func (t valueTarget) setString(v string) error { + t.get().SetString(v) + return nil +} + +func (t valueTarget) setBool(v bool) error { + t.get().SetBool(v) + return nil +} + +func (t valueTarget) setInt64(v int64) error { + t.get().SetInt(v) + return nil +} + +func (t valueTarget) setFloat64(v float64) error { + t.get().SetFloat(v) + return nil +} + +// mapTarget targets a specific key of a map. +type mapTarget struct { + v reflect.Value + k reflect.Value +} + +func (t mapTarget) get() reflect.Value { + return t.v.MapIndex(t.k) +} + +func (t mapTarget) set(v reflect.Value) error { + t.v.SetMapIndex(t.k, v) + return nil +} + +func (t mapTarget) setString(v string) error { + return t.set(reflect.ValueOf(v)) +} + +func (t mapTarget) setBool(v bool) error { + return t.set(reflect.ValueOf(v)) +} + +func (t mapTarget) setInt64(v int64) error { + return t.set(reflect.ValueOf(v)) +} + +func (t mapTarget) setFloat64(v float64) error { + return t.set(reflect.ValueOf(v)) +} + +func ensureSlice(t target) error { f := t.get() switch f.Type().Kind() { case reflect.Slice: if f.IsNil() { - f.Set(reflect.MakeSlice(f.Type(), 0, 0)) + return t.set(reflect.MakeSlice(f.Type(), 0, 0)) } case reflect.Interface: if f.IsNil() { - f.Set(reflect.MakeSlice(reflect.TypeOf([]interface{}{}), 0, 0)) - } else { - if f.Type().Elem().Kind() != reflect.Slice { - return fmt.Errorf("interface is pointing to a %s, not a slice", f.Kind()) - } + return t.set(reflect.MakeSlice(reflect.TypeOf([]interface{}{}), 0, 0)) + } + if f.Type().Elem().Kind() != reflect.Slice { + return fmt.Errorf("interface is pointing to a %s, not a slice", f.Kind()) } default: return fmt.Errorf("cannot initialize a slice in %s", f.Kind()) @@ -59,76 +110,71 @@ func (t valueTarget) ensureSlice() error { return nil } -func (t valueTarget) setString(v string) error { +func setString(t target, v string) error { f := t.get() switch f.Kind() { case reflect.String: - f.SetString(v) + return t.setString(v) case reflect.Interface: - f.Set(reflect.ValueOf(v)) + return t.set(reflect.ValueOf(v)) default: - return fmt.Errorf("cannot assign string to a %s", f.String()) + return fmt.Errorf("cannot assign string to a %s", f.Kind()) } - - return nil } -func (t valueTarget) setBool(v bool) error { +func setBool(t target, v bool) error { f := t.get() switch f.Kind() { case reflect.Bool: - f.SetBool(v) + return t.setBool(v) case reflect.Interface: - f.Set(reflect.ValueOf(v)) + return t.set(reflect.ValueOf(v)) default: return fmt.Errorf("cannot assign bool to a %s", f.String()) } - - return nil } -func (t valueTarget) setInt64(v int64) error { +func setInt64(t target, v int64) error { f := t.get() switch f.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: // TODO: overflow checks - f.SetInt(v) + return t.setInt64(v) case reflect.Interface: - f.Set(reflect.ValueOf(v)) + return t.set(reflect.ValueOf(v)) default: return fmt.Errorf("cannot assign int64 to a %s", f.String()) } - - return nil } -func (t valueTarget) setFloat64(v float64) error { +func setFloat64(t target, v float64) error { f := t.get() switch f.Kind() { case reflect.Float32, reflect.Float64: // TODO: overflow checks - f.SetFloat(v) + return t.setFloat64(v) case reflect.Interface: - f.Set(reflect.ValueOf(v)) + return t.set(reflect.ValueOf(v)) default: return fmt.Errorf("cannot assign float64 to a %s", f.String()) } - - return nil } -func (t valueTarget) pushNew() (target, error) { +func pushNew(t target) (target, error) { f := t.get() switch f.Kind() { case reflect.Slice: idx := f.Len() - f.Set(reflect.Append(f, reflect.New(f.Type().Elem()).Elem())) - return valueTarget(f.Index(idx)), nil + err := t.set(reflect.Append(f, reflect.New(f.Type().Elem()).Elem())) + if err != nil { + return nil, err + } + return valueTarget(t.get().Index(idx)), nil case reflect.Interface: if f.IsNil() { panic("interface should have been initialized") @@ -140,8 +186,11 @@ func (t valueTarget) pushNew() (target, error) { idx := ifaceElem.Len() newElem := reflect.New(ifaceElem.Type().Elem()).Elem() newSlice := reflect.Append(ifaceElem, newElem) - f.Set(newSlice) - return valueTarget(f.Elem().Index(idx)), nil + err := t.set(newSlice) + if err != nil { + return nil, err + } + return valueTarget(t.get().Elem().Index(idx)), nil default: return nil, fmt.Errorf("cannot pushNew on a %s", f.Kind()) } @@ -162,11 +211,30 @@ func scope(v reflect.Value, name string) (target, error) { } else { return scope(v.Elem(), name) } + case reflect.Map: + return scopeMap(v, name) default: panic(fmt.Errorf("can't scope on a %s", v.Kind())) } } +func scopeMap(v reflect.Value, name string) (target, error) { + if v.IsNil() { + v.Set(reflect.MakeMap(v.Type())) + } + + k := reflect.ValueOf(name) + if !v.MapIndex(k).IsValid() { + newElem := reflect.New(v.Type().Elem()) + v.SetMapIndex(k, newElem.Elem()) + } + + return mapTarget{ + v: v, + k: k, + }, nil +} + func scopeStruct(v reflect.Value, name string) (target, error) { // TODO: cache this t := v.Type() diff --git a/targets_test.go b/targets_test.go index 25d63581..1522d22f 100644 --- a/targets_test.go +++ b/targets_test.go @@ -41,7 +41,7 @@ func TestStructTarget_Ensure(t *testing.T) { t.Run(e.desc, func(t *testing.T) { target, err := scope(e.input, e.name) require.NoError(t, err) - err = target.ensureSlice() + err = ensureSlice(target) v := target.get() e.test(v, err) }) @@ -88,7 +88,7 @@ func TestStructTarget_SetString(t *testing.T) { t.Run(e.desc, func(t *testing.T) { target, err := scope(e.input, e.name) require.NoError(t, err) - err = target.setString(str) + err = setString(target, str) v := target.get() e.test(v, err) }) @@ -105,12 +105,12 @@ func TestPushNew(t *testing.T) { x, err := scope(reflect.ValueOf(&d).Elem(), "A") require.NoError(t, err) - n, err := x.pushNew() + n, err := pushNew(x) require.NoError(t, err) require.NoError(t, n.setString("hello")) require.Equal(t, []string{"hello"}, d.A) - n, err = x.pushNew() + n, err = pushNew(x) require.NoError(t, err) require.NoError(t, n.setString("world")) require.Equal(t, []string{"hello", "world"}, d.A) @@ -125,14 +125,14 @@ func TestPushNew(t *testing.T) { x, err := scope(reflect.ValueOf(&d).Elem(), "A") require.NoError(t, err) - n, err := x.pushNew() + n, err := pushNew(x) require.NoError(t, err) - require.NoError(t, n.setString("hello")) + require.NoError(t, setString(n, "hello")) require.Equal(t, []interface{}{"hello"}, d.A) - n, err = x.pushNew() + n, err = pushNew(x) require.NoError(t, err) - require.NoError(t, n.setString("world")) + require.NoError(t, setString(n, "world")) require.Equal(t, []interface{}{"hello", "world"}, d.A) }) } diff --git a/unmarshaler.go b/unmarshaler.go index e40449df..2859c532 100644 --- a/unmarshaler.go +++ b/unmarshaler.go @@ -95,13 +95,13 @@ func unmarshalValue(x target, node *ast.Node) error { func unmarshalString(x target, node *ast.Node) error { assertNode(ast.String, node) - return x.setString(string(node.Data)) + return setString(x, string(node.Data)) } func unmarshalBool(x target, node *ast.Node) error { assertNode(ast.Bool, node) v := node.Data[0] == 't' - return x.setBool(v) + return setBool(x, v) } func unmarshalInteger(x target, node *ast.Node) error { @@ -110,7 +110,7 @@ func unmarshalInteger(x target, node *ast.Node) error { if err != nil { return err } - return x.setInt64(v) + return setInt64(x, v) } func unmarshalFloat(x target, node *ast.Node) error { @@ -119,7 +119,7 @@ func unmarshalFloat(x target, node *ast.Node) error { if err != nil { return err } - return x.setFloat64(v) + return setFloat64(x, v) } func unmarshalInlineTable(x target, node *ast.Node) error { @@ -137,13 +137,13 @@ func unmarshalInlineTable(x target, node *ast.Node) error { func unmarshalArray(x target, node *ast.Node) error { assertNode(ast.Array, node) - err := x.ensureSlice() + err := ensureSlice(x) if err != nil { return err } for _, n := range node.Children { - v, err := x.pushNew() + v, err := pushNew(x) if err != nil { return err } diff --git a/unmarshaler_test.go b/unmarshaler_test.go index f9df67e9..deb05329 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -172,6 +172,7 @@ func TestUnmarshal(t *testing.T) { type test struct { target interface{} expected interface{} + err bool } examples := []struct { desc string @@ -186,8 +187,8 @@ func TestUnmarshal(t *testing.T) { A string } return test{ - &doc{}, - &doc{A: "foo"}, + target: &doc{}, + expected: &doc{A: "foo"}, } }, }, @@ -199,8 +200,8 @@ func TestUnmarshal(t *testing.T) { A bool } return test{ - &doc{}, - &doc{A: true}, + target: &doc{}, + expected: &doc{A: true}, } }, }, @@ -212,8 +213,8 @@ func TestUnmarshal(t *testing.T) { A bool } return test{ - &doc{A: true}, - &doc{A: false}, + target: &doc{A: true}, + expected: &doc{A: false}, } }, }, @@ -225,8 +226,8 @@ func TestUnmarshal(t *testing.T) { A []string } return test{ - &doc{}, - &doc{A: []string{"foo", "bar"}}, + target: &doc{}, + expected: &doc{A: []string{"foo", "bar"}}, } }, }, @@ -242,8 +243,8 @@ B = "data"`, A A } return test{ - &doc{}, - &doc{A: A{B: "data"}}, + target: &doc{}, + expected: &doc{A: A{B: "data"}}, } }, }, @@ -259,8 +260,8 @@ B = "data"`, Name name } return test{ - &doc{}, - &doc{Name: name{ + target: &doc{}, + expected: &doc{Name: name{ First: "hello", Last: "world", }}, @@ -279,8 +280,8 @@ B = "data"`, Names []name } return test{ - &doc{}, - &doc{ + target: &doc{}, + expected: &doc{ Names: []name{ { First: "hello", @@ -295,14 +296,86 @@ B = "data"`, } }, }, + { + desc: "into map[string]interface{}", + input: `A = "foo"`, + gen: func() test { + doc := map[string]interface{}{} + return test{ + target: &doc, + expected: &map[string]interface{}{ + "A": "foo", + }, + } + }, + }, + { + desc: "multi keys of different types into map[string]interface{}", + input: `A = "foo" +B = 42`, + gen: func() test { + doc := map[string]interface{}{} + return test{ + target: &doc, + expected: &map[string]interface{}{ + "A": "foo", + "B": int64(42), + }, + } + }, + }, + { + desc: "slice in a map[string]interface{}", + input: `A = ["foo", "bar"]`, + gen: func() test { + doc := map[string]interface{}{} + return test{ + target: &doc, + expected: &map[string]interface{}{ + "A": []interface{}{"foo", "bar"}, + }, + } + }, + }, + { + desc: "string into map[string]string", + input: `A = "foo"`, + gen: func() test { + doc := map[string]string{} + return test{ + target: &doc, + expected: &map[string]string{ + "A": "foo", + }, + } + }, + }, + { + desc: "float64 into map[string]string", + input: `A = 42.0`, + gen: func() test { + doc := map[string]string{} + return test{ + target: &doc, + err: true, + } + }, + }, } for _, e := range examples { t.Run(e.desc, func(t *testing.T) { test := e.gen() + if test.err && test.expected != nil { + panic("invalid test: cannot expect both an error and a value") + } err := Unmarshal([]byte(e.input), test.target) - require.NoError(t, err) - assert.Equal(t, test.expected, test.target) + if test.err { + require.Error(t, err) + } else { + require.NoError(t, err) + assert.Equal(t, test.expected, test.target) + } }) } } @@ -437,8 +510,6 @@ func TestFromAst_Table(t *testing.T) { func TestFromAst_InlineTable(t *testing.T) { t.Run("one level of strings", func(t *testing.T) { - // name = { first = "Tom", last = "Preston-Werner" } - root := ast.Root{ ast.Node{ Kind: ast.KeyValue, From 844c9093a2fc24a5726e8244d5f53a3e04f94472 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Mon, 15 Mar 2021 09:50:01 -0400 Subject: [PATCH 080/228] Add todo --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 75a11062..e1ebf599 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ Development branch. Probably does not work. - [ ] Support Date / times. - [ ] Support Unmarshaler interface. - [ ] Support struct tags annotations. +- [ ] Original go-toml unmarshal tests pass. - [ ] Benchmark! ## Further work From c6892fcf5a310e0a3b8e3d701f1e012eb01b554e Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Mon, 15 Mar 2021 19:35:48 -0400 Subject: [PATCH 081/228] wip array table --- parser.go | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/parser.go b/parser.go index 34346c26..9459500e 100644 --- a/parser.go +++ b/parser.go @@ -98,21 +98,24 @@ func (p *parser) parseArrayTable(b []byte) (ast.Node, []byte, error) { //array-table-open = %x5B.5B ws ; [[ Double left square bracket //array-table-close = ws %x5D.5D ; ]] Double right square bracket - // TODO - //b = b[2:] - //b = p.parseWhitespace(b) - //b, err := p.parseKey(b) - //if err != nil { - // return nil, err - //} - //b = p.parseWhitespace(b) - //b, err = expect(']', b) - //if err != nil { - // return nil, err - //} - //return expect(']', b) - - return ast.NoNode, nil, nil + node := ast.Node{ + Kind: ast.ArrayTable, + } + + b = b[2:] + b = p.parseWhitespace(b) + k, b, err := p.parseKey(b) + if err != nil { + return node, nil, err + } + node.Children = k + b = p.parseWhitespace(b) + b, err = expect(']', b) + if err != nil { + return node, nil, err + } + b, err = expect(']', b) + return node, b, err } func (p *parser) parseStdTable(b []byte) (ast.Node, []byte, error) { From f9f9ccb7774a58d7b2c6c2b351a3bdc041f1749d Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Tue, 16 Mar 2021 10:24:19 -0400 Subject: [PATCH 082/228] Basic array table implementation --- internal/ast/ast.go | 2 +- parser.go | 4 +- targets.go | 27 +++++++++++++ unmarshaler.go | 35 +++++++++++++--- unmarshaler_test.go | 97 ++++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 156 insertions(+), 9 deletions(-) diff --git a/internal/ast/ast.go b/internal/ast/ast.go index eed8675a..0ec989ef 100644 --- a/internal/ast/ast.go +++ b/internal/ast/ast.go @@ -147,7 +147,7 @@ func (n *Node) Key() []Node { panic(fmt.Errorf("KeyValue should have at least two children, not %d", len(n.Children))) } return n.Children[:len(n.Children)-1] - case Table: + case Table, ArrayTable: return n.Children default: panic(fmt.Errorf("Key() is not supported on a %s", n.Kind)) diff --git a/parser.go b/parser.go index 9459500e..803e48a6 100644 --- a/parser.go +++ b/parser.go @@ -73,6 +73,8 @@ func (p *parser) parseExpression(b []byte) ([]byte, error) { return nil, err } + p.tree = append(p.tree, node) + b = p.parseWhitespace(b) if len(b) > 0 && b[0] == '#' { @@ -80,8 +82,6 @@ func (p *parser) parseExpression(b []byte) ([]byte, error) { return rest, err } - p.tree = append(p.tree, node) - return b, nil } diff --git a/targets.go b/targets.go index bff99704..417cdb59 100644 --- a/targets.go +++ b/targets.go @@ -201,6 +201,19 @@ func scopeTarget(t target, name string) (target, error) { return scope(x, name) } +func scopeTableTarget(append bool, t target, name string) (target, error) { + x := t.get() + t, err := scope(x, name) + if err != nil { + return t, err + } + x = t.get() + if x.Kind() == reflect.Slice { + return scopeSlice(t, append) + } + return t, nil +} + func scope(v reflect.Value, name string) (target, error) { switch v.Kind() { case reflect.Struct: @@ -218,6 +231,20 @@ func scope(v reflect.Value, name string) (target, error) { } } +func scopeSlice(t target, append bool) (target, error) { + v := t.get() + if append { + newElem := reflect.New(v.Type().Elem()) + newSlice := reflect.Append(v, newElem.Elem()) + err := t.set(newSlice) + if err != nil { + return t, err + } + v = t.get() + } + return valueTarget(v.Index(v.Len() - 1)), nil +} + func scopeMap(v reflect.Value, name string) (target, error) { if v.IsNil() { v.Set(reflect.MakeMap(v.Type())) diff --git a/unmarshaler.go b/unmarshaler.go index 2859c532..309fd38f 100644 --- a/unmarshaler.go +++ b/unmarshaler.go @@ -25,10 +25,11 @@ func fromAst(tree ast.Root, v interface{}) error { return fmt.Errorf("target pointer must be non-nil") } - var x target = valueTarget(r.Elem()) var err error + var root target = valueTarget(r.Elem()) + current := root for _, node := range tree { - x, err = unmarshalTopLevelNode(x, &node) + current, err = unmarshalTopLevelNode(root, current, &node) if err != nil { return err } @@ -39,12 +40,12 @@ func fromAst(tree ast.Root, v interface{}) error { // The target return value is the target for the next top-level node. Mostly // unchanged, except by table and array table. -func unmarshalTopLevelNode(x target, node *ast.Node) (target, error) { +func unmarshalTopLevelNode(root target, x target, node *ast.Node) (target, error) { switch node.Kind { case ast.Table: - return scopeWithKey(x, node.Key()) + return scopeWithTable(root, node.Key()) case ast.ArrayTable: - panic("TODO") + return scopeWithArrayTable(root, node.Key()) case ast.KeyValue: return x, unmarshalKeyValue(x, node) default: @@ -52,6 +53,30 @@ func unmarshalTopLevelNode(x target, node *ast.Node) (target, error) { } } +func scopeWithTable(x target, key []ast.Node) (target, error) { + var err error + for _, n := range key { + x, err = scopeTableTarget(false, x, string(n.Data)) + if err != nil { + return nil, err + } + } + return x, nil +} + +func scopeWithArrayTable(x target, key []ast.Node) (target, error) { + var err error + if len(key) > 1 { + for _, n := range key[:len(key)-1] { + x, err = scopeTableTarget(false, x, string(n.Data)) + if err != nil { + return nil, err + } + } + } + return scopeTableTarget(true, x, string(key[len(key)-1].Data)) +} + func scopeWithKey(x target, key []ast.Node) (target, error) { var err error for _, n := range key { diff --git a/unmarshaler_test.go b/unmarshaler_test.go index deb05329..8c682ed7 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -175,6 +175,7 @@ func TestUnmarshal(t *testing.T) { err bool } examples := []struct { + skip bool desc string input string gen func() test @@ -312,7 +313,7 @@ B = "data"`, { desc: "multi keys of different types into map[string]interface{}", input: `A = "foo" -B = 42`, + B = 42`, gen: func() test { doc := map[string]interface{}{} return test{ @@ -361,10 +362,104 @@ B = 42`, } }, }, + { + desc: "one-level one-element array table", + input: `[[First]] + Second = "hello"`, + gen: func() test { + type First struct { + Second string + } + type Doc struct { + First []First + } + return test{ + target: &Doc{}, + expected: &Doc{ + First: []First{ + { + Second: "hello", + }, + }, + }, + } + }, + }, + { + desc: "one-level multi-element array table", + input: `[[Products]] + Name = "Hammer" + Sku = 738594937 + + [[Products]] # empty table within the array + + [[Products]] + Name = "Nail" + Sku = 284758393 + + Color = "gray"`, + gen: func() test { + type Product struct { + Name string + Sku int64 + Color string + } + type Doc struct { + Products []Product + } + return test{ + target: &Doc{}, + expected: &Doc{ + Products: []Product{ + {Name: "Hammer", Sku: 738594937}, + {}, + {Name: "Nail", Sku: 284758393, Color: "gray"}, + }, + }, + } + }, + }, + { + skip: true, // TODO + desc: "one-level multi-element array table to map", + input: `[[Products]] + Name = "Hammer" + Sku = 738594937 + + [[Products]] # empty table within the array + + [[Products]] + Name = "Nail" + Sku = 284758393 + + Color = "gray"`, + gen: func() test { + return test{ + target: &map[string]interface{}{}, + expected: &map[string]interface{}{ + "Products": []interface{}{ + map[string]interface{}{ + "Name": "Hammer", + "Sku": 738594937, + }, + nil, + map[string]interface{}{ + "Name": "Nail", + "Sku": 284758393, + "Color": "gray", + }, + }, + }, + } + }, + }, } for _, e := range examples { t.Run(e.desc, func(t *testing.T) { + if e.skip { + t.Skip() + } test := e.gen() if test.err && test.expected != nil { panic("invalid test: cannot expect both an error and a value") From 939f8896661a72582149ec35351c517734e3bde8 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Wed, 17 Mar 2021 09:57:50 -0400 Subject: [PATCH 083/228] wip: figuring out unmarshaling to interfaces --- targets.go | 56 +++++++++++++++++++++++++++++++++------------ unmarshaler.go | 45 ++++++++++++++++++++++++++---------- unmarshaler_test.go | 1 - 3 files changed, 75 insertions(+), 27 deletions(-) diff --git a/targets.go b/targets.go index 417cdb59..b0f9db25 100644 --- a/targets.go +++ b/targets.go @@ -196,21 +196,54 @@ func pushNew(t target) (target, error) { } } -func scopeTarget(t target, name string) (target, error) { - x := t.get() - return scope(x, name) -} - func scopeTableTarget(append bool, t target, name string) (target, error) { x := t.get() + + if x.Kind() == reflect.Interface { + t, err := initInterface(append, t) + if err != nil { + return t, err + } + x = t.get() + } + + if x.Kind() == reflect.Slice { + return scopeSlice(t, append) + } + t, err := scope(x, name) if err != nil { return t, err } - x = t.get() - if x.Kind() == reflect.Slice { - return scopeSlice(t, append) + return t, nil +} + +// initInterface makes sure that the interface pointed at by the target is not +// nil. +// Returns the target to the initialized value of the target. +func initInterface(append bool, t target) (target, error) { + x := t.get() + + if x.Kind() != reflect.Interface { + panic("this should only be called on interfaces") + } + + if x.IsNil() { + var newElement reflect.Value + if append { + newElement = reflect.MakeSlice(reflect.TypeOf([]interface{}{}), 0, 0) + } else { + newElement = reflect.MakeMap(reflect.TypeOf(map[string]interface{}{})) + } + err := t.set(newElement) + if err != nil { + return t, err + } + x = t.get() } + + x = x.Elem() + t = valueTarget(x) return t, nil } @@ -218,12 +251,6 @@ func scope(v reflect.Value, name string) (target, error) { switch v.Kind() { case reflect.Struct: return scopeStruct(v, name) - case reflect.Interface: - if v.IsNil() { - panic("not implemented") // TODO - } else { - return scope(v.Elem(), name) - } case reflect.Map: return scopeMap(v, name) default: @@ -233,6 +260,7 @@ func scope(v reflect.Value, name string) (target, error) { func scopeSlice(t target, append bool) (target, error) { v := t.get() + if append { newElem := reflect.New(v.Type().Elem()) newSlice := reflect.Append(v, newElem.Elem()) diff --git a/unmarshaler.go b/unmarshaler.go index 309fd38f..478a127f 100644 --- a/unmarshaler.go +++ b/unmarshaler.go @@ -42,18 +42,26 @@ func fromAst(tree ast.Root, v interface{}) error { // unchanged, except by table and array table. func unmarshalTopLevelNode(root target, x target, node *ast.Node) (target, error) { switch node.Kind { + case ast.KeyValue: + return x, unmarshalKeyValue(x, node) case ast.Table: - return scopeWithTable(root, node.Key()) + return scopeWithKey(root, node.Key()) case ast.ArrayTable: return scopeWithArrayTable(root, node.Key()) - case ast.KeyValue: - return x, unmarshalKeyValue(x, node) default: panic(fmt.Errorf("this should not be a top level node type: %s", node.Kind)) } } -func scopeWithTable(x target, key []ast.Node) (target, error) { +// scopeWithKey performs target scoping when unmarshaling an ast.KeyValue node. +// +// The goal is to hop from target to target recursively using the names in key. +// Parts of the key should be used to resolve field names for structs, and as +// keys when targeting maps. +// +// When encountering slices, it should always use its last element, and error +// if the slice does not have any. +func scopeWithKey(x target, key []ast.Node) (target, error) { var err error for _, n := range key { x, err = scopeTableTarget(false, x, string(n.Data)) @@ -64,6 +72,11 @@ func scopeWithTable(x target, key []ast.Node) (target, error) { return x, nil } +// scopeWithArrayTable performs target scoping when unmarshaling an +// ast.ArrayTable node. +// +// It is the same as scopeWithKey, but when scoping the last part of the key +// it creates a new element in the array instead of using the last one. func scopeWithArrayTable(x target, key []ast.Node) (target, error) { var err error if len(key) > 1 { @@ -74,18 +87,26 @@ func scopeWithArrayTable(x target, key []ast.Node) (target, error) { } } } - return scopeTableTarget(true, x, string(key[len(key)-1].Data)) -} + x, err = scopeTableTarget(true, x, string(key[len(key)-1].Data)) + if err != nil { + return x, err + } -func scopeWithKey(x target, key []ast.Node) (target, error) { - var err error - for _, n := range key { - x, err = scopeTarget(x, string(n.Data)) + v := x.get() + + if v.Kind() == reflect.Interface { + x, err = initInterface(true, x) if err != nil { - return nil, err + return x, err } + v = x.get() } - return x, nil + + if v.Kind() == reflect.Slice { + return scopeSlice(x, true) + } + + return x, err } func unmarshalKeyValue(x target, node *ast.Node) error { diff --git a/unmarshaler_test.go b/unmarshaler_test.go index 8c682ed7..e748d0c8 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -420,7 +420,6 @@ B = "data"`, }, }, { - skip: true, // TODO desc: "one-level multi-element array table to map", input: `[[Products]] Name = "Hammer" From cb678e622190ad6cfb013aa501f892710a345e07 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Thu, 18 Mar 2021 08:47:50 -0400 Subject: [PATCH 084/228] Passing unmarshal of array table into interfaces --- targets.go | 97 ++++++++++++++++++++++++++++----------------- targets_test.go | 10 ++--- unmarshaler.go | 4 +- unmarshaler_test.go | 4 +- 4 files changed, 70 insertions(+), 45 deletions(-) diff --git a/targets.go b/targets.go index b0f9db25..17e45f20 100644 --- a/targets.go +++ b/targets.go @@ -58,6 +58,35 @@ func (t valueTarget) setFloat64(v float64) error { return nil } +// interfaceTarget wraps an other target to dereference on get. +type interfaceTarget struct { + x target +} + +func (t interfaceTarget) get() reflect.Value { + return t.x.get().Elem() +} + +func (t interfaceTarget) set(v reflect.Value) error { + return t.x.set(v) +} + +func (t interfaceTarget) setString(v string) error { + return t.x.setString(v) +} + +func (t interfaceTarget) setBool(v bool) error { + return t.x.setBool(v) +} + +func (t interfaceTarget) setInt64(v int64) error { + return t.x.setInt64(v) +} + +func (t interfaceTarget) setFloat64(v float64) error { + return t.x.setFloat64(v) +} + // mapTarget targets a specific key of a map. type mapTarget struct { v reflect.Value @@ -199,66 +228,62 @@ func pushNew(t target) (target, error) { func scopeTableTarget(append bool, t target, name string) (target, error) { x := t.get() - if x.Kind() == reflect.Interface { - t, err := initInterface(append, t) + switch x.Kind() { + case reflect.Interface: + t, err := scopeInterface(append, t) if err != nil { return t, err } - x = t.get() - } - - if x.Kind() == reflect.Slice { - return scopeSlice(t, append) + return scopeTableTarget(append, t, name) + case reflect.Struct: + return scopeStruct(x, name) + case reflect.Map: + return scopeMap(x, name) + case reflect.Slice: + return scopeSlice(append, t) + default: + panic(fmt.Errorf("can't scope on a %s", x.Kind())) } + return t, nil +} - t, err := scope(x, name) +func scopeInterface(append bool, t target) (target, error) { + err := initInterface(append, t) if err != nil { return t, err } - return t, nil + return interfaceTarget{t}, nil } // initInterface makes sure that the interface pointed at by the target is not // nil. // Returns the target to the initialized value of the target. -func initInterface(append bool, t target) (target, error) { +func initInterface(append bool, t target) error { x := t.get() if x.Kind() != reflect.Interface { panic("this should only be called on interfaces") } - if x.IsNil() { - var newElement reflect.Value - if append { - newElement = reflect.MakeSlice(reflect.TypeOf([]interface{}{}), 0, 0) - } else { - newElement = reflect.MakeMap(reflect.TypeOf(map[string]interface{}{})) - } - err := t.set(newElement) - if err != nil { - return t, err - } - x = t.get() + if !x.IsNil() { + return nil } - x = x.Elem() - t = valueTarget(x) - return t, nil -} - -func scope(v reflect.Value, name string) (target, error) { - switch v.Kind() { - case reflect.Struct: - return scopeStruct(v, name) - case reflect.Map: - return scopeMap(v, name) - default: - panic(fmt.Errorf("can't scope on a %s", v.Kind())) + var newElement reflect.Value + if append { + newElement = reflect.MakeSlice(reflect.TypeOf([]interface{}{}), 0, 0) + } else { + newElement = reflect.MakeMap(reflect.TypeOf(map[string]interface{}{})) + } + err := t.set(newElement) + if err != nil { + return err } + + return nil } -func scopeSlice(t target, append bool) (target, error) { +func scopeSlice(append bool, t target) (target, error) { v := t.get() if append { diff --git a/targets_test.go b/targets_test.go index 1522d22f..2fd57088 100644 --- a/targets_test.go +++ b/targets_test.go @@ -39,7 +39,7 @@ func TestStructTarget_Ensure(t *testing.T) { for _, e := range examples { t.Run(e.desc, func(t *testing.T) { - target, err := scope(e.input, e.name) + target, err := scopeTableTarget(false, valueTarget(e.input), e.name) require.NoError(t, err) err = ensureSlice(target) v := target.get() @@ -86,7 +86,7 @@ func TestStructTarget_SetString(t *testing.T) { for _, e := range examples { t.Run(e.desc, func(t *testing.T) { - target, err := scope(e.input, e.name) + target, err := scopeTableTarget(false, valueTarget(e.input), e.name) require.NoError(t, err) err = setString(target, str) v := target.get() @@ -102,7 +102,7 @@ func TestPushNew(t *testing.T) { } d := Doc{} - x, err := scope(reflect.ValueOf(&d).Elem(), "A") + x, err := scopeTableTarget(false, valueTarget(reflect.ValueOf(&d).Elem()), "A") require.NoError(t, err) n, err := pushNew(x) @@ -122,7 +122,7 @@ func TestPushNew(t *testing.T) { } d := Doc{} - x, err := scope(reflect.ValueOf(&d).Elem(), "A") + x, err := scopeTableTarget(false, valueTarget(reflect.ValueOf(&d).Elem()), "A") require.NoError(t, err) n, err := pushNew(x) @@ -161,7 +161,7 @@ func TestScope_Struct(t *testing.T) { for _, e := range examples { t.Run(e.desc, func(t *testing.T) { - x, err := scope(e.input, e.name) + x, err := scopeTableTarget(false, valueTarget(e.input), e.name) if e.err { require.Error(t, err) } else { diff --git a/unmarshaler.go b/unmarshaler.go index 478a127f..ba944725 100644 --- a/unmarshaler.go +++ b/unmarshaler.go @@ -95,7 +95,7 @@ func scopeWithArrayTable(x target, key []ast.Node) (target, error) { v := x.get() if v.Kind() == reflect.Interface { - x, err = initInterface(true, x) + x, err = scopeInterface(true, x) if err != nil { return x, err } @@ -103,7 +103,7 @@ func scopeWithArrayTable(x target, key []ast.Node) (target, error) { } if v.Kind() == reflect.Slice { - return scopeSlice(x, true) + return scopeSlice(true, x) } return x, err diff --git a/unmarshaler_test.go b/unmarshaler_test.go index e748d0c8..b0611479 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -439,12 +439,12 @@ B = "data"`, "Products": []interface{}{ map[string]interface{}{ "Name": "Hammer", - "Sku": 738594937, + "Sku": int64(738594937), }, nil, map[string]interface{}{ "Name": "Nail", - "Sku": 284758393, + "Sku": int64(284758393), "Color": "gray", }, }, From a577df2dbbb34a5049ecce78c67c2b385f215345 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Thu, 18 Mar 2021 17:19:50 -0400 Subject: [PATCH 085/228] wip --- targets.go | 7 ++++- unmarshaler_test.go | 70 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 1 deletion(-) diff --git a/targets.go b/targets.go index 17e45f20..0b0401d3 100644 --- a/targets.go +++ b/targets.go @@ -240,7 +240,12 @@ func scopeTableTarget(append bool, t target, name string) (target, error) { case reflect.Map: return scopeMap(x, name) case reflect.Slice: - return scopeSlice(append, t) + t, err := scopeSlice(append, t) + if err != nil { + return t, err + } + append = false + return scopeTableTarget(append, t, name) default: panic(fmt.Errorf("can't scope on a %s", x.Kind())) } diff --git a/unmarshaler_test.go b/unmarshaler_test.go index b0611479..0a21132d 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -452,6 +452,76 @@ B = "data"`, } }, }, + { + desc: "sub-table in array table", + input: `[[Fruits]] + Name = "apple" + + [Fruits.Physical] # subtable + Color = "red" + Shape = "round"`, + gen: func() test { + return test{ + target: &map[string]interface{}{}, + expected: &map[string]interface{}{ + "Fruits": []interface{}{ + map[string]interface{}{ + "Name": "apple", + "Physical": map[string]interface{}{ + "Color": "red", + "Shape": "round", + }, + }, + }, + }, + } + }, + }, + { + desc: "multiple sub-table in array tables", + input: `[[Fruits]] + Name = "apple" + + [[Fruits.Varieties]] # nested array of tables + Name = "red delicious" + + [[Fruits.Varieties]] + Name = "granny smith" + + [[Fruits]] + Name = "banana" + + [[Fruits.Varieties]] + Name = "plantain"`, + gen: func() test { + return test{ + target: &map[string]interface{}{}, + expected: &map[string]interface{}{ + "Fruits": []interface{}{ + map[string]interface{}{ + "Name": "apple", + "Varieties": []interface{}{ + map[string]interface{}{ + "Name": "red delicious", + }, + map[string]interface{}{ + "Name": "granny smith", + }, + }, + }, + map[string]interface{}{ + "Name": "banana", + "Varieties": []interface{}{ + map[string]interface{}{ + "Name": "plantain", + }, + }, + }, + }, + }, + } + }, + }, } for _, e := range examples { From 548b128e6706336d0bc4fdb11204478d60e349fd Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Thu, 18 Mar 2021 19:42:48 -0400 Subject: [PATCH 086/228] Fix multiple sub-table in array table --- unmarshaler.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unmarshaler.go b/unmarshaler.go index ba944725..7f1e45b5 100644 --- a/unmarshaler.go +++ b/unmarshaler.go @@ -87,7 +87,7 @@ func scopeWithArrayTable(x target, key []ast.Node) (target, error) { } } } - x, err = scopeTableTarget(true, x, string(key[len(key)-1].Data)) + x, err = scopeTableTarget(false, x, string(key[len(key)-1].Data)) if err != nil { return x, err } From fad86a5f2417aa873977b04c12e570692ecaa9bb Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Thu, 18 Mar 2021 19:48:09 -0400 Subject: [PATCH 087/228] Test for sub-table in array table into structs --- unmarshaler_test.go | 50 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/unmarshaler_test.go b/unmarshaler_test.go index 0a21132d..ad1730f1 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -522,6 +522,56 @@ B = "data"`, } }, }, + { + desc: "multiple sub-table in array tables into structs", + input: `[[Fruits]] + Name = "apple" + + [[Fruits.Varieties]] # nested array of tables + Name = "red delicious" + + [[Fruits.Varieties]] + Name = "granny smith" + + [[Fruits]] + Name = "banana" + + [[Fruits.Varieties]] + Name = "plantain"`, + gen: func() test { + type Variety struct { + Name string + } + type Fruit struct { + Name string + Varieties []Variety + } + type doc struct { + Fruits []Fruit + } + + return test{ + target: &doc{}, + expected: &doc{ + Fruits: []Fruit{ + { + Name: "apple", + Varieties: []Variety{ + {Name: "red delicious"}, + {Name: "granny smith"}, + }, + }, + { + Name: "banana", + Varieties: []Variety{ + {Name: "plantain"}, + }, + }, + }, + }, + } + }, + }, } for _, e := range examples { From 8957a768ef5185b0a0e9dd853bcbc2821e9d1aaf Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Thu, 18 Mar 2021 19:52:11 -0400 Subject: [PATCH 088/228] Next stop: pointers --- README.md | 9 ++++++--- internal/imported_tests/unmarshal_imported_test.go | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e1ebf599..5b8ed22c 100644 --- a/README.md +++ b/README.md @@ -7,14 +7,17 @@ Development branch. Probably does not work. ## Must do - [x] Unmarshal into maps. -- [ ] Attach comments to AST (gated by parser flag). -- [ ] Abstract AST. -- [ ] Support Array Tables +- [x] Support Array Tables. +- [ ] Unmarshal into pointers. - [ ] Support Date / times. - [ ] Support Unmarshaler interface. - [ ] Support struct tags annotations. - [ ] Original go-toml unmarshal tests pass. - [ ] Benchmark! +- [ ] Abstract AST. +- [ ] Attach comments to AST (gated by parser flag). +- [ ] Track file position (line, column) for errors. +- [ ] Benchmark again! ## Further work diff --git a/internal/imported_tests/unmarshal_imported_test.go b/internal/imported_tests/unmarshal_imported_test.go index ee817731..d604cfe6 100644 --- a/internal/imported_tests/unmarshal_imported_test.go +++ b/internal/imported_tests/unmarshal_imported_test.go @@ -14,7 +14,7 @@ import ( "testing" "time" - toml "github.com/pelletier/go-toml/v2/internal/unmarshaler" + "github.com/pelletier/go-toml/v2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) From 3e8b8db7862f85604d1cb2c723bc8484babc7f88 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Thu, 18 Mar 2021 20:02:32 -0400 Subject: [PATCH 089/228] Unmarshal into pointers --- README.md | 2 +- .../imported_tests/unmarshal_imported_test.go | 9 ++--- targets.go | 35 ++++++++++++++++--- 3 files changed, 34 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 5b8ed22c..2253d6e4 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Development branch. Probably does not work. - [x] Unmarshal into maps. - [x] Support Array Tables. -- [ ] Unmarshal into pointers. +- [x] Unmarshal into pointers. - [ ] Support Date / times. - [ ] Support Unmarshaler interface. - [ ] Support struct tags annotations. diff --git a/internal/imported_tests/unmarshal_imported_test.go b/internal/imported_tests/unmarshal_imported_test.go index d604cfe6..9c924918 100644 --- a/internal/imported_tests/unmarshal_imported_test.go +++ b/internal/imported_tests/unmarshal_imported_test.go @@ -138,13 +138,8 @@ func TestInterface(t *testing.T) { func TestBasicUnmarshal(t *testing.T) { result := basicMarshalTestStruct{} err := toml.Unmarshal(basicTestToml, &result) - expected := basicTestData - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(result, expected) { - t.Errorf("Bad unmarshal: expected %v, got %v", expected, result) - } + require.NoError(t, err) + require.Equal(t, basicTestData, result) } type quotedKeyMarshalTestStruct struct { diff --git a/targets.go b/targets.go index 0b0401d3..f7c0b025 100644 --- a/targets.go +++ b/targets.go @@ -229,16 +229,20 @@ func scopeTableTarget(append bool, t target, name string) (target, error) { x := t.get() switch x.Kind() { + // Kinds that need to recurse + case reflect.Interface: t, err := scopeInterface(append, t) if err != nil { return t, err } return scopeTableTarget(append, t, name) - case reflect.Struct: - return scopeStruct(x, name) - case reflect.Map: - return scopeMap(x, name) + case reflect.Ptr: + t, err := scopePtr(t) + if err != nil { + return t, err + } + return scopeTableTarget(append, t, name) case reflect.Slice: t, err := scopeSlice(append, t) if err != nil { @@ -246,6 +250,13 @@ func scopeTableTarget(append bool, t target, name string) (target, error) { } append = false return scopeTableTarget(append, t, name) + + // Terminal kinds + + case reflect.Struct: + return scopeStruct(x, name) + case reflect.Map: + return scopeMap(x, name) default: panic(fmt.Errorf("can't scope on a %s", x.Kind())) } @@ -260,6 +271,22 @@ func scopeInterface(append bool, t target) (target, error) { return interfaceTarget{t}, nil } +func scopePtr(t target) (target, error) { + err := initPtr(t) + if err != nil { + return t, err + } + return valueTarget(t.get().Elem()), nil +} + +func initPtr(t target) error { + x := t.get() + if !x.IsNil() { + return nil + } + return t.set(reflect.New(x.Type().Elem())) +} + // initInterface makes sure that the interface pointed at by the target is not // nil. // Returns the target to the initialized value of the target. From 93a7b0d77d636f1c249d1e195008e5bd2a915781 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Thu, 18 Mar 2021 20:30:51 -0400 Subject: [PATCH 090/228] Skip AST branches that don't exist in the target --- .../imported_tests/unmarshal_imported_test.go | 9 +-- targets.go | 19 +++-- targets_test.go | 21 +++-- unmarshaler.go | 76 +++++++++++-------- 4 files changed, 68 insertions(+), 57 deletions(-) diff --git a/internal/imported_tests/unmarshal_imported_test.go b/internal/imported_tests/unmarshal_imported_test.go index 9c924918..d970dbb2 100644 --- a/internal/imported_tests/unmarshal_imported_test.go +++ b/internal/imported_tests/unmarshal_imported_test.go @@ -328,13 +328,8 @@ shouldntBeHere = 2 func TestUnexportedUnmarshal(t *testing.T) { result := unexportedMarshalTestStruct{} err := toml.Unmarshal(unexportedTestToml, &result) - expected := unexportedTestData - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(result, expected) { - t.Errorf("Bad unexported unmarshal: expected %v, got %v", expected, result) - } + require.NoError(t, err) + assert.Equal(t, unexportedTestData, result) } type errStruct struct { diff --git a/targets.go b/targets.go index f7c0b025..216701de 100644 --- a/targets.go +++ b/targets.go @@ -225,7 +225,7 @@ func pushNew(t target) (target, error) { } } -func scopeTableTarget(append bool, t target, name string) (target, error) { +func scopeTableTarget(append bool, t target, name string) (target, bool, error) { x := t.get() switch x.Kind() { @@ -234,19 +234,19 @@ func scopeTableTarget(append bool, t target, name string) (target, error) { case reflect.Interface: t, err := scopeInterface(append, t) if err != nil { - return t, err + return t, false, err } return scopeTableTarget(append, t, name) case reflect.Ptr: t, err := scopePtr(t) if err != nil { - return t, err + return t, false, err } return scopeTableTarget(append, t, name) case reflect.Slice: t, err := scopeSlice(append, t) if err != nil { - return t, err + return t, false, err } append = false return scopeTableTarget(append, t, name) @@ -260,7 +260,6 @@ func scopeTableTarget(append bool, t target, name string) (target, error) { default: panic(fmt.Errorf("can't scope on a %s", x.Kind())) } - return t, nil } func scopeInterface(append bool, t target) (target, error) { @@ -330,7 +329,7 @@ func scopeSlice(append bool, t target) (target, error) { return valueTarget(v.Index(v.Len() - 1)), nil } -func scopeMap(v reflect.Value, name string) (target, error) { +func scopeMap(v reflect.Value, name string) (target, bool, error) { if v.IsNil() { v.Set(reflect.MakeMap(v.Type())) } @@ -344,10 +343,10 @@ func scopeMap(v reflect.Value, name string) (target, error) { return mapTarget{ v: v, k: k, - }, nil + }, true, nil } -func scopeStruct(v reflect.Value, name string) (target, error) { +func scopeStruct(v reflect.Value, name string) (target, bool, error) { // TODO: cache this t := v.Type() for i := 0; i < t.NumField(); i++ { @@ -361,9 +360,9 @@ func scopeStruct(v reflect.Value, name string) (target, error) { } else { // TODO: handle names variations if f.Name == name { - return valueTarget(v.Field(i)), nil + return valueTarget(v.Field(i)), true, nil } } } - return nil, fmt.Errorf("field '%s' not found on %s", name, v.Type()) + return nil, false, nil } diff --git a/targets_test.go b/targets_test.go index 2fd57088..7316994e 100644 --- a/targets_test.go +++ b/targets_test.go @@ -39,7 +39,7 @@ func TestStructTarget_Ensure(t *testing.T) { for _, e := range examples { t.Run(e.desc, func(t *testing.T) { - target, err := scopeTableTarget(false, valueTarget(e.input), e.name) + target, _, err := scopeTableTarget(false, valueTarget(e.input), e.name) require.NoError(t, err) err = ensureSlice(target) v := target.get() @@ -86,7 +86,7 @@ func TestStructTarget_SetString(t *testing.T) { for _, e := range examples { t.Run(e.desc, func(t *testing.T) { - target, err := scopeTableTarget(false, valueTarget(e.input), e.name) + target, _, err := scopeTableTarget(false, valueTarget(e.input), e.name) require.NoError(t, err) err = setString(target, str) v := target.get() @@ -102,7 +102,7 @@ func TestPushNew(t *testing.T) { } d := Doc{} - x, err := scopeTableTarget(false, valueTarget(reflect.ValueOf(&d).Elem()), "A") + x, _, err := scopeTableTarget(false, valueTarget(reflect.ValueOf(&d).Elem()), "A") require.NoError(t, err) n, err := pushNew(x) @@ -122,7 +122,7 @@ func TestPushNew(t *testing.T) { } d := Doc{} - x, err := scopeTableTarget(false, valueTarget(reflect.ValueOf(&d).Elem()), "A") + x, _, err := scopeTableTarget(false, valueTarget(reflect.ValueOf(&d).Elem()), "A") require.NoError(t, err) n, err := pushNew(x) @@ -143,6 +143,7 @@ func TestScope_Struct(t *testing.T) { input reflect.Value name string err bool + found bool idx []int }{ { @@ -150,21 +151,25 @@ func TestScope_Struct(t *testing.T) { input: reflect.ValueOf(&struct{ A string }{}).Elem(), name: "A", idx: []int{0}, + found: true, }, { desc: "fails not-exported field", input: reflect.ValueOf(&struct{ a string }{}).Elem(), name: "a", - err: true, + err: false, + found: false, }, } for _, e := range examples { t.Run(e.desc, func(t *testing.T) { - x, err := scopeTableTarget(false, valueTarget(e.input), e.name) + x, found, err := scopeTableTarget(false, valueTarget(e.input), e.name) + assert.Equal(t, e.found, found) if e.err { - require.Error(t, err) - } else { + assert.Error(t, err) + } + if found { x2, ok := x.(valueTarget) require.True(t, ok) x2.get() diff --git a/unmarshaler.go b/unmarshaler.go index 7f1e45b5..1fee3849 100644 --- a/unmarshaler.go +++ b/unmarshaler.go @@ -26,33 +26,38 @@ func fromAst(tree ast.Root, v interface{}) error { } var err error + var skipUntilTable bool var root target = valueTarget(r.Elem()) current := root for _, node := range tree { - current, err = unmarshalTopLevelNode(root, current, &node) + var found bool + switch node.Kind { + case ast.KeyValue: + if skipUntilTable { + continue + } + err = unmarshalKeyValue(current, &node) + found = true + case ast.Table: + current, found, err = scopeWithKey(root, node.Key()) + case ast.ArrayTable: + current, found, err = scopeWithArrayTable(root, node.Key()) + default: + panic(fmt.Errorf("this should not be a top level node type: %s", node.Kind)) + } + if err != nil { return err } + + if !found { + skipUntilTable = true + } } return nil } -// The target return value is the target for the next top-level node. Mostly -// unchanged, except by table and array table. -func unmarshalTopLevelNode(root target, x target, node *ast.Node) (target, error) { - switch node.Kind { - case ast.KeyValue: - return x, unmarshalKeyValue(x, node) - case ast.Table: - return scopeWithKey(root, node.Key()) - case ast.ArrayTable: - return scopeWithArrayTable(root, node.Key()) - default: - panic(fmt.Errorf("this should not be a top level node type: %s", node.Kind)) - } -} - // scopeWithKey performs target scoping when unmarshaling an ast.KeyValue node. // // The goal is to hop from target to target recursively using the names in key. @@ -61,15 +66,16 @@ func unmarshalTopLevelNode(root target, x target, node *ast.Node) (target, error // // When encountering slices, it should always use its last element, and error // if the slice does not have any. -func scopeWithKey(x target, key []ast.Node) (target, error) { +func scopeWithKey(x target, key []ast.Node) (target, bool, error) { var err error + found := true for _, n := range key { - x, err = scopeTableTarget(false, x, string(n.Data)) - if err != nil { - return nil, err + x, found, err = scopeTableTarget(false, x, string(n.Data)) + if err != nil || !found { + return nil, found, err } } - return x, nil + return x, true, nil } // scopeWithArrayTable performs target scoping when unmarshaling an @@ -77,19 +83,20 @@ func scopeWithKey(x target, key []ast.Node) (target, error) { // // It is the same as scopeWithKey, but when scoping the last part of the key // it creates a new element in the array instead of using the last one. -func scopeWithArrayTable(x target, key []ast.Node) (target, error) { +func scopeWithArrayTable(x target, key []ast.Node) (target, bool, error) { var err error + found := true if len(key) > 1 { for _, n := range key[:len(key)-1] { - x, err = scopeTableTarget(false, x, string(n.Data)) - if err != nil { - return nil, err + x, found, err = scopeTableTarget(false, x, string(n.Data)) + if err != nil || !found { + return nil, found, err } } } - x, err = scopeTableTarget(false, x, string(key[len(key)-1].Data)) - if err != nil { - return x, err + x, found, err = scopeTableTarget(false, x, string(key[len(key)-1].Data)) + if err != nil || !found { + return x, found, err } v := x.get() @@ -97,26 +104,31 @@ func scopeWithArrayTable(x target, key []ast.Node) (target, error) { if v.Kind() == reflect.Interface { x, err = scopeInterface(true, x) if err != nil { - return x, err + return x, found, err } v = x.get() } if v.Kind() == reflect.Slice { - return scopeSlice(true, x) + x, err = scopeSlice(true, x) } - return x, err + return x, found, err } func unmarshalKeyValue(x target, node *ast.Node) error { assertNode(ast.KeyValue, node) - x, err := scopeWithKey(x, node.Key()) + x, found, err := scopeWithKey(x, node.Key()) if err != nil { return err } + // A struct in the path was not found. Skip this value. + if !found { + return nil + } + return unmarshalValue(x, node.Value()) } From 9ec4e86883aedeba40107462b7009915bd75512d Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Thu, 18 Mar 2021 20:42:41 -0400 Subject: [PATCH 091/228] Handle struct field name variations --- internal/ast/ast.go | 5 ++++- targets.go | 8 ++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/internal/ast/ast.go b/internal/ast/ast.go index 0ec989ef..0cd9f931 100644 --- a/internal/ast/ast.go +++ b/internal/ast/ast.go @@ -9,7 +9,8 @@ type Kind int const ( // meta - Comment Kind = iota + Invalid Kind = iota + Comment Key // top level structures @@ -34,6 +35,8 @@ const ( func (k Kind) String() string { switch k { + case Invalid: + return "Invalid" case Comment: return "Comment" case Key: diff --git a/targets.go b/targets.go index 216701de..333b7567 100644 --- a/targets.go +++ b/targets.go @@ -3,6 +3,7 @@ package toml import ( "fmt" "reflect" + "strings" ) type target interface { @@ -358,8 +359,11 @@ func scopeStruct(v reflect.Value, name string) (target, bool, error) { if f.Anonymous { // TODO: handle embedded structs } else { - // TODO: handle names variations - if f.Name == name { + fieldName, ok := f.Tag.Lookup("toml") + if !ok { + fieldName = f.Name + } + if strings.EqualFold(fieldName, name) { return valueTarget(v.Field(i)), true, nil } } From ebffe6db83663947ca38aa43b242f20523aa35ec Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Thu, 18 Mar 2021 21:11:51 -0400 Subject: [PATCH 092/228] Naive implementation of anonymous structs --- README.md | 2 +- targets.go | 59 ++++++++++++++++++++++++++++++++++++++---------------- 2 files changed, 43 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 2253d6e4..8f760f56 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Development branch. Probably does not work. - [x] Unmarshal into pointers. - [ ] Support Date / times. - [ ] Support Unmarshaler interface. -- [ ] Support struct tags annotations. +- [x] Support struct tags annotations. - [ ] Original go-toml unmarshal tests pass. - [ ] Benchmark! - [ ] Abstract AST. diff --git a/targets.go b/targets.go index 333b7567..79ef60c7 100644 --- a/targets.go +++ b/targets.go @@ -348,25 +348,50 @@ func scopeMap(v reflect.Value, name string) (target, bool, error) { } func scopeStruct(v reflect.Value, name string) (target, bool, error) { - // TODO: cache this - t := v.Type() - for i := 0; i < t.NumField(); i++ { - f := t.Field(i) - if f.PkgPath != "" { - // only consider exported fields - continue - } - if f.Anonymous { - // TODO: handle embedded structs - } else { - fieldName, ok := f.Tag.Lookup("toml") - if !ok { - fieldName = f.Name + // TODO: cache this, and reduce allocations + + fieldPaths := map[string][]int{} + + path := make([]int, 0, 16) + var walk func(reflect.Value) + walk = func(v reflect.Value) { + t := v.Type() + for i := 0; i < t.NumField(); i++ { + l := len(path) + path = append(path, i) + f := t.Field(i) + if f.PkgPath != "" { + // only consider exported fields + continue } - if strings.EqualFold(fieldName, name) { - return valueTarget(v.Field(i)), true, nil + if f.Anonymous { + walk(v.Field(i)) + } else { + fieldName, ok := f.Tag.Lookup("toml") + if !ok { + fieldName = f.Name + } + + pathCopy := make([]int, len(path)) + copy(pathCopy, path) + + fieldPaths[fieldName] = pathCopy + // extra copy for the case-insensitive match + fieldPaths[strings.ToLower(fieldName)] = pathCopy } + path = path[:l] } } - return nil, false, nil + + walk(v) + + path, ok := fieldPaths[name] + if !ok { + path, ok = fieldPaths[strings.ToLower(name)] + } + if !ok { + return nil, false, nil + } + + return valueTarget(v.FieldByIndex(path)), true, nil } From 8b34e547649e51474160099acdb271b92363b585 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Thu, 18 Mar 2021 22:10:31 -0400 Subject: [PATCH 093/228] Improve DOT representation for AST --- internal/ast/ast.go | 85 ++++++++++++++++++++++++++++++++++----------- unmarshaler.go | 20 +++++++++++ 2 files changed, 84 insertions(+), 21 deletions(-) diff --git a/internal/ast/ast.go b/internal/ast/ast.go index 0cd9f931..a8727e3b 100644 --- a/internal/ast/ast.go +++ b/internal/ast/ast.go @@ -76,47 +76,90 @@ type Root []Node // Dot returns a dot representation of the AST for debugging. func (r Root) Sdot() string { type edge struct { - from int - to int + from int + childIdx int + to int } - var nodes []string + var nodes []Node var edges []edge // indexes into nodes - nodes = append(nodes, "root") + nodes = append(nodes, Node{ + Kind: Invalid, + Data: []byte(`ROOT`), + Children: r, + }) - labelForNode := func(node *Node) string { - return fmt.Sprintf("{%s}", node.Kind) - } - - var processNode func(int, *Node) - processNode = func(parentIdx int, node *Node) { + var processNode func(int, int, *Node) + processNode = func(parentIdx int, childIdx int, node *Node) { idx := len(nodes) - label := labelForNode(node) - nodes = append(nodes, label) - edges = append(edges, edge{from: parentIdx, to: idx}) - - for _, c := range node.Children { - processNode(idx, &c) + nodes = append(nodes, *node) + edges = append(edges, edge{ + from: parentIdx, + childIdx: childIdx, + to: idx, + }) + + for i, c := range node.Children { + processNode(idx, i, &c) } } - for _, n := range r { - processNode(0, &n) + for i, n := range r { + processNode(0, i, &n) } var b strings.Builder b.WriteString("digraph tree {\n") + b.WriteString("\tnode [shape=record];\n") + + for i, node := range nodes { + label := "" + attrs := map[string]string{} + + if i == 0 { + var ports []string + for i := 0; i < len(node.Children); i++ { + ports = append(ports, fmt.Sprintf(" %d", i, i)) + } + joinedPorts := strings.Join(ports, "|") + label = fmt.Sprintf("{ROOT|{%s}}", joinedPorts) + } else { + fields := []string{node.Kind.String()} + if len(node.Data) > 0 { + fields = append(fields, string(node.Data)) + } + + var ports []string + for i := 0; i < len(node.Children); i++ { + ports = append(ports, fmt.Sprintf(" %d", i, i)) + } + joinedPorts := strings.Join(ports, "|") + + joinedFields := strings.Join(fields, "|") + label = fmt.Sprintf("{{%s}", joinedFields) + if len(ports) > 0 { + label += fmt.Sprintf("|{%s}", joinedPorts) + } + label += "}" + if node.Kind == Invalid { + attrs["style"] = "filled" + attrs["fillcolor"] = "red" + } + } - for i, label := range nodes { - _, _ = fmt.Fprintf(&b, "\tnode%d [label=\"%s\"];\n", i, label) + _, _ = fmt.Fprintf(&b, "\tnode%d [label=\"%s\"", i, label) + for k, v := range attrs { + _, _ = fmt.Fprintf(&b, ", %s=\"%s\"", k, v) + } + _, _ = fmt.Fprintf(&b, "];\n") } b.WriteString("\n") for _, e := range edges { - _, _ = fmt.Fprintf(&b, "\tnode%d -> node%d;\n", e.from, e.to) + _, _ = fmt.Fprintf(&b, "\tnode%d:f%d -> node%d;\n", e.from, e.childIdx, e.to) } b.WriteString("}") diff --git a/unmarshaler.go b/unmarshaler.go index 1fee3849..45f8fbcd 100644 --- a/unmarshaler.go +++ b/unmarshaler.go @@ -2,6 +2,7 @@ package toml import ( "fmt" + "os" "reflect" "github.com/pelletier/go-toml/v2/internal/ast" @@ -13,9 +14,28 @@ func Unmarshal(data []byte, v interface{}) error { if err != nil { return err } + + // TODO: remove me; sanity check + allValidOrDump(p.tree, p.tree) + return fromAst(p.tree, v) } +func allValidOrDump(tree ast.Root, nodes []ast.Node) bool { + for i, n := range nodes { + if n.Kind == ast.Invalid { + fmt.Printf("AST contains invalid node! idx=%d\n", i) + fmt.Fprintf(os.Stderr, "%s\n", tree.Sdot()) + return false + } + ok := allValidOrDump(tree, n.Children) + if !ok { + return ok + } + } + return true +} + func fromAst(tree ast.Root, v interface{}) error { r := reflect.ValueOf(v) if r.Kind() != reflect.Ptr { From fcc91f261844d652243b8a5cf30524ed9542ebbf Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Mon, 22 Mar 2021 09:59:15 -0400 Subject: [PATCH 094/228] Progress on date/times --- README.md | 6 +- decode.go | 288 +++++++++++++++++++++++++++++++++++++++++ internal/ast/ast.go | 28 ---- internal/ast/decode.go | 113 ---------------- parser.go | 60 +++++++-- unmarshaler.go | 38 +++++- 6 files changed, 380 insertions(+), 153 deletions(-) create mode 100644 decode.go delete mode 100644 internal/ast/decode.go diff --git a/README.md b/README.md index 8f760f56..298ef054 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,10 @@ Development branch. Probably does not work. - [x] Unmarshal into maps. - [x] Support Array Tables. -- [x] Unmarshal into pointers. -- [ ] Support Date / times. +- [ ] Unmarshal into pointers. + > Was supposed to be done, but seems like there are still some assignation + > issues. +- [x] Support Date / times. - [ ] Support Unmarshaler interface. - [x] Support struct tags annotations. - [ ] Original go-toml unmarshal tests pass. diff --git a/decode.go b/decode.go new file mode 100644 index 00000000..5bd321e7 --- /dev/null +++ b/decode.go @@ -0,0 +1,288 @@ +package toml + +import ( + "errors" + "fmt" + "math" + "strconv" + "strings" + "time" +) + +func parseInteger(b []byte) (int64, error) { + if len(b) > 2 && b[0] == '0' { + switch b[1] { + case 'x': + return parseIntHex(b) + case 'b': + return parseIntBin(b) + case 'o': + return parseIntOct(b) + default: + return 0, fmt.Errorf("invalid base: '%c'", b[1]) + } + } + return parseIntDec(b) +} + +func parseLocalDate(b []byte) (LocalDate, error) { + // full-date = date-fullyear "-" date-month "-" date-mday + // date-fullyear = 4DIGIT + // date-month = 2DIGIT ; 01-12 + // date-mday = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on month/year + + date := LocalDate{} + + if len(b) != 10 || b[4] != '-' || b[7] != '-' { + return date, fmt.Errorf("dates are expected to have the format YYYY-MM-DD") + } + + var err error + + date.Year, err = parseDecimalDigits(b[0:4]) + if err != nil { + return date, err + } + + v, err := parseDecimalDigits(b[5:7]) + if err != nil { + return date, err + } + date.Month = time.Month(v) + + date.Day, err = parseDecimalDigits(b[8:10]) + + return date, nil +} + +func parseDecimalDigits(b []byte) (int, error) { + v := 0 + for _, c := range b { + if !isDigit(c) { + return 0, fmt.Errorf("expected digit") + } + v *= 10 + v += int(c - '0') + } + return v, nil +} + +func parseDateTime(b []byte) (time.Time, error) { + // offset-date-time = full-date time-delim full-time + // full-time = partial-time time-offset + // time-offset = "Z" / time-numoffset + // time-numoffset = ( "+" / "-" ) time-hour ":" time-minute + + dt, b, err := parseLocalDateTime(b) + if err != nil { + return time.Time{}, nil + } + + var zone *time.Location + + if len(b) == 0 { + return time.Time{}, fmt.Errorf("date-time missing timezone information") + } + + if b[0] == 'Z' { + b = b[1:] + zone = time.UTC + } else { + if len(b) != 6 { + return time.Time{}, fmt.Errorf("invalid date-time timezone") + } + direction := 1 + switch b[0] { + case '+': + case '-': + direction = -1 + default: + return time.Time{}, fmt.Errorf("invalid timezone offset character") + } + + hours := digitsToInt(b[1:3]) + minutes := digitsToInt(b[4:6]) + seconds := direction * (hours*3600 + minutes*60) + zone = time.FixedZone("", seconds) + } + + if len(b) > 0 { + return time.Time{}, fmt.Errorf("extra bytes at the end of the timezone") + } + + t := time.Date( + dt.Date.Year, + dt.Date.Month, + dt.Date.Day, + dt.Time.Hour, + dt.Time.Minute, + dt.Time.Second, + dt.Time.Nanosecond, + zone) + + return t, nil +} + +func parseLocalDateTime(b []byte) (LocalDateTime, []byte, error) { + dt := LocalDateTime{} + + if len(b) < 11 { + return dt, nil, fmt.Errorf("local datetimes are expected to have the format YYYY-MM-DDTHH:MM:SS[.NNNNNN]") + } + + date, err := parseLocalDate(b[:10]) + if err != nil { + return dt, nil, err + } + dt.Date = date + + sep := b[10] + if sep != 'T' && sep != ' ' { + return dt, nil, fmt.Errorf("datetime separator is expected to be T or a space") + } + + t, rest, err := parseLocalTime(b[11:]) + if err != nil { + return dt, nil, err + } + dt.Time = t + + return dt, rest, nil +} + +// parseLocalTime is a bit different because it also returns the remaining +// []byte that is didn't need. This is to allow parseDateTime to parse those +// remaining bytes as a timezone. +func parseLocalTime(b []byte) (LocalTime, []byte, error) { + t := LocalTime{} + + if len(b) < 8 { + return t, nil, fmt.Errorf("times are expected to have the format HH:MM:SS[.NNNNNN]") + } + + var err error + t.Hour, err = parseDecimalDigits(b[0:2]) + if err != nil { + return t, nil, err + } + t.Minute, err = parseDecimalDigits(b[3:5]) + if err != nil { + return t, nil, err + } + t.Second, err = parseDecimalDigits(b[6:8]) + if err != nil { + return t, nil, err + } + + if len(b) >= 15 && b[8] == '.' { + t.Nanosecond, err = parseDecimalDigits(b[9:15]) + return t, b[15:], nil + } + + return t, b[8:], nil +} + +func parseFloat(b []byte) (float64, error) { + // TODO: inefficient + if len(b) == 4 && (b[0] == '+' || b[0] == '-') && b[1] == 'n' && b[2] == 'a' && b[3] == 'n' { + return math.NaN(), nil + } + + tok := string(b) + err := numberContainsInvalidUnderscore(tok) + if err != nil { + return 0, err + } + cleanedVal := cleanupNumberToken(tok) + return strconv.ParseFloat(cleanedVal, 64) +} + +func parseIntHex(b []byte) (int64, error) { + tok := string(b) + cleanedVal := cleanupNumberToken(tok) + err := hexNumberContainsInvalidUnderscore(cleanedVal) + if err != nil { + return 0, nil + } + return strconv.ParseInt(cleanedVal[2:], 16, 64) +} + +func parseIntOct(b []byte) (int64, error) { + tok := string(b) + cleanedVal := cleanupNumberToken(tok) + err := numberContainsInvalidUnderscore(cleanedVal) + if err != nil { + return 0, err + } + return strconv.ParseInt(cleanedVal[2:], 8, 64) +} + +func parseIntBin(b []byte) (int64, error) { + tok := string(b) + cleanedVal := cleanupNumberToken(tok) + err := numberContainsInvalidUnderscore(cleanedVal) + if err != nil { + return 0, err + } + return strconv.ParseInt(cleanedVal[2:], 2, 64) +} + +func parseIntDec(b []byte) (int64, error) { + tok := string(b) + cleanedVal := cleanupNumberToken(tok) + err := numberContainsInvalidUnderscore(cleanedVal) + if err != nil { + return 0, err + } + return strconv.ParseInt(cleanedVal, 10, 64) +} + +func numberContainsInvalidUnderscore(value string) error { + // For large numbers, you may use underscores between digits to enhance + // readability. Each underscore must be surrounded by at least one digit on + // each side. + + hasBefore := false + for idx, r := range value { + if r == '_' { + if !hasBefore || idx+1 >= len(value) { + // can't end with an underscore + return errInvalidUnderscore + } + } + hasBefore = isDigitRune(r) + } + return nil +} + +func hexNumberContainsInvalidUnderscore(value string) error { + hasBefore := false + for idx, r := range value { + if r == '_' { + if !hasBefore || idx+1 >= len(value) { + // can't end with an underscore + return errInvalidUnderscoreHex + } + } + hasBefore = isHexDigit(r) + } + return nil +} + +func cleanupNumberToken(value string) string { + cleanedVal := strings.Replace(value, "_", "", -1) + return cleanedVal +} + +func isHexDigit(r rune) bool { + return isDigitRune(r) || + (r >= 'a' && r <= 'f') || + (r >= 'A' && r <= 'F') +} + +func isDigitRune(r rune) bool { + return r >= '0' && r <= '9' +} + +var errInvalidUnderscore = errors.New("invalid use of _ in number") +var errInvalidUnderscoreHex = errors.New("invalid use of _ in hex number") diff --git a/internal/ast/ast.go b/internal/ast/ast.go index a8727e3b..e927dd08 100644 --- a/internal/ast/ast.go +++ b/internal/ast/ast.go @@ -211,34 +211,6 @@ func (n *Node) Value() *Node { return &n.Children[len(n.Children)-1] } -// DecodeInteger parse the data of an Integer node and returns the represented -// int64, or an error. -// Panics if not called on an Integer node. -func (n *Node) DecodeInteger() (int64, error) { - assertKind(Integer, n) - if len(n.Data) > 2 && n.Data[0] == '0' { - switch n.Data[1] { - case 'x': - return parseIntHex(n.Data) - case 'b': - return parseIntBin(n.Data) - case 'o': - return parseIntOct(n.Data) - default: - return 0, fmt.Errorf("invalid base: '%c'", n.Data[1]) - } - } - return parseIntDec(n.Data) -} - -// DecodeFloat parse the data of a Float node and returns the represented -// float64, or an error. -// Panics if not called on an Float node. -func (n *Node) DecodeFloat() (float64, error) { - assertKind(Float, n) - return parseFloat(n.Data) -} - func assertKind(k Kind, n *Node) { if n.Kind != k { panic(fmt.Errorf("method was expecting a %s, not a %s", k, n.Kind)) diff --git a/internal/ast/decode.go b/internal/ast/decode.go deleted file mode 100644 index a27f04ce..00000000 --- a/internal/ast/decode.go +++ /dev/null @@ -1,113 +0,0 @@ -package ast - -import ( - "errors" - "math" - "strconv" - "strings" -) - -func parseFloat(b []byte) (float64, error) { - // TODO: inefficient - if len(b) == 4 && (b[0] == '+' || b[0] == '-') && b[1] == 'n' && b[2] == 'a' && b[3] == 'n' { - return math.NaN(), nil - } - - tok := string(b) - err := numberContainsInvalidUnderscore(tok) - if err != nil { - return 0, err - } - cleanedVal := cleanupNumberToken(tok) - return strconv.ParseFloat(cleanedVal, 64) -} - -func parseIntHex(b []byte) (int64, error) { - tok := string(b) - cleanedVal := cleanupNumberToken(tok) - err := hexNumberContainsInvalidUnderscore(cleanedVal) - if err != nil { - return 0, nil - } - return strconv.ParseInt(cleanedVal[2:], 16, 64) -} - -func parseIntOct(b []byte) (int64, error) { - tok := string(b) - cleanedVal := cleanupNumberToken(tok) - err := numberContainsInvalidUnderscore(cleanedVal) - if err != nil { - return 0, err - } - return strconv.ParseInt(cleanedVal[2:], 8, 64) -} - -func parseIntBin(b []byte) (int64, error) { - tok := string(b) - cleanedVal := cleanupNumberToken(tok) - err := numberContainsInvalidUnderscore(cleanedVal) - if err != nil { - return 0, err - } - return strconv.ParseInt(cleanedVal[2:], 2, 64) -} - -func parseIntDec(b []byte) (int64, error) { - tok := string(b) - cleanedVal := cleanupNumberToken(tok) - err := numberContainsInvalidUnderscore(cleanedVal) - if err != nil { - return 0, err - } - return strconv.ParseInt(cleanedVal, 10, 64) -} - -func numberContainsInvalidUnderscore(value string) error { - // For large numbers, you may use underscores between digits to enhance - // readability. Each underscore must be surrounded by at least one digit on - // each side. - - hasBefore := false - for idx, r := range value { - if r == '_' { - if !hasBefore || idx+1 >= len(value) { - // can't end with an underscore - return errInvalidUnderscore - } - } - hasBefore = isDigitRune(r) - } - return nil -} - -func hexNumberContainsInvalidUnderscore(value string) error { - hasBefore := false - for idx, r := range value { - if r == '_' { - if !hasBefore || idx+1 >= len(value) { - // can't end with an underscore - return errInvalidUnderscoreHex - } - } - hasBefore = isHexDigit(r) - } - return nil -} - -func cleanupNumberToken(value string) string { - cleanedVal := strings.Replace(value, "_", "", -1) - return cleanedVal -} - -func isHexDigit(r rune) bool { - return isDigitRune(r) || - (r >= 'a' && r <= 'f') || - (r >= 'A' && r <= 'F') -} - -func isDigitRune(r rune) bool { - return r >= '0' && r <= '9' -} - -var errInvalidUnderscore = errors.New("invalid use of _ in number") -var errInvalidUnderscoreHex = errors.New("invalid use of _ in hex number") diff --git a/parser.go b/parser.go index 803e48a6..289bb156 100644 --- a/parser.go +++ b/parser.go @@ -637,14 +637,11 @@ func (p *parser) parseIntOrFloatOrDateTime(node *ast.Node, b []byte) ([]byte, er s = len(b) } for idx, c := range b[:s] { - if c >= '0' && c <= '9' { + if isDigit(c) { continue } - if idx == 2 && c == ':' { - return p.parseDateTime(b) - } - if idx == 4 && c == '-' { - return p.parseDateTime(b) + if idx == 2 && c == ':' || (idx == 4 && c == '-') { + return p.scanDateTime(node, b) } } return p.scanIntOrFloat(node, b) @@ -659,14 +656,61 @@ func digitsToInt(b []byte) int { return x } +func (p *parser) scanDateTime(node *ast.Node, b []byte) ([]byte, error) { + // scans for contiguous characters in [0-9T:Z.+-], and up to one space if + // followed by a digit. + + hasTime := false + hasTz := false + seenSpace := false + + i := 0 + for ; i < len(b); i++ { + c := b[i] + if isDigit(c) || c == '-' { + } else if c == 'T' || c == ':' || c == '.' { + hasTime = true + continue + } else if c == '+' || c == '-' || c == 'Z' { + hasTz = true + } else if c == ' ' { + if !seenSpace && i+1 < len(b) && isDigit(b[i+1]) { + i += 2 + seenSpace = true + hasTime = true + } else { + break + } + } else { + break + } + } + + if hasTime { + if hasTz { + node.Kind = ast.DateTime + } else { + node.Kind = ast.LocalDateTime + } + } else { + if hasTz { + return nil, fmt.Errorf("possible DateTime cannot have a timezone but no time component") + } + node.Kind = ast.LocalDate + } + + node.Data = b[:i] + + return b[i:], nil +} + func (p *parser) parseDateTime(b []byte) ([]byte, error) { - // we know the first 2 ar digits. + // we know the first 2 are digits. if b[2] == ':' { return p.parseTime(b) } // This state accepts an offset date-time, a local date-time, or a local date. // - // v--- cursor // 1979-05-27T07:32:00Z // 1979-05-27T00:32:00-07:00 // 1979-05-27T00:32:00.999999-07:00 diff --git a/unmarshaler.go b/unmarshaler.go index 45f8fbcd..536b103d 100644 --- a/unmarshaler.go +++ b/unmarshaler.go @@ -4,6 +4,7 @@ import ( "fmt" "os" "reflect" + "time" "github.com/pelletier/go-toml/v2/internal/ast" ) @@ -166,11 +167,44 @@ func unmarshalValue(x target, node *ast.Node) error { return unmarshalArray(x, node) case ast.InlineTable: return unmarshalInlineTable(x, node) + case ast.LocalDateTime: + return unmarshalLocalDateTime(x, node) + case ast.DateTime: + return unmarshalDateTime(x, node) default: panic(fmt.Errorf("unhandled unmarshalValue kind %s", node.Kind)) } } +func unmarshalLocalDateTime(x target, node *ast.Node) error { + assertNode(ast.LocalDateTime, node) + v, rest, err := parseLocalDateTime(node.Data) + if err != nil { + return err + } + if len(rest) > 0 { + return fmt.Errorf("extra characters at the end of a local date time") + } + return setLocalDateTime(x, v) +} + +func unmarshalDateTime(x target, node *ast.Node) error { + assertNode(ast.DateTime, node) + v, err := parseDateTime(node.Data) + if err != nil { + return err + } + return setDateTime(x, v) +} + +func setLocalDateTime(x target, v LocalDateTime) error { + return x.set(reflect.ValueOf(v)) +} + +func setDateTime(x target, v time.Time) error { + return x.set(reflect.ValueOf(v)) +} + func unmarshalString(x target, node *ast.Node) error { assertNode(ast.String, node) return setString(x, string(node.Data)) @@ -184,7 +218,7 @@ func unmarshalBool(x target, node *ast.Node) error { func unmarshalInteger(x target, node *ast.Node) error { assertNode(ast.Integer, node) - v, err := node.DecodeInteger() + v, err := parseInteger(node.Data) if err != nil { return err } @@ -193,7 +227,7 @@ func unmarshalInteger(x target, node *ast.Node) error { func unmarshalFloat(x target, node *ast.Node) error { assertNode(ast.Float, node) - v, err := node.DecodeFloat() + v, err := parseFloat(node.Data) if err != nil { return err } From ac2d6e203096cfb96048e465c7e5960326716ff5 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Mon, 22 Mar 2021 20:03:35 -0400 Subject: [PATCH 095/228] Handle unmarshalling value to nil ptr. --- README.md | 4 +--- targets.go | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 298ef054..158313cb 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,7 @@ Development branch. Probably does not work. - [x] Unmarshal into maps. - [x] Support Array Tables. -- [ ] Unmarshal into pointers. - > Was supposed to be done, but seems like there are still some assignation - > issues. +- [x] Unmarshal into pointers. - [x] Support Date / times. - [ ] Support Unmarshaler interface. - [x] Support struct tags annotations. diff --git a/targets.go b/targets.go index 79ef60c7..52c4cfe1 100644 --- a/targets.go +++ b/targets.go @@ -148,6 +148,16 @@ func setString(t target, v string) error { return t.setString(v) case reflect.Interface: return t.set(reflect.ValueOf(v)) + case reflect.Ptr: + if !f.Elem().IsValid() { + err := t.set(reflect.New(f.Type().Elem())) + if err != nil { + return err + } + f = t.get() + } + f.Elem().Set(reflect.ValueOf(v)) + return nil default: return fmt.Errorf("cannot assign string to a %s", f.Kind()) } @@ -161,6 +171,16 @@ func setBool(t target, v bool) error { return t.setBool(v) case reflect.Interface: return t.set(reflect.ValueOf(v)) + case reflect.Ptr: + if !f.Elem().IsValid() { + err := t.set(reflect.New(f.Type().Elem())) + if err != nil { + return err + } + f = t.get() + } + f.Elem().Set(reflect.ValueOf(v)) + return nil default: return fmt.Errorf("cannot assign bool to a %s", f.String()) } @@ -175,6 +195,16 @@ func setInt64(t target, v int64) error { return t.setInt64(v) case reflect.Interface: return t.set(reflect.ValueOf(v)) + case reflect.Ptr: + if !f.Elem().IsValid() { + err := t.set(reflect.New(f.Type().Elem())) + if err != nil { + return err + } + f = t.get() + } + f.Elem().Set(reflect.ValueOf(v)) + return nil default: return fmt.Errorf("cannot assign int64 to a %s", f.String()) } @@ -189,6 +219,16 @@ func setFloat64(t target, v float64) error { return t.setFloat64(v) case reflect.Interface: return t.set(reflect.ValueOf(v)) + case reflect.Ptr: + if !f.Elem().IsValid() { + err := t.set(reflect.New(f.Type().Elem())) + if err != nil { + return err + } + f = t.get() + } + f.Elem().Set(reflect.ValueOf(v)) + return nil default: return fmt.Errorf("cannot assign float64 to a %s", f.String()) } From e5d63aa8fc2ad52242c7a106e1654f0f42bac0db Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Mon, 22 Mar 2021 20:09:11 -0400 Subject: [PATCH 096/228] Add some type conversions --- targets.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/targets.go b/targets.go index 52c4cfe1..028a92da 100644 --- a/targets.go +++ b/targets.go @@ -156,8 +156,7 @@ func setString(t target, v string) error { } f = t.get() } - f.Elem().Set(reflect.ValueOf(v)) - return nil + return setString(valueTarget(f.Elem()), v) default: return fmt.Errorf("cannot assign string to a %s", f.Kind()) } @@ -179,8 +178,7 @@ func setBool(t target, v bool) error { } f = t.get() } - f.Elem().Set(reflect.ValueOf(v)) - return nil + return setBool(valueTarget(f.Elem()), v) default: return fmt.Errorf("cannot assign bool to a %s", f.String()) } @@ -193,6 +191,10 @@ func setInt64(t target, v int64) error { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: // TODO: overflow checks return t.setInt64(v) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + // TODO: overflow checks + converted := reflect.ValueOf(v).Convert(f.Type()) + return t.set(converted) case reflect.Interface: return t.set(reflect.ValueOf(v)) case reflect.Ptr: @@ -203,8 +205,7 @@ func setInt64(t target, v int64) error { } f = t.get() } - f.Elem().Set(reflect.ValueOf(v)) - return nil + return setInt64(valueTarget(f.Elem()), v) default: return fmt.Errorf("cannot assign int64 to a %s", f.String()) } @@ -227,8 +228,7 @@ func setFloat64(t target, v float64) error { } f = t.get() } - f.Elem().Set(reflect.ValueOf(v)) - return nil + return setFloat64(valueTarget(f.Elem()), v) default: return fmt.Errorf("cannot assign float64 to a %s", f.String()) } From b8da9d1854ebf1c56c0dc4267dad0d1946baa7dd Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Tue, 23 Mar 2021 08:54:44 -0400 Subject: [PATCH 097/228] Fix datetime error checking --- decode.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/decode.go b/decode.go index 5bd321e7..b016d309 100644 --- a/decode.go +++ b/decode.go @@ -75,7 +75,7 @@ func parseDateTime(b []byte) (time.Time, error) { dt, b, err := parseLocalDateTime(b) if err != nil { - return time.Time{}, nil + return time.Time{}, err } var zone *time.Location @@ -165,10 +165,16 @@ func parseLocalTime(b []byte) (LocalTime, []byte, error) { if err != nil { return t, nil, err } + if b[2] != ':' { + return t, nil, fmt.Errorf("expecting colon between hours and minutes") + } t.Minute, err = parseDecimalDigits(b[3:5]) if err != nil { return t, nil, err } + if b[5] != ':' { + return t, nil, fmt.Errorf("expecting colon between minutes and seconds") + } t.Second, err = parseDecimalDigits(b[6:8]) if err != nil { return t, nil, err From e78ccff9a47bba455b4339f40ddd9991c7256684 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Tue, 23 Mar 2021 09:02:48 -0400 Subject: [PATCH 098/228] Fix parsing integer 0 --- README.md | 2 +- .../imported_tests/unmarshal_imported_test.go | 2 +- parser.go | 18 +++++++++++------- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 158313cb..b077e970 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Development branch. Probably does not work. - [x] Unmarshal into maps. - [x] Support Array Tables. -- [x] Unmarshal into pointers. +- [x] Unmarshal into pointers. - [x] Support Date / times. - [ ] Support Unmarshaler interface. - [x] Support struct tags annotations. diff --git a/internal/imported_tests/unmarshal_imported_test.go b/internal/imported_tests/unmarshal_imported_test.go index d970dbb2..1ea19c4e 100644 --- a/internal/imported_tests/unmarshal_imported_test.go +++ b/internal/imported_tests/unmarshal_imported_test.go @@ -439,7 +439,7 @@ func TestEmptytomlUnmarshal(t *testing.T) { String: "", StringList: []string{}, Ptr: nil, - Map: map[string]string{}, + Map: nil, } result := emptyMarshalTestStruct{} diff --git a/parser.go b/parser.go index 289bb156..ce45e55f 100644 --- a/parser.go +++ b/parser.go @@ -977,17 +977,21 @@ func (p *parser) scanIntOrFloat(node *ast.Node, b []byte) ([]byte, error) { case 'b': isValidRune = isValidBinaryRune default: - return b, fmt.Errorf("unknown number base: %c. possible options are x (hex) o (octal) b (binary)", b[1]) + i++ } - i += 2 - for ; i < len(b); i++ { - if !isValidRune(b[i]) { - node.Kind = ast.Integer - node.Data = b[:i] - return b[i:], nil + if isValidRune != nil { + i += 2 + for ; i < len(b); i++ { + if !isValidRune(b[i]) { + break + } } } + + node.Kind = ast.Integer + node.Data = b[:i] + return b[i:], nil } isFloat := false From c6f117c45dfdd0f0bc17885c368f956ff2075fba Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Tue, 23 Mar 2021 09:15:48 -0400 Subject: [PATCH 099/228] Handle pointers in slices --- .../imported_tests/unmarshal_imported_test.go | 1 - targets.go | 16 +++++++++++++++- unmarshaler_test.go | 16 ++++++++++++++++ 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/internal/imported_tests/unmarshal_imported_test.go b/internal/imported_tests/unmarshal_imported_test.go index 1ea19c4e..dda52d8f 100644 --- a/internal/imported_tests/unmarshal_imported_test.go +++ b/internal/imported_tests/unmarshal_imported_test.go @@ -517,7 +517,6 @@ Str = "Hello" `) func TestPointerUnmarshal(t *testing.T) { - t.Log("TOML data:", string(pointerTestToml)) result := pointerMarshalTestStruct{} err := toml.Unmarshal(pointerTestToml, &result) require.NoError(t, err) diff --git a/targets.go b/targets.go index 028a92da..02db3727 100644 --- a/targets.go +++ b/targets.go @@ -129,17 +129,29 @@ func ensureSlice(t target) error { } case reflect.Interface: if f.IsNil() { - return t.set(reflect.MakeSlice(reflect.TypeOf([]interface{}{}), 0, 0)) + return t.set(reflect.MakeSlice(sliceInterfaceType, 0, 0)) } if f.Type().Elem().Kind() != reflect.Slice { return fmt.Errorf("interface is pointing to a %s, not a slice", f.Kind()) } + case reflect.Ptr: + if f.IsNil() { + ptr := reflect.New(f.Type().Elem()) + err := t.set(ptr) + if err != nil { + return err + } + f = t.get() + } + return ensureSlice(valueTarget(f.Elem())) default: return fmt.Errorf("cannot initialize a slice in %s", f.Kind()) } return nil } +var sliceInterfaceType = reflect.TypeOf([]interface{}{}) + func setString(t target, v string) error { f := t.get() @@ -261,6 +273,8 @@ func pushNew(t target) (target, error) { return nil, err } return valueTarget(t.get().Elem().Index(idx)), nil + case reflect.Ptr: + return pushNew(valueTarget(f.Elem())) default: return nil, fmt.Errorf("cannot pushNew on a %s", f.Kind()) } diff --git a/unmarshaler_test.go b/unmarshaler_test.go index ad1730f1..af399ebb 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -572,6 +572,22 @@ B = "data"`, } }, }, + { + desc: "slice pointer in slice pointer", + input: `A = ["Hello"]`, + gen: func() test { + type doc struct { + A *[]*string + } + hello := "Hello" + return test{ + target: &doc{}, + expected: &doc{ + A: &[]*string{&hello}, + }, + } + }, + }, } for _, e := range examples { From 5b92184e420191e9f7ac528d644e3aae2a4a4c2f Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Tue, 23 Mar 2021 09:20:28 -0400 Subject: [PATCH 100/228] Cast map key type --- targets.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/targets.go b/targets.go index 02db3727..9846e232 100644 --- a/targets.go +++ b/targets.go @@ -202,7 +202,8 @@ func setInt64(t target, v int64) error { switch f.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: // TODO: overflow checks - return t.setInt64(v) + converted := reflect.ValueOf(v).Convert(f.Type()) + return t.set(converted) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: // TODO: overflow checks converted := reflect.ValueOf(v).Convert(f.Type()) @@ -390,6 +391,15 @@ func scopeMap(v reflect.Value, name string) (target, bool, error) { } k := reflect.ValueOf(name) + + keyType := v.Type().Key() + if !k.Type().AssignableTo(keyType) { + if !k.Type().ConvertibleTo(keyType) { + return nil, false, fmt.Errorf("cannot convert string into map key type %s", keyType) + } + k = k.Convert(keyType) + } + if !v.MapIndex(k).IsValid() { newElem := reflect.New(v.Type().Elem()) v.SetMapIndex(k, newElem.Elem()) From 4038ec3daeb2d5b7f7646201a25129a9570d48ae Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Tue, 23 Mar 2021 09:44:03 -0400 Subject: [PATCH 101/228] Overflow checks --- targets.go | 77 +++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 67 insertions(+), 10 deletions(-) diff --git a/targets.go b/targets.go index 9846e232..fb531b08 100644 --- a/targets.go +++ b/targets.go @@ -2,6 +2,7 @@ package toml import ( "fmt" + "math" "reflect" "strings" ) @@ -196,18 +197,70 @@ func setBool(t target, v bool) error { } } +const maxInt = int64(^uint(0) >> 1) +const minInt = -maxInt - 1 + func setInt64(t target, v int64) error { f := t.get() switch f.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - // TODO: overflow checks - converted := reflect.ValueOf(v).Convert(f.Type()) - return t.set(converted) - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - // TODO: overflow checks - converted := reflect.ValueOf(v).Convert(f.Type()) - return t.set(converted) + case reflect.Int64: + return t.setInt64(v) + case reflect.Int32: + if v < math.MinInt32 || v > math.MaxInt32 { + return fmt.Errorf("integer %d does not fit in an int32", v) + } + return t.set(reflect.ValueOf(int32(v))) + case reflect.Int16: + if v < math.MinInt16 || v > math.MaxInt16 { + return fmt.Errorf("integer %d does not fit in an int16", v) + } + return t.set(reflect.ValueOf(int16(v))) + case reflect.Int8: + if v < math.MinInt8 || v > math.MaxInt8 { + return fmt.Errorf("integer %d does not fit in an int8", v) + } + return t.set(reflect.ValueOf(int8(v))) + case reflect.Int: + if v < minInt || v > maxInt { + return fmt.Errorf("integer %d does not fit in an int", v) + } + return t.set(reflect.ValueOf(int(v))) + + case reflect.Uint64: + if v < 0 { + return fmt.Errorf("negative integer %d cannot be stored in an uint64", v) + } + return t.set(reflect.ValueOf(uint64(v))) + case reflect.Uint32: + if v < 0 { + return fmt.Errorf("negative integer %d cannot be stored in an uint32", v) + } + if v > math.MaxUint32 { + return fmt.Errorf("integer %d cannot be stored in an uint32", v) + } + return t.set(reflect.ValueOf(uint32(v))) + case reflect.Uint16: + if v < 0 { + return fmt.Errorf("negative integer %d cannot be stored in an uint16", v) + } + if v > math.MaxUint16 { + return fmt.Errorf("integer %d cannot be stored in an uint16", v) + } + return t.set(reflect.ValueOf(uint16(v))) + case reflect.Uint8: + if v < 0 { + return fmt.Errorf("negative integer %d cannot be stored in an uint8", v) + } + if v > math.MaxUint8 { + return fmt.Errorf("integer %d cannot be stored in an uint8", v) + } + return t.set(reflect.ValueOf(uint8(v))) + case reflect.Uint: + if v < 0 { + return fmt.Errorf("negative integer %d cannot be stored in an uint", v) + } + return t.set(reflect.ValueOf(uint(v))) case reflect.Interface: return t.set(reflect.ValueOf(v)) case reflect.Ptr: @@ -228,9 +281,13 @@ func setFloat64(t target, v float64) error { f := t.get() switch f.Kind() { - case reflect.Float32, reflect.Float64: - // TODO: overflow checks + case reflect.Float64: return t.setFloat64(v) + case reflect.Float32: + if v > math.MaxFloat32 { + return fmt.Errorf("float %f cannot be stored in a float32", v) + } + return t.set(reflect.ValueOf(float32(v))) case reflect.Interface: return t.set(reflect.ValueOf(v)) case reflect.Ptr: From d458ddf4d43b134a812112ea493571c02ad58dd9 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Tue, 23 Mar 2021 09:45:12 -0400 Subject: [PATCH 102/228] Add TODO --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b077e970..efcb64ba 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ Development branch. Probably does not work. node. - [ ] Provide "minimal allocations" option that uses `unsafe` to reuse the input byte array as storage for strings. +- [ ] Cache reflection operations per type. ## Ideas From 0703eeb262a29fab06619d7c7f31ad024982b428 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Tue, 23 Mar 2021 18:01:14 -0400 Subject: [PATCH 103/228] Fix bug parsing anonymous structs --- targets.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/targets.go b/targets.go index fb531b08..b11f57ae 100644 --- a/targets.go +++ b/targets.go @@ -483,9 +483,7 @@ func scopeStruct(v reflect.Value, name string) (target, bool, error) { f := t.Field(i) if f.PkgPath != "" { // only consider exported fields - continue - } - if f.Anonymous { + } else if f.Anonymous { walk(v.Field(i)) } else { fieldName, ok := f.Tag.Lookup("toml") From bfeb32c9ce804f3ecb33d2ef81eb1ad31587357b Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Tue, 23 Mar 2021 20:03:45 -0400 Subject: [PATCH 104/228] Make unmarshal to interface{} consistent with encoding/json --- .../imported_tests/unmarshal_imported_test.go | 47 ++++++++----------- targets.go | 11 +++-- unmarshaler_test.go | 25 ++++++++++ 3 files changed, 51 insertions(+), 32 deletions(-) diff --git a/internal/imported_tests/unmarshal_imported_test.go b/internal/imported_tests/unmarshal_imported_test.go index dda52d8f..f450d509 100644 --- a/internal/imported_tests/unmarshal_imported_test.go +++ b/internal/imported_tests/unmarshal_imported_test.go @@ -126,10 +126,10 @@ func TestInterface(t *testing.T) { expected := Conf{ Name: "rui", Age: 18, - Inter: &NestedStruct{ - FirstName: "wang", - LastName: "jl", - Age: 100, + Inter: map[string]interface{}{ + "FirstName": "wang", + "LastName": "jl", + "Age": int64(100), }, } assert.Equal(t, expected, config) @@ -1663,25 +1663,21 @@ Age = 23 }, NilField: nil, InterfacePointerField: &s, - StructArrayField: []map[string]interface{}{ - { + StructArrayField: []interface{}{ + map[string]interface{}{ "Name": "Allen", "Age": int64(20), }, - { + map[string]interface{}{ "Name": "Jack", "Age": int64(23), }, }, } actual := OuterStruct{} - if err := toml.Unmarshal(doc, &actual); err == nil { - if !reflect.DeepEqual(actual, expected) { - t.Errorf("Bad unmarshal: expected %v, got %v", expected, actual) - } - } else { - t.Fatal(err) - } + err := toml.Unmarshal(doc, &actual) + require.NoError(t, err) + assert.Equal(t, expected, actual) } func TestUnmarshalToNonNilInterface(t *testing.T) { @@ -1727,8 +1723,8 @@ InnerField = "After4" var s interface{} = InnerStruct{"After"} expected := OuterStruct{ PrimitiveField: "Allen", - ArrayField: []int{1, 2, 3}, - StructField: InnerStruct{InnerField: "After1"}, + ArrayField: []interface{}{int64(1), int64(2), int64(3)}, + StructField: map[string]interface{}{"InnerField": "After1"}, MapField: map[string]interface{}{ "MapField1": []interface{}{int64(4), int64(5), int64(6)}, "MapField2": map[string]interface{}{ @@ -1736,12 +1732,12 @@ InnerField = "After4" }, "MapField3": false, }, - PointerField: &InnerStruct{InnerField: "After2"}, + PointerField: map[string]interface{}{"InnerField": "After2"}, NilField: nil, InterfacePointerField: &s, - StructArrayField: []InnerStruct{ - {InnerField: "After3"}, - {InnerField: "After4"}, + StructArrayField: []interface{}{ + map[string]interface{}{"InnerField": "After3"}, + map[string]interface{}{"InnerField": "After4"}, }, } actual := OuterStruct{ @@ -1763,13 +1759,10 @@ InnerField = "After4" {InnerField: "Before4"}, }, } - if err := toml.Unmarshal(doc, &actual); err == nil { - if !reflect.DeepEqual(actual, expected) { - t.Errorf("Bad unmarshal: expected %v, got %v", expected, actual) - } - } else { - t.Fatal(err) - } + + err := toml.Unmarshal(doc, &actual) + require.NoError(t, err) + assert.Equal(t, expected, actual) } func TestUnmarshalNil(t *testing.T) { diff --git a/targets.go b/targets.go index b11f57ae..8595f09d 100644 --- a/targets.go +++ b/targets.go @@ -129,10 +129,10 @@ func ensureSlice(t target) error { return t.set(reflect.MakeSlice(f.Type(), 0, 0)) } case reflect.Interface: - if f.IsNil() { + if f.IsNil() || f.Elem().Type() != sliceInterfaceType { return t.set(reflect.MakeSlice(sliceInterfaceType, 0, 0)) } - if f.Type().Elem().Kind() != reflect.Slice { + if f.Elem().Type().Kind() != reflect.Slice { return fmt.Errorf("interface is pointing to a %s, not a slice", f.Kind()) } case reflect.Ptr: @@ -152,6 +152,7 @@ func ensureSlice(t target) error { } var sliceInterfaceType = reflect.TypeOf([]interface{}{}) +var mapStringInterfaceType = reflect.TypeOf(map[string]interface{}{}) func setString(t target, v string) error { f := t.get() @@ -409,15 +410,15 @@ func initInterface(append bool, t target) error { panic("this should only be called on interfaces") } - if !x.IsNil() { + if !x.IsNil() && (x.Elem().Type() == sliceInterfaceType || x.Elem().Type() == mapStringInterfaceType) { return nil } var newElement reflect.Value if append { - newElement = reflect.MakeSlice(reflect.TypeOf([]interface{}{}), 0, 0) + newElement = reflect.MakeSlice(sliceInterfaceType, 0, 0) } else { - newElement = reflect.MakeMap(reflect.TypeOf(map[string]interface{}{})) + newElement = reflect.MakeMap(mapStringInterfaceType) } err := t.set(newElement) if err != nil { diff --git a/unmarshaler_test.go b/unmarshaler_test.go index af399ebb..8e4c0846 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -588,6 +588,31 @@ B = "data"`, } }, }, + { + desc: "interface holding a struct", + input: `[A] + B = "After"`, + gen: func() test { + type inner struct { + B interface{} + } + type doc struct { + A interface{} + } + return test{ + target: &doc{ + A: inner{ + B: "Before", + }, + }, + expected: &doc{ + A: map[string]interface{}{ + "B": "After", + }, + }, + } + }, + }, } for _, e := range examples { From a3b7e1e3539bf30935505bb4038035dc6f90dc82 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Tue, 23 Mar 2021 21:14:54 -0400 Subject: [PATCH 105/228] Fix table array into pointer to slice --- unmarshaler.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/unmarshaler.go b/unmarshaler.go index 536b103d..47592edd 100644 --- a/unmarshaler.go +++ b/unmarshaler.go @@ -122,6 +122,14 @@ func scopeWithArrayTable(x target, key []ast.Node) (target, bool, error) { v := x.get() + if v.Kind() == reflect.Ptr { + x, err = scopePtr(x) + if err != nil { + return x, false, err + } + v = x.get() + } + if v.Kind() == reflect.Interface { x, err = scopeInterface(true, x) if err != nil { From a25f636a07375c5a04b276d33cf6937ed3916716 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Tue, 23 Mar 2021 21:18:19 -0400 Subject: [PATCH 106/228] Add array support todo --- README.md | 3 ++- internal/imported_tests/unmarshal_imported_test.go | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index efcb64ba..cfc08dce 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,9 @@ Development branch. Probably does not work. - [x] Support Array Tables. - [x] Unmarshal into pointers. - [x] Support Date / times. -- [ ] Support Unmarshaler interface. - [x] Support struct tags annotations. +- [ ] Support Arrays. +- [ ] Support Unmarshaler interface. - [ ] Original go-toml unmarshal tests pass. - [ ] Benchmark! - [ ] Abstract AST. diff --git a/internal/imported_tests/unmarshal_imported_test.go b/internal/imported_tests/unmarshal_imported_test.go index f450d509..0e06d0c6 100644 --- a/internal/imported_tests/unmarshal_imported_test.go +++ b/internal/imported_tests/unmarshal_imported_test.go @@ -1833,7 +1833,7 @@ func TestUnmarshalSliceFail2(t *testing.T) { assert.Error(t, toml.Unmarshal([]byte(doc), &actual)) } -func TestUnmarshalMixedTypeArray(t *testing.T) { +func TestUnmarshalMixedTypeSlice(t *testing.T) { type TestStruct struct { ArrayField []interface{} } @@ -1854,9 +1854,9 @@ func TestUnmarshalMixedTypeArray(t *testing.T) { map[string]interface{}{ "Field": "inner1", }, - []map[string]interface{}{ - {"Field": "inner2"}, - {"Field": "inner3"}, + []interface{}{ + map[string]interface{}{"Field": "inner2"}, + map[string]interface{}{"Field": "inner3"}, }, }, } From a0d031abec86cd817124037da5557501bc1fb95a Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Wed, 24 Mar 2021 20:21:55 -0400 Subject: [PATCH 107/228] Arrays support --- README.md | 2 +- .../imported_tests/unmarshal_imported_test.go | 14 ---- targets.go | 53 ++++++++++--- targets_test.go | 25 +++--- unmarshaler.go | 79 +++++++++++++------ unmarshaler_test.go | 45 +++++++++-- 6 files changed, 152 insertions(+), 66 deletions(-) diff --git a/README.md b/README.md index cfc08dce..c7f0738b 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Development branch. Probably does not work. - [x] Unmarshal into pointers. - [x] Support Date / times. - [x] Support struct tags annotations. -- [ ] Support Arrays. +- [x] Support Arrays. - [ ] Support Unmarshaler interface. - [ ] Original go-toml unmarshal tests pass. - [ ] Benchmark! diff --git a/internal/imported_tests/unmarshal_imported_test.go b/internal/imported_tests/unmarshal_imported_test.go index 0e06d0c6..602765fb 100644 --- a/internal/imported_tests/unmarshal_imported_test.go +++ b/internal/imported_tests/unmarshal_imported_test.go @@ -1883,20 +1883,6 @@ func TestUnmarshalArray(t *testing.T) { assert.Equal(t, expected, actual) } -func TestUnmarshalArrayFail(t *testing.T) { - var actual arrayTooSmallStruct - err := toml.Unmarshal([]byte(`str_slice = ["Howdy", "Hey There"]`), &actual) - assert.Error(t, err) -} - -func TestUnmarshalArrayFail2(t *testing.T) { - doc := `str_slice=["Howdy","Hey There"]` - - var actual arrayTooSmallStruct - err := toml.Unmarshal([]byte(doc), &actual) - assert.Error(t, err) -} - func TestUnmarshalArrayFail3(t *testing.T) { doc := `[[struct_slice]] String2="1" diff --git a/targets.go b/targets.go index 8595f09d..e620d28e 100644 --- a/targets.go +++ b/targets.go @@ -120,7 +120,9 @@ func (t mapTarget) setFloat64(v float64) error { return t.set(reflect.ValueOf(v)) } -func ensureSlice(t target) error { +// makes sure that the value pointed at by t is indexable (Slice, Array), or +// dereferences to an indexable (Ptr, Interface). +func ensureValueIndexable(t target) error { f := t.get() switch f.Type().Kind() { @@ -144,7 +146,9 @@ func ensureSlice(t target) error { } f = t.get() } - return ensureSlice(valueTarget(f.Elem())) + return ensureValueIndexable(valueTarget(f.Elem())) + case reflect.Array: + // arrays are always initialized. default: return fmt.Errorf("cannot initialize a slice in %s", f.Kind()) } @@ -305,24 +309,34 @@ func setFloat64(t target, v float64) error { } } -func pushNew(t target) (target, error) { +// Returns the element at idx of the value pointed at by target, or an error if +// t does not point to an indexable. +// If the target points to an Array and idx is out of bounds, it returns +// (nil, nil) as this is not a fatal error (the unmarshaler will skip). +func elementAt(t target, idx int) (target, error) { f := t.get() switch f.Kind() { case reflect.Slice: + // TODO: use the idx function argument and avoid alloc if possible. idx := f.Len() err := t.set(reflect.Append(f, reflect.New(f.Type().Elem()).Elem())) if err != nil { return nil, err } return valueTarget(t.get().Index(idx)), nil + case reflect.Array: + if idx >= f.Len() { + return nil, nil + } + return valueTarget(f.Index(idx)), nil case reflect.Interface: if f.IsNil() { panic("interface should have been initialized") } ifaceElem := f.Elem() if ifaceElem.Kind() != reflect.Slice { - return nil, fmt.Errorf("cannot pushNew on a %s", f.Kind()) + return nil, fmt.Errorf("cannot elementAt on a %s", f.Kind()) } idx := ifaceElem.Len() newElem := reflect.New(ifaceElem.Type().Elem()).Elem() @@ -333,13 +347,13 @@ func pushNew(t target) (target, error) { } return valueTarget(t.get().Elem().Index(idx)), nil case reflect.Ptr: - return pushNew(valueTarget(f.Elem())) + return elementAt(valueTarget(f.Elem()), idx) default: - return nil, fmt.Errorf("cannot pushNew on a %s", f.Kind()) + return nil, fmt.Errorf("cannot elementAt on a %s", f.Kind()) } } -func scopeTableTarget(append bool, t target, name string) (target, bool, error) { +func (d *decoder) scopeTableTarget(append bool, t target, name string) (target, bool, error) { x := t.get() switch x.Kind() { @@ -350,20 +364,27 @@ func scopeTableTarget(append bool, t target, name string) (target, bool, error) if err != nil { return t, false, err } - return scopeTableTarget(append, t, name) + return d.scopeTableTarget(append, t, name) case reflect.Ptr: t, err := scopePtr(t) if err != nil { return t, false, err } - return scopeTableTarget(append, t, name) + return d.scopeTableTarget(append, t, name) case reflect.Slice: t, err := scopeSlice(append, t) if err != nil { return t, false, err } append = false - return scopeTableTarget(append, t, name) + return d.scopeTableTarget(append, t, name) + case reflect.Array: + t, err := d.scopeArray(append, t) + if err != nil { + return t, false, err + } + append = false + return d.scopeTableTarget(append, t, name) // Terminal kinds @@ -443,6 +464,18 @@ func scopeSlice(append bool, t target) (target, error) { return valueTarget(v.Index(v.Len() - 1)), nil } +func (d *decoder) scopeArray(append bool, t target) (target, error) { + v := t.get() + + idx := d.arrayIndex(append, v) + + if idx >= v.Len() { + return nil, fmt.Errorf("not enough space in the array") + } + + return valueTarget(v.Index(idx)), nil +} + func scopeMap(v reflect.Value, name string) (target, bool, error) { if v.IsNil() { v.Set(reflect.MakeMap(v.Type())) diff --git a/targets_test.go b/targets_test.go index 7316994e..86aab96e 100644 --- a/targets_test.go +++ b/targets_test.go @@ -39,9 +39,10 @@ func TestStructTarget_Ensure(t *testing.T) { for _, e := range examples { t.Run(e.desc, func(t *testing.T) { - target, _, err := scopeTableTarget(false, valueTarget(e.input), e.name) + d := decoder{} + target, _, err := d.scopeTableTarget(false, valueTarget(e.input), e.name) require.NoError(t, err) - err = ensureSlice(target) + err = ensureValueIndexable(target) v := target.get() e.test(v, err) }) @@ -86,7 +87,8 @@ func TestStructTarget_SetString(t *testing.T) { for _, e := range examples { t.Run(e.desc, func(t *testing.T) { - target, _, err := scopeTableTarget(false, valueTarget(e.input), e.name) + d := decoder{} + target, _, err := d.scopeTableTarget(false, valueTarget(e.input), e.name) require.NoError(t, err) err = setString(target, str) v := target.get() @@ -102,15 +104,16 @@ func TestPushNew(t *testing.T) { } d := Doc{} - x, _, err := scopeTableTarget(false, valueTarget(reflect.ValueOf(&d).Elem()), "A") + dec := decoder{} + x, _, err := dec.scopeTableTarget(false, valueTarget(reflect.ValueOf(&d).Elem()), "A") require.NoError(t, err) - n, err := pushNew(x) + n, err := elementAt(x, 0) require.NoError(t, err) require.NoError(t, n.setString("hello")) require.Equal(t, []string{"hello"}, d.A) - n, err = pushNew(x) + n, err = elementAt(x, 1) require.NoError(t, err) require.NoError(t, n.setString("world")) require.Equal(t, []string{"hello", "world"}, d.A) @@ -122,15 +125,16 @@ func TestPushNew(t *testing.T) { } d := Doc{} - x, _, err := scopeTableTarget(false, valueTarget(reflect.ValueOf(&d).Elem()), "A") + dec := decoder{} + x, _, err := dec.scopeTableTarget(false, valueTarget(reflect.ValueOf(&d).Elem()), "A") require.NoError(t, err) - n, err := pushNew(x) + n, err := elementAt(x, 0) require.NoError(t, err) require.NoError(t, setString(n, "hello")) require.Equal(t, []interface{}{"hello"}, d.A) - n, err = pushNew(x) + n, err = elementAt(x, 1) require.NoError(t, err) require.NoError(t, setString(n, "world")) require.Equal(t, []interface{}{"hello", "world"}, d.A) @@ -164,7 +168,8 @@ func TestScope_Struct(t *testing.T) { for _, e := range examples { t.Run(e.desc, func(t *testing.T) { - x, found, err := scopeTableTarget(false, valueTarget(e.input), e.name) + dec := decoder{} + x, found, err := dec.scopeTableTarget(false, valueTarget(e.input), e.name) assert.Equal(t, e.found, found) if e.err { assert.Error(t, err) diff --git a/unmarshaler.go b/unmarshaler.go index 47592edd..68881af6 100644 --- a/unmarshaler.go +++ b/unmarshaler.go @@ -19,7 +19,9 @@ func Unmarshal(data []byte, v interface{}) error { // TODO: remove me; sanity check allValidOrDump(p.tree, p.tree) - return fromAst(p.tree, v) + d := decoder{} + + return d.fromAst(p.tree, v) } func allValidOrDump(tree ast.Root, nodes []ast.Node) bool { @@ -37,7 +39,28 @@ func allValidOrDump(tree ast.Root, nodes []ast.Node) bool { return true } -func fromAst(tree ast.Root, v interface{}) error { +type decoder struct { + // Tracks position in Go arrays. + arrayIndexes map[reflect.Value]int +} + +func (d *decoder) arrayIndex(append bool, v reflect.Value) int { + if d.arrayIndexes == nil { + d.arrayIndexes = make(map[reflect.Value]int, 1) + } + + idx, ok := d.arrayIndexes[v] + + if !ok { + d.arrayIndexes[v] = 0 + } else if append { + idx++ + d.arrayIndexes[v] = idx + } + return idx +} + +func (d *decoder) fromAst(tree ast.Root, v interface{}) error { r := reflect.ValueOf(v) if r.Kind() != reflect.Ptr { return fmt.Errorf("need to target a pointer, not %s", r.Kind()) @@ -57,12 +80,12 @@ func fromAst(tree ast.Root, v interface{}) error { if skipUntilTable { continue } - err = unmarshalKeyValue(current, &node) + err = d.unmarshalKeyValue(current, &node) found = true case ast.Table: - current, found, err = scopeWithKey(root, node.Key()) + current, found, err = d.scopeWithKey(root, node.Key()) case ast.ArrayTable: - current, found, err = scopeWithArrayTable(root, node.Key()) + current, found, err = d.scopeWithArrayTable(root, node.Key()) default: panic(fmt.Errorf("this should not be a top level node type: %s", node.Kind)) } @@ -87,11 +110,11 @@ func fromAst(tree ast.Root, v interface{}) error { // // When encountering slices, it should always use its last element, and error // if the slice does not have any. -func scopeWithKey(x target, key []ast.Node) (target, bool, error) { +func (d *decoder) scopeWithKey(x target, key []ast.Node) (target, bool, error) { var err error found := true for _, n := range key { - x, found, err = scopeTableTarget(false, x, string(n.Data)) + x, found, err = d.scopeTableTarget(false, x, string(n.Data)) if err != nil || !found { return nil, found, err } @@ -104,18 +127,18 @@ func scopeWithKey(x target, key []ast.Node) (target, bool, error) { // // It is the same as scopeWithKey, but when scoping the last part of the key // it creates a new element in the array instead of using the last one. -func scopeWithArrayTable(x target, key []ast.Node) (target, bool, error) { +func (d *decoder) scopeWithArrayTable(x target, key []ast.Node) (target, bool, error) { var err error found := true if len(key) > 1 { for _, n := range key[:len(key)-1] { - x, found, err = scopeTableTarget(false, x, string(n.Data)) + x, found, err = d.scopeTableTarget(false, x, string(n.Data)) if err != nil || !found { return nil, found, err } } } - x, found, err = scopeTableTarget(false, x, string(key[len(key)-1].Data)) + x, found, err = d.scopeTableTarget(false, x, string(key[len(key)-1].Data)) if err != nil || !found { return x, found, err } @@ -138,17 +161,20 @@ func scopeWithArrayTable(x target, key []ast.Node) (target, bool, error) { v = x.get() } - if v.Kind() == reflect.Slice { + switch v.Kind() { + case reflect.Slice: x, err = scopeSlice(true, x) + case reflect.Array: + x, err = d.scopeArray(true, x) } return x, found, err } -func unmarshalKeyValue(x target, node *ast.Node) error { +func (d *decoder) unmarshalKeyValue(x target, node *ast.Node) error { assertNode(ast.KeyValue, node) - x, found, err := scopeWithKey(x, node.Key()) + x, found, err := d.scopeWithKey(x, node.Key()) if err != nil { return err } @@ -158,10 +184,10 @@ func unmarshalKeyValue(x target, node *ast.Node) error { return nil } - return unmarshalValue(x, node.Value()) + return d.unmarshalValue(x, node.Value()) } -func unmarshalValue(x target, node *ast.Node) error { +func (d *decoder) unmarshalValue(x target, node *ast.Node) error { switch node.Kind { case ast.String: return unmarshalString(x, node) @@ -172,9 +198,9 @@ func unmarshalValue(x target, node *ast.Node) error { case ast.Float: return unmarshalFloat(x, node) case ast.Array: - return unmarshalArray(x, node) + return d.unmarshalArray(x, node) case ast.InlineTable: - return unmarshalInlineTable(x, node) + return d.unmarshalInlineTable(x, node) case ast.LocalDateTime: return unmarshalLocalDateTime(x, node) case ast.DateTime: @@ -242,11 +268,11 @@ func unmarshalFloat(x target, node *ast.Node) error { return setFloat64(x, v) } -func unmarshalInlineTable(x target, node *ast.Node) error { +func (d *decoder) unmarshalInlineTable(x target, node *ast.Node) error { assertNode(ast.InlineTable, node) for _, kv := range node.Children { - err := unmarshalKeyValue(x, &kv) + err := d.unmarshalKeyValue(x, &kv) if err != nil { return err } @@ -254,20 +280,25 @@ func unmarshalInlineTable(x target, node *ast.Node) error { return nil } -func unmarshalArray(x target, node *ast.Node) error { +func (d *decoder) unmarshalArray(x target, node *ast.Node) error { assertNode(ast.Array, node) - err := ensureSlice(x) + err := ensureValueIndexable(x) if err != nil { return err } - for _, n := range node.Children { - v, err := pushNew(x) + for idx, n := range node.Children { + v, err := elementAt(x, idx) if err != nil { return err } - err = unmarshalValue(v, &n) + if v == nil { + // when we go out of bound for an array just stop processing it to + // mimic encoding/json + break + } + err = d.unmarshalValue(v, &n) if err != nil { return err } diff --git a/unmarshaler_test.go b/unmarshaler_test.go index 8e4c0846..af1592ab 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -613,6 +613,30 @@ B = "data"`, } }, }, + { + desc: "array of structs with table arrays", + input: `[[A]] + B = "one" + [[A]] + B = "two"`, + gen: func() test { + type inner struct { + B string + } + type doc struct { + A [4]inner + } + return test{ + target: &doc{}, + expected: &doc{ + A: [4]inner{ + {B: "one"}, + {B: "two"}, + }, + }, + } + }, + }, } for _, e := range examples { @@ -657,7 +681,8 @@ func TestFromAst_KV(t *testing.T) { } x := Doc{} - err := fromAst(root, &x) + d := decoder{} + err := d.fromAst(root, &x) require.NoError(t, err) assert.Equal(t, Doc{Foo: "hello"}, x) } @@ -709,7 +734,8 @@ func TestFromAst_Table(t *testing.T) { } x := Doc{} - err := fromAst(root, &x) + d := decoder{} + err := d.fromAst(root, &x) require.NoError(t, err) assert.Equal(t, Doc{ Level1: Level1{ @@ -755,7 +781,8 @@ func TestFromAst_Table(t *testing.T) { } x := Doc{} - err := fromAst(root, &x) + d := decoder{} + err := d.fromAst(root, &x) require.NoError(t, err) assert.Equal(t, Doc{ A: A{B: B{C: "value"}}, @@ -805,7 +832,8 @@ func TestFromAst_InlineTable(t *testing.T) { } x := Doc{} - err := fromAst(root, &x) + d := decoder{} + err := d.fromAst(root, &x) require.NoError(t, err) assert.Equal(t, Doc{ Name: Name{ @@ -849,7 +877,8 @@ func TestFromAst_Slice(t *testing.T) { } x := Doc{} - err := fromAst(root, &x) + d := decoder{} + err := d.fromAst(root, &x) require.NoError(t, err) assert.Equal(t, Doc{Foo: []string{"hello", "world"}}, x) }) @@ -885,7 +914,8 @@ func TestFromAst_Slice(t *testing.T) { } x := Doc{} - err := fromAst(root, &x) + d := decoder{} + err := d.fromAst(root, &x) require.NoError(t, err) assert.Equal(t, Doc{Foo: []interface{}{"hello", "world"}}, x) }) @@ -930,7 +960,8 @@ func TestFromAst_Slice(t *testing.T) { } x := Doc{} - err := fromAst(root, &x) + d := decoder{} + err := d.fromAst(root, &x) require.NoError(t, err) assert.Equal(t, Doc{Foo: []interface{}{"hello", []interface{}{"inner1", "inner2"}}}, x) }) From dd5837651d77c6caf44e6dc1535fbed4492a876b Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Wed, 24 Mar 2021 21:02:02 -0400 Subject: [PATCH 108/228] Support TextUnmarshaler --- README.md | 4 +- .../imported_tests/unmarshal_imported_test.go | 42 +++++---------- unmarshaler.go | 54 ++++++++++++------- 3 files changed, 50 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index c7f0738b..167f57fe 100644 --- a/README.md +++ b/README.md @@ -12,8 +12,8 @@ Development branch. Probably does not work. - [x] Support Date / times. - [x] Support struct tags annotations. - [x] Support Arrays. -- [ ] Support Unmarshaler interface. -- [ ] Original go-toml unmarshal tests pass. +- [x] Support Unmarshaler interface. +- [x] Original go-toml unmarshal tests pass. - [ ] Benchmark! - [ ] Abstract AST. - [ ] Attach comments to AST (gated by parser flag). diff --git a/internal/imported_tests/unmarshal_imported_test.go b/internal/imported_tests/unmarshal_imported_test.go index 602765fb..5e113abb 100644 --- a/internal/imported_tests/unmarshal_imported_test.go +++ b/internal/imported_tests/unmarshal_imported_test.go @@ -1994,6 +1994,7 @@ type parent struct { } func TestCustomUnmarshal(t *testing.T) { + t.Skip("not sure if UnmarshalTOML is a good idea") input := ` [Doc] key = "ok1" @@ -2002,18 +2003,15 @@ func TestCustomUnmarshal(t *testing.T) { ` var d parent - if err := toml.Unmarshal([]byte(input), &d); err != nil { - t.Fatalf("unexpected err: %s", err.Error()) - } - if d.Doc.Decoded.Key != "ok1" { - t.Errorf("Bad unmarshal: expected ok, got %v", d.Doc.Decoded.Key) - } - if d.DocPointer.Decoded.Key != "ok2" { - t.Errorf("Bad unmarshal: expected ok, got %v", d.DocPointer.Decoded.Key) - } + err := toml.Unmarshal([]byte(input), &d) + require.NoError(t, err) + assert.Equal(t, "ok1", d.Doc.Decoded.Key) + assert.Equal(t, "ok2", d.DocPointer.Decoded.Key) } func TestCustomUnmarshalError(t *testing.T) { + t.Skip("not sure if UnmarshalTOML is a good idea") + input := ` [Doc] key = 1 @@ -2071,25 +2069,13 @@ Bool = true Int = 21 Float = 2.0 ` - - if err := toml.Unmarshal([]byte(input), &doc); err != nil { - t.Fatalf("unexpected err: %s", err.Error()) - } - if doc.UnixTime.Value != 12 { - t.Fatalf("expected UnixTime: 12 got: %d", doc.UnixTime.Value) - } - if doc.Version.Value != 42 { - t.Fatalf("expected Version: 42 got: %d", doc.Version.Value) - } - if doc.Bool.Value != 1 { - t.Fatalf("expected Bool: 1 got: %d", doc.Bool.Value) - } - if doc.Int.Value != 21 { - t.Fatalf("expected Int: 21 got: %d", doc.Int.Value) - } - if doc.Float.Value != 2 { - t.Fatalf("expected Float: 2 got: %d", doc.Float.Value) - } + err := toml.Unmarshal([]byte(input), &doc) + require.NoError(t, err) + assert.Equal(t, 12, doc.UnixTime.Value) + assert.Equal(t, 42, doc.Version.Value) + assert.Equal(t, 1, doc.Bool.Value) + assert.Equal(t, 21, doc.Int.Value) + assert.Equal(t, 2, doc.Float.Value) } func TestTextUnmarshalError(t *testing.T) { diff --git a/unmarshaler.go b/unmarshaler.go index 68881af6..bba90ffe 100644 --- a/unmarshaler.go +++ b/unmarshaler.go @@ -1,8 +1,8 @@ package toml import ( + "encoding" "fmt" - "os" "reflect" "time" @@ -16,29 +16,10 @@ func Unmarshal(data []byte, v interface{}) error { return err } - // TODO: remove me; sanity check - allValidOrDump(p.tree, p.tree) - d := decoder{} - return d.fromAst(p.tree, v) } -func allValidOrDump(tree ast.Root, nodes []ast.Node) bool { - for i, n := range nodes { - if n.Kind == ast.Invalid { - fmt.Printf("AST contains invalid node! idx=%d\n", i) - fmt.Fprintf(os.Stderr, "%s\n", tree.Sdot()) - return false - } - ok := allValidOrDump(tree, n.Children) - if !ok { - return ok - } - } - return true -} - type decoder struct { // Tracks position in Go arrays. arrayIndexes map[reflect.Value]int @@ -187,7 +168,40 @@ func (d *decoder) unmarshalKeyValue(x target, node *ast.Node) error { return d.unmarshalValue(x, node.Value()) } +var textUnmarshalerType = reflect.TypeOf(new(encoding.TextUnmarshaler)).Elem() + +func tryTextUnmarshaler(x target, node *ast.Node) (bool, error) { + v := x.get() + + if v.Kind() == reflect.Ptr { + if !v.Elem().IsValid() { + err := x.set(reflect.New(v.Type().Elem())) + if err != nil { + return false, nil + } + v = x.get() + } + return tryTextUnmarshaler(valueTarget(v.Elem()), node) + } + + if v.Kind() != reflect.Struct { + return false, nil + } + if v.Type().Implements(textUnmarshalerType) { + return true, v.Interface().(encoding.TextUnmarshaler).UnmarshalText(node.Data) + } + if v.CanAddr() && v.Addr().Type().Implements(textUnmarshalerType) { + return true, v.Addr().Interface().(encoding.TextUnmarshaler).UnmarshalText(node.Data) + } + return false, nil +} + func (d *decoder) unmarshalValue(x target, node *ast.Node) error { + ok, err := tryTextUnmarshaler(x, node) + if ok { + return err + } + switch node.Kind { case ast.String: return unmarshalString(x, node) From 43fc2fa552be5b183cddeb212cbf430bcb85ec86 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Wed, 24 Mar 2021 21:05:44 -0400 Subject: [PATCH 109/228] Factor pointer handling --- targets.go | 36 ------------------------------------ unmarshaler.go | 23 ++++++++++++----------- 2 files changed, 12 insertions(+), 47 deletions(-) diff --git a/targets.go b/targets.go index e620d28e..2fd410d4 100644 --- a/targets.go +++ b/targets.go @@ -166,15 +166,6 @@ func setString(t target, v string) error { return t.setString(v) case reflect.Interface: return t.set(reflect.ValueOf(v)) - case reflect.Ptr: - if !f.Elem().IsValid() { - err := t.set(reflect.New(f.Type().Elem())) - if err != nil { - return err - } - f = t.get() - } - return setString(valueTarget(f.Elem()), v) default: return fmt.Errorf("cannot assign string to a %s", f.Kind()) } @@ -188,15 +179,6 @@ func setBool(t target, v bool) error { return t.setBool(v) case reflect.Interface: return t.set(reflect.ValueOf(v)) - case reflect.Ptr: - if !f.Elem().IsValid() { - err := t.set(reflect.New(f.Type().Elem())) - if err != nil { - return err - } - f = t.get() - } - return setBool(valueTarget(f.Elem()), v) default: return fmt.Errorf("cannot assign bool to a %s", f.String()) } @@ -268,15 +250,6 @@ func setInt64(t target, v int64) error { return t.set(reflect.ValueOf(uint(v))) case reflect.Interface: return t.set(reflect.ValueOf(v)) - case reflect.Ptr: - if !f.Elem().IsValid() { - err := t.set(reflect.New(f.Type().Elem())) - if err != nil { - return err - } - f = t.get() - } - return setInt64(valueTarget(f.Elem()), v) default: return fmt.Errorf("cannot assign int64 to a %s", f.String()) } @@ -295,15 +268,6 @@ func setFloat64(t target, v float64) error { return t.set(reflect.ValueOf(float32(v))) case reflect.Interface: return t.set(reflect.ValueOf(v)) - case reflect.Ptr: - if !f.Elem().IsValid() { - err := t.set(reflect.New(f.Type().Elem())) - if err != nil { - return err - } - f = t.get() - } - return setFloat64(valueTarget(f.Elem()), v) default: return fmt.Errorf("cannot assign float64 to a %s", f.String()) } diff --git a/unmarshaler.go b/unmarshaler.go index bba90ffe..d8b31d6f 100644 --- a/unmarshaler.go +++ b/unmarshaler.go @@ -173,17 +173,6 @@ var textUnmarshalerType = reflect.TypeOf(new(encoding.TextUnmarshaler)).Elem() func tryTextUnmarshaler(x target, node *ast.Node) (bool, error) { v := x.get() - if v.Kind() == reflect.Ptr { - if !v.Elem().IsValid() { - err := x.set(reflect.New(v.Type().Elem())) - if err != nil { - return false, nil - } - v = x.get() - } - return tryTextUnmarshaler(valueTarget(v.Elem()), node) - } - if v.Kind() != reflect.Struct { return false, nil } @@ -197,6 +186,18 @@ func tryTextUnmarshaler(x target, node *ast.Node) (bool, error) { } func (d *decoder) unmarshalValue(x target, node *ast.Node) error { + v := x.get() + if v.Kind() == reflect.Ptr { + if !v.Elem().IsValid() { + err := x.set(reflect.New(v.Type().Elem())) + if err != nil { + return err + } + v = x.get() + } + return d.unmarshalValue(valueTarget(v.Elem()), node) + } + ok, err := tryTextUnmarshaler(x, node) if ok { return err From ad538d97c951463bd94eb233c88fa6adb34ab1f5 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Wed, 24 Mar 2021 21:06:38 -0400 Subject: [PATCH 110/228] Delete reflectbuild --- internal/reflectbuild/reflectbuild.go | 837 --------------------- internal/reflectbuild/reflectbuild_test.go | 205 ----- 2 files changed, 1042 deletions(-) delete mode 100644 internal/reflectbuild/reflectbuild.go delete mode 100644 internal/reflectbuild/reflectbuild_test.go diff --git a/internal/reflectbuild/reflectbuild.go b/internal/reflectbuild/reflectbuild.go deleted file mode 100644 index 7bc88259..00000000 --- a/internal/reflectbuild/reflectbuild.go +++ /dev/null @@ -1,837 +0,0 @@ -// reflectbuild is a package that provides utility functions to build Go -// objects using reflection. -package reflectbuild - -import ( - "fmt" - "math" - "reflect" - "strings" -) - -// fieldGetters are functions that given a struct return a specific field -// (likely captured in their scope) -type fieldGetter func(s reflect.Value) reflect.Value - -// collection of fieldGetters for a given struct type -type structFieldGetters map[string]fieldGetter - -type target interface { - get() reflect.Value - set(value reflect.Value) error - - fmt.Stringer -} - -type valueTarget reflect.Value - -func (v valueTarget) get() reflect.Value { - return reflect.Value(v) -} - -func (v valueTarget) set(value reflect.Value) error { - rv := reflect.Value(v) - - // value is guaranteed to be a pointer - if value.Kind() != reflect.Ptr { - panic(fmt.Sprintf("set() should receive a pointer, not a '%s'", value.Kind())) - } - - if rv.Kind() != reflect.Ptr { - // TODO: check value is nil? - value = value.Elem() - } - - targetType := rv.Type() - value, err := convert(targetType, value) - if err != nil { - return err - } - - rv.Set(value) - return nil -} - -func (v valueTarget) String() string { - return fmt.Sprintf("valueTarget: '%s' (%s)", reflect.Value(v), reflect.Value(v).Type()) -} - -type mapTarget struct { - index reflect.Value - m reflect.Value -} - -func (v mapTarget) get() reflect.Value { - return v.m.MapIndex(v.index) -} - -func (v mapTarget) set(value reflect.Value) error { - // value is guaranteed to be a pointer - - if v.m.Type().Elem().Kind() != reflect.Ptr { - // TODO: check value is nil? - value = value.Elem() - } - - targetType := v.m.Type().Elem() - value, err := convert(targetType, value) - if err != nil { - return err - } - - v.m.SetMapIndex(v.index, value) - return nil -} - -func (v mapTarget) String() string { - return fmt.Sprintf("mapTarget: '%s'[%s]", v.m, v.index) -} - -// Builder wraps a value and provides method to modify its structure. -// It is a stateful object that keeps a cursor of what part of the object is -// being modified. -// Create a Builder with NewBuilder. -type Builder struct { - root reflect.Value - // Root is always a pointer to a non-nil value. - // Cursor is the top of the stack. - stack []target - // Struct field tag to use to retrieve name. - nameTag string - // Cache of functions to access specific fields. - fieldGettersCache map[reflect.Type]structFieldGetters -} - -func copyAndAppend(s []int, i int) []int { - ns := make([]int, len(s)+1) - copy(ns, s) - ns[len(ns)-1] = i - return ns -} - -func (b *Builder) getOrGenerateFieldGettersRecursive(m structFieldGetters, idx []int, s reflect.Type) { - for i := 0; i < s.NumField(); i++ { - f := s.Field(i) - if f.PkgPath != "" { - // only consider exported fields - continue - } - if f.Anonymous { - b.getOrGenerateFieldGettersRecursive(m, copyAndAppend(idx, i), f.Type) - } else { - fieldName, ok := f.Tag.Lookup(b.nameTag) - if !ok { - fieldName = f.Name - } - - if len(idx) == 0 { - m[fieldName] = makeFieldGetterByIndex(i) - } else { - m[fieldName] = makeFieldGetterByIndexes(copyAndAppend(idx, i)) - } - } - } - - if b.fieldGettersCache == nil { - b.fieldGettersCache = make(map[reflect.Type]structFieldGetters, 1) - } - - b.fieldGettersCache[s] = m -} - -func (b *Builder) getOrGenerateFieldGetters(s reflect.Type) structFieldGetters { - if s.Kind() != reflect.Struct { - panic("generateFieldGetters can only be called on a struct") - } - m, ok := b.fieldGettersCache[s] - if ok { - return m - } - - m = make(structFieldGetters, s.NumField()) - b.getOrGenerateFieldGettersRecursive(m, nil, s) - b.fieldGettersCache[s] = m - return m -} - -func makeFieldGetterByIndex(idx int) fieldGetter { - return func(s reflect.Value) reflect.Value { - return s.Field(idx) - } -} - -func makeFieldGetterByIndexes(idx []int) fieldGetter { - return func(s reflect.Value) reflect.Value { - return s.FieldByIndex(idx) - } -} - -func (b *Builder) fieldGetter(t reflect.Type, s string) (fieldGetter, error) { - m := b.getOrGenerateFieldGetters(t) - g, ok := m[s] - if !ok { - return nil, fmt.Errorf("field '%s' not accessible on '%s'", s, t) - } - return g, nil -} - -// NewBuilder creates a Builder to construct v. -// If v is nil or not a pointer, an error will be returned. -func NewBuilder(tag string, v interface{}) (Builder, error) { - if v == nil { - return Builder{}, fmt.Errorf("cannot build a nil value") - } - - rv := reflect.ValueOf(v) - if rv.Type().Kind() != reflect.Ptr { - return Builder{}, fmt.Errorf("cannot build a %s: need a pointer", rv.Type().Kind()) - } - - if rv.IsNil() { - return Builder{}, fmt.Errorf("cannot build a nil value") - } - - return Builder{ - root: rv.Elem(), - stack: []target{valueTarget(rv.Elem())}, - nameTag: tag, - }, nil -} - -func (b *Builder) top() target { - return b.stack[len(b.stack)-1] -} - -func (b *Builder) duplicate() { - b.stack = append(b.stack, b.stack[len(b.stack)-1]) - // TODO: remove me. just here to make sure the method is included in the - // binary for debug - b.Dump() -} - -func (b *Builder) pop() { - b.stack = b.stack[:len(b.stack)-1] -} - -func (b *Builder) len() int { - return len(b.stack) -} - -func (b *Builder) Dump() string { - str := strings.Builder{} - str.WriteByte('[') - - for i, x := range b.stack { - if i > 0 { - str.WriteString(" | ") - } - fmt.Fprintf(&str, "%s", x) - } - - str.WriteByte(']') - return str.String() -} - -func (b *Builder) replace(v target) { - b.stack[len(b.stack)-1] = v -} - -var mapStringInterfaceType = reflect.TypeOf(map[string]interface{}{}) - -// DigField pushes the cursor into a field of the current struct. -// Dereferences all pointers found along the way. -// Errors if the current value is not a struct, or the field does not exist. -func (b *Builder) DigField(s string) error { - t := b.top() - v := t.get() - - for v.Kind() == reflect.Interface || v.Kind() == reflect.Ptr { - if v.IsNil() { - if v.Kind() == reflect.Ptr { - thing := reflect.New(v.Type().Elem()) - v.Set(thing) - } else { - v.Set(reflect.MakeMap(mapStringInterfaceType)) - } - } - v = v.Elem() - } - - if v.Kind() == reflect.Map { - // if map is nil, allocate it - if v.IsNil() { - v.Set(reflect.MakeMap(v.Type())) - } - - // TODO: handle error when map is not indexed by strings - key := reflect.ValueOf(s) - - key, err := convert(v.Type().Key(), key) - if err != nil { - return err - } - - b.replace(mapTarget{ - index: key, - m: v, - }) - } else { - err := checkKind(v.Type(), reflect.Struct) - if err != nil { - return err - } - - g, err := b.fieldGetter(v.Type(), s) - if err != nil { - return FieldNotFoundError{FieldName: s, Struct: v} - } - - f := g(v) - if !f.IsValid() { - return FieldNotFoundError{FieldName: s, Struct: v} - } - - b.replace(valueTarget(f)) - } - - return nil -} - -// Save stores a copy of the current cursor position. -// It can be restored using Back(). -// Save points are stored as a stack. -func (b *Builder) Save() { - b.duplicate() -} - -// Reset brings the cursor back to the root object. -func (b *Builder) Reset() { - b.stack = b.stack[:1] - b.stack[0] = valueTarget(b.root) -} - -// Load is the opposite of Save. It discards the current cursor and loads the -// last saved cursor. -// Panics if no cursor has been saved. -func (b *Builder) Load() { - if b.len() < 2 { - panic(fmt.Errorf("tried to Back() when cursor was already at root")) - } - b.pop() -} - -// Cursor returns the value pointed at by the cursor. -func (b *Builder) Cursor() reflect.Value { - return b.top().get() -} - -func (b *Builder) IsSlice() bool { - return b.top().get().Kind() == reflect.Slice -} - -func (b *Builder) IsSliceOrPtr() bool { - t := b.top().get() - if t.Kind() == reflect.Slice { - return true - } - - if t.Kind() == reflect.Ptr && t.Type().Elem().Kind() == reflect.Slice { - return true - } - - if t.Kind() == reflect.Interface && !t.IsNil() && t.Elem().Type().Kind() == reflect.Slice { - return true - } - - return false -} - -// Last moves the cursor to the last value of the current value. -// For a slice or an array, it is the last element they contain, if any. -// For anything else, it's a no-op. -func (b *Builder) Last() { - switch b.Cursor().Kind() { - case reflect.Slice, reflect.Array: - length := b.Cursor().Len() - if length > 0 { - x := b.Cursor().Index(length - 1) - b.replace(valueTarget(x)) // TODO: create a "sliceTarget" ? - } - } -} - -// SliceLastOrCreate moves the cursor to the last element of the slice if any. -// Otherwise creates a new element in that slice and moves to it. -func (b *Builder) SliceLastOrCreate() error { - t := b.top() - v := t.get() - err := checkKind(v.Type(), reflect.Slice) - if err != nil { - return err - } - - if v.Len() == 0 { - return b.SliceNewElem() - } - b.Last() - return nil -} - -// SliceNewElem operates on a slice. It creates a new object (of type contained -// by the slice), append it to the slice, and moves the cursor to the new -// object. -func (b *Builder) SliceNewElem() error { - t := b.top() - v := t.get() - - if v.Kind() == reflect.Ptr { - // if the pointer is nil we need to allocate the slice - if v.IsNil() { - x := reflect.New(v.Type().Elem()) - v.Set(x) - } - // target the slice itself - v = v.Elem() - } - - err := checkKind(v.Type(), reflect.Slice) - if err != nil { - return err - } - elem := reflect.New(v.Type().Elem()) - newSlice := reflect.Append(v, elem.Elem()) - v.Set(newSlice) - b.replace(valueTarget(v.Index(v.Len() - 1))) // TODO: "sliceTarget"? - return nil -} - -func assertPtr(v reflect.Value) { - if v.Kind() != reflect.Ptr { - panic(fmt.Sprintf("value '%s' should be a ptr, not '%s'", v, v.Kind())) - } -} - -func (b *Builder) SliceAppend(value reflect.Value) error { - assertPtr(value) - - t := b.top() - v := t.get() - - // pointer to a slice - if v.Kind() == reflect.Ptr { - // if the pointer is nil we need to allocate the slice - if v.IsNil() { - x := reflect.New(v.Type().Elem()) - v.Set(x) - } - // target the slice itself - v = v.Elem() - } - - err := checkKind(v.Type(), reflect.Slice) - if err != nil { - return err - } - - if v.Type().Elem().Kind() == reflect.Ptr { - // if it is a slice of pointers, we can just append - } else { - // otherwise we need to reference the value - value = value.Elem() - } - - value, err = convert(v.Type().Elem(), value) - if err != nil { - return err - } - - newSlice := reflect.Append(v, value) - v.Set(newSlice) - b.replace(valueTarget(v.Index(v.Len() - 1))) // TODO: "sliceTarget" ? - return nil -} - -// convert value so that it can be assigned to t. -// -// Conversion rules: -// -// * Pointers are de-referenced as needed. -// * Integer types are converted between each other as long as they don't -// overflow. -// * Float types are converted between each other as long as they don't -// overflow. -// -// TODO: this function acts as a switchboard. Runtime has enough information to -// generate per-type functions avoiding the double type switches. -func convert(t reflect.Type, value reflect.Value) (reflect.Value, error) { - if value.Type().AssignableTo(t) { - return value, nil - } - - returnPtr := false - if value.Kind() == reflect.Ptr { - if t.Kind() != reflect.Ptr { - return reflect.Value{}, fmt.Errorf("cannot convert pointer to non-pointer") - } - - value = value.Elem() - t = t.Elem() - returnPtr = true - } - - if t.Kind() == value.Kind() { - return value.Convert(t), nil - } - - var err error - switch t.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - value, err = convertInt(t, value) - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - value, err = convertUint(t, value) - case reflect.Float32, reflect.Float64: - value, err = convertFloat(t, value) - default: - err = fmt.Errorf("not converting a %s into a %s", value.Kind(), t.Kind()) - } - - if err != nil { - return value, err - } - - result := reflect.New(t) // TODO: remove alloc - result.Elem().Set(value.Convert(t)) - if returnPtr { - return result, nil - } - return result.Elem(), nil -} - -type IntegerOverflowError struct { - value int64 - min int64 - max int64 - kind reflect.Kind -} - -func (e IntegerOverflowError) Error() string { - return fmt.Sprintf("integer overflow: cannot store %d in %s [%d, %d]", e.value, e.kind, e.min, e.max) -} - -const maxInt = int64(^uint(0) >> 1) -const minInt = -maxInt - 1 - -func convertInt(t reflect.Type, value reflect.Value) (reflect.Value, error) { - switch value.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - x := value.Int() - - switch t.Kind() { - case reflect.Int: - if x > maxInt || x < minInt { - return value, IntegerOverflowError{ - value: x, - min: minInt, - max: maxInt, - kind: t.Kind(), - } - } - case reflect.Int8: - if x > math.MaxInt8 || x < math.MinInt8 { - return value, IntegerOverflowError{ - value: x, - min: math.MinInt8, - max: math.MaxInt8, - kind: t.Kind(), - } - } - case reflect.Int16: - if x > math.MaxInt16 || x < math.MinInt16 { - return value, IntegerOverflowError{ - value: x, - min: math.MinInt16, - max: math.MaxInt16, - kind: t.Kind(), - } - } - case reflect.Int32: - if x > math.MaxInt32 || x < math.MinInt32 { - return value, IntegerOverflowError{ - value: x, - min: math.MinInt32, - max: math.MaxInt32, - kind: t.Kind(), - } - } - case reflect.Int64: - if x > math.MaxInt64 || x < math.MinInt64 { - return value, IntegerOverflowError{ - value: x, - min: math.MinInt64, - max: math.MaxInt64, - kind: t.Kind(), - } - } - } - - return value.Convert(t), nil - default: - return value, fmt.Errorf("cannot convert %s to integer (%s)", value.Kind(), t.Kind()) - } -} - -type UnsignedIntegerOverflowError struct { - value uint64 - max uint64 - kind reflect.Kind -} - -func (e UnsignedIntegerOverflowError) Error() string { - return fmt.Sprintf("unsigned integer overflow: cannot store %d in %s [max %d]", e.value, e.kind, e.max) -} - -func convertUint(t reflect.Type, value reflect.Value) (reflect.Value, error) { - switch value.Kind() { - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - err := convertUintOverflowCheck(t.Kind(), value.Uint()) - if err != nil { - return value, err - } - return value.Convert(t), nil // reflect.TypeOf(int64(0)) - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - signed := value.Int() - if signed < 0 { - return value, fmt.Errorf("cannot store negative integer '%d' into %s", signed, t.Kind()) - } - - x := uint64(signed) - err := convertUintOverflowCheck(t.Kind(), x) - if err != nil { - return value, err - } - - return value.Convert(t), nil - default: - return value, fmt.Errorf("cannot convert %s to unsigned integer (%s)", value.Kind(), t.Kind()) - } -} - -const maxUint = uint64(^uint(0)) - -func convertUintOverflowCheck(t reflect.Kind, x uint64) error { - switch t { - case reflect.Uint: - if x > maxUint { - return UnsignedIntegerOverflowError{ - value: x, - max: maxUint, - kind: t, - } - } - case reflect.Uint8: - if x > math.MaxUint8 { - return UnsignedIntegerOverflowError{ - value: x, - max: math.MaxUint8, - kind: t, - } - } - case reflect.Uint16: - if x > math.MaxUint16 { - return UnsignedIntegerOverflowError{ - value: x, - max: math.MaxUint16, - kind: t, - } - } - case reflect.Uint32: - if x > math.MaxUint32 { - return UnsignedIntegerOverflowError{ - value: x, - max: math.MaxUint32, - kind: t, - } - } - case reflect.Uint64: - if x > math.MaxUint64 { - return UnsignedIntegerOverflowError{ - value: x, - max: math.MaxUint64, - kind: t, - } - } - } - return nil -} - -func convertFloat(t reflect.Type, value reflect.Value) (reflect.Value, error) { - switch value.Kind() { - case reflect.Float32, reflect.Float64: - if t.Kind() == reflect.Float32 { - f := value.Float() - if f > math.MaxFloat32 { - return value, fmt.Errorf("float overflow: %f does not fit in %s [max %f]", f, t, math.MaxFloat32) - } - } - return value.Convert(t), nil - default: - return value, fmt.Errorf("cannot convert %s to integer (%s)", value.Kind(), t.Kind()) - } -} - -// Set the value at the cursor to the given string. -// Errors if a string cannot be assigned to the current value. -func (b *Builder) SetString(s string) error { - t := b.top() - v := t.get() - - if v.Kind() == reflect.Ptr { - v.Set(reflect.ValueOf(&s)) - return nil - } - return t.set(reflect.ValueOf(&s)) -} - -// Set the value at the cursor to the given boolean. -// Errors if a boolean cannot be assigned to the current value. -func (b *Builder) SetBool(value bool) error { - t := b.top() - v := t.get() - - err := checkKind(v.Type(), reflect.Bool) - if err != nil { - return err - } - - v.SetBool(value) - return nil -} - -func (b *Builder) SetFloat(n float64) error { - t := b.top() - v := t.get() - - err := checkKindFloat(v.Type()) - if err != nil { - return err - } - - v.SetFloat(n) - return nil -} - -func (b *Builder) Set(v reflect.Value) error { - assertPtr(v) - t := b.top() - return t.set(v) -} - -// EnsureSlice makes sure that the cursor points to a non-nil slice. -func (b *Builder) EnsureSlice() error { - t := b.top() - v := t.get() - - if v.Kind() == reflect.Ptr { - if v.IsNil() { - v.Set(reflect.New(v.Type().Elem())) - } - v = v.Elem() - } - - if v.Kind() != reflect.Slice { - return IncorrectKindError{ - Reason: "EnsureSlice", - Actual: v.Kind(), - Expected: []reflect.Kind{reflect.Slice}, - } - } - - if v.IsNil() { - v.Set(reflect.MakeSlice(v.Type(), 0, 0)) - } - - return nil -} - -// EnsureStructOrMap makes sure that the cursor points to an initialized -// struct or map. -func (b *Builder) EnsureStructOrMap() error { - t := b.top() - v := t.get() - - switch v.Kind() { - case reflect.Struct: - case reflect.Map: - if v.IsNil() { - x := reflect.New(v.Type()) - x.Elem().Set(reflect.MakeMap(v.Type())) - return t.set(x) - } - case reflect.Interface: - // TODO: ? - default: - return IncorrectKindError{ - Reason: "EnsureStructOrMap", - Actual: v.Kind(), - Expected: []reflect.Kind{reflect.Struct, reflect.Map}, - } - } - return nil -} - -func checkKindFloat(rt reflect.Type) error { - switch rt.Kind() { - case reflect.Float32, reflect.Float64: - return nil - } - - return IncorrectKindError{ - Reason: "CheckKindFloat", - Actual: rt.Kind(), - Expected: []reflect.Kind{reflect.Float64}, - } -} - -func checkKind(rt reflect.Type, expected reflect.Kind) error { - if rt.Kind() != expected { - return IncorrectKindError{ - Reason: "CheckKind", - Actual: rt.Kind(), - Expected: []reflect.Kind{expected}, - } - } - return nil -} - -type IncorrectKindError struct { - Reason string - Actual reflect.Kind - Expected []reflect.Kind -} - -func (e IncorrectKindError) Error() string { - b := strings.Builder{} - b.WriteString("incorrect kind: ") - - if len(e.Expected) < 2 { - b.WriteString(fmt.Sprintf("expected '%s', got '%s'", e.Expected[0], e.Actual)) - } else { - b.WriteString(fmt.Sprintf("expected any of '%s', got '%s'", e.Expected, e.Actual)) - } - - if e.Reason != "" { - b.WriteString(": ") - b.WriteString(e.Reason) - } - - return b.String() -} - -type FieldNotFoundError struct { - Struct reflect.Value - FieldName string -} - -func (e FieldNotFoundError) Error() string { - return fmt.Sprintf("field not found: '%s' on '%s'", e.FieldName, e.Struct.Type()) -} diff --git a/internal/reflectbuild/reflectbuild_test.go b/internal/reflectbuild/reflectbuild_test.go deleted file mode 100644 index 692e0c23..00000000 --- a/internal/reflectbuild/reflectbuild_test.go +++ /dev/null @@ -1,205 +0,0 @@ -package reflectbuild_test - -import ( - "reflect" - "testing" - - "github.com/pelletier/go-toml/v2/internal/reflectbuild" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestNewBuilderSuccess(t *testing.T) { - x := struct{}{} - _, err := reflectbuild.NewBuilder("", &x) - assert.NoError(t, err) -} - -func TestNewBuilderNil(t *testing.T) { - _, err := reflectbuild.NewBuilder("", nil) - assert.Error(t, err) -} - -func TestNewBuilderNonPtr(t *testing.T) { - _, err := reflectbuild.NewBuilder("", struct{}{}) - assert.Error(t, err) -} - -func TestDigField(t *testing.T) { - x := struct { - Field string - }{} - b, err := reflectbuild.NewBuilder("", &x) - require.NoError(t, err) - assert.Error(t, b.DigField("oops")) - assert.NoError(t, b.DigField("Field")) - assert.Error(t, b.DigField("does not work on strings")) -} - -func TestBack(t *testing.T) { - x := struct { - A string - B string - }{} - b, err := reflectbuild.NewBuilder("", &x) - require.NoError(t, err) - b.Save() - assert.NoError(t, b.DigField("A")) - assert.NoError(t, b.SetString("A")) - b.Load() - b.Save() - assert.NoError(t, b.DigField("B")) - assert.NoError(t, b.SetString("B")) - assert.Equal(t, "A", x.A) - assert.Equal(t, "B", x.B) - b.Load() // back to root - assert.Panics(t, func() { - b.Load() // oops - }) -} - -func TestReset(t *testing.T) { - x := struct { - A []string - B string - }{} - b, err := reflectbuild.NewBuilder("", &x) - require.NoError(t, err) - require.NoError(t, b.DigField("A")) - require.NoError(t, b.SliceNewElem()) - require.NoError(t, b.SetString("hello")) - b.Reset() - require.NoError(t, b.DigField("B")) - require.NoError(t, b.SetString("world")) - - assert.Equal(t, []string{"hello"}, x.A) - assert.Equal(t, "world", x.B) -} - -func TestSetString(t *testing.T) { - x := struct { - Field string - }{} - b, err := reflectbuild.NewBuilder("", &x) - require.NoError(t, err) - assert.Error(t, b.SetString("oops")) - require.NoError(t, b.DigField("Field")) - require.NoError(t, b.SetString("hello!")) - assert.Equal(t, "hello!", x.Field) -} - -func TestSliceNewElem(t *testing.T) { - x := struct { - Field []string - }{} - b, err := reflectbuild.NewBuilder("", &x) - require.NoError(t, err) - require.NoError(t, b.DigField("Field")) - b.Save() - - require.NoError(t, b.SliceNewElem()) - require.NoError(t, b.SetString("Val1")) - b.Load() - require.NoError(t, b.SliceNewElem()) - require.NoError(t, b.SetString("Val2")) - - require.Error(t, b.SliceNewElem()) - - assert.Equal(t, []string{"Val1", "Val2"}, x.Field) -} - -func TestSliceNewElemNested(t *testing.T) { - x := struct { - Field [][]string - }{} - b, err := reflectbuild.NewBuilder("", &x) - require.NoError(t, err) - require.NoError(t, b.DigField("Field")) - - b.Save() - - require.NoError(t, b.SliceNewElem()) - require.NoError(t, b.SliceNewElem()) - require.NoError(t, b.SetString("Val1.1")) - b.Load() - b.Save() - - require.NoError(t, b.SliceNewElem()) - b.Save() - require.NoError(t, b.SliceNewElem()) - require.NoError(t, b.SetString("Val2.1")) - b.Load() - require.NoError(t, b.SliceNewElem()) - require.NoError(t, b.SetString("Val2.2")) - b.Load() - require.NoError(t, b.SliceNewElem()) - - assert.Equal(t, [][]string{{"Val1.1"}, {"Val2.1", "Val2.2"}, nil}, x.Field) -} - -func TestIncorrectKindError(t *testing.T) { - err := reflectbuild.IncorrectKindError{ - Actual: reflect.String, - Expected: []reflect.Kind{reflect.Struct}, - } - assert.NotEmpty(t, err.Error()) -} - -func TestFieldNotFoundError(t *testing.T) { - err := reflectbuild.FieldNotFoundError{ - Struct: reflect.ValueOf(struct { - Blah string - }{}), - FieldName: "Foo", - } - assert.NotEmpty(t, err.Error()) -} - -func TestCursor(t *testing.T) { - x := struct { - Field string - }{} - b, err := reflectbuild.NewBuilder("", &x) - require.NoError(t, err) - assert.Equal(t, b.Cursor().Kind(), reflect.Struct) - require.NoError(t, b.DigField("Field")) - assert.Equal(t, b.Cursor().Kind(), reflect.String) -} - -func TestStringPtr(t *testing.T) { - x := struct { - Field *string - }{} - b, err := reflectbuild.NewBuilder("", &x) - require.NoError(t, err) - assert.Equal(t, b.Cursor().Kind(), reflect.Struct) - require.NoError(t, b.DigField("Field")) - assert.NoError(t, b.SetString("A")) - assert.Equal(t, "A", *x.Field) -} - -func TestAppendSlicePtr(t *testing.T) { - x := struct { - Field *[]string - }{} - b, err := reflectbuild.NewBuilder("", &x) - require.NoError(t, err) - assert.Equal(t, b.Cursor().Kind(), reflect.Struct) - require.NoError(t, b.DigField("Field")) - v := "A" - assert.NoError(t, b.SliceAppend(reflect.ValueOf(&v))) - assert.Equal(t, []string{"A"}, *x.Field) -} - -func TestAppendPtrSlicePtr(t *testing.T) { - x := struct { - Field *[]*string - }{} - b, err := reflectbuild.NewBuilder("", &x) - require.NoError(t, err) - assert.Equal(t, b.Cursor().Kind(), reflect.Struct) - require.NoError(t, b.DigField("Field")) - v := "A" - assert.NoError(t, b.SliceAppend(reflect.ValueOf(&v))) - assert.Equal(t, "A", *(*x.Field)[0]) -} From 8a8d1233bb6b9afad122a2c66a54be3826642458 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Wed, 24 Mar 2021 22:15:12 -0400 Subject: [PATCH 111/228] First benchmark! ~/s/g/p/g/benchmark$ go test -bench=. goos: linux goarch: amd64 pkg: github.com/pelletier/go-toml/v2/benchmark cpu: Intel(R) Core(TM) i7-7700 CPU @ 3.60GHz BenchmarkUnmarshalSimple/v2-8 1607115 742.0 ns/op BenchmarkUnmarshalSimple/v1-8 307977 3915 ns/op BenchmarkUnmarshalSimple/bs-8 516754 2330 ns/op BenchmarkReferenceFile/v2-8 9604 129158 ns/op 111422 B/op 1381 allocs/op BenchmarkReferenceFile/v1-8 4521 263808 ns/op 130566 B/op 2649 allocs/op BenchmarkReferenceFile/bs-8 4070 296271 ns/op 80784 B/op 1729 allocs/op PASS ok github.com/pelletier/go-toml/v2/benchmark 8.139s --- benchmark/benchmark.toml | 244 ++++++++++++++++++++++++++++++++++++ benchmark/benchmark_test.go | 178 ++++++++++++++++++++++++++ benchmark/go.mod | 15 +++ benchmark/go.sum | 16 +++ go.mod | 2 +- scanner.go | 2 +- 6 files changed, 455 insertions(+), 2 deletions(-) create mode 100644 benchmark/benchmark.toml create mode 100644 benchmark/benchmark_test.go create mode 100644 benchmark/go.mod create mode 100644 benchmark/go.sum diff --git a/benchmark/benchmark.toml b/benchmark/benchmark.toml new file mode 100644 index 00000000..dfd77e09 --- /dev/null +++ b/benchmark/benchmark.toml @@ -0,0 +1,244 @@ +################################################################################ +## Comment + +# Speak your mind with the hash symbol. They go from the symbol to the end of +# the line. + + +################################################################################ +## Table + +# Tables (also known as hash tables or dictionaries) are collections of +# key/value pairs. They appear in square brackets on a line by themselves. + +[table] + +key = "value" # Yeah, you can do this. + +# Nested tables are denoted by table names with dots in them. Name your tables +# whatever crap you please, just don't use #, ., [ or ]. + +[table.subtable] + +key = "another value" + +# You don't need to specify all the super-tables if you don't want to. TOML +# knows how to do it for you. + +# [x] you +# [x.y] don't +# [x.y.z] need these +[x.y.z.w] # for this to work + + +################################################################################ +## Inline Table + +# Inline tables provide a more compact syntax for expressing tables. They are +# especially useful for grouped data that can otherwise quickly become verbose. +# Inline tables are enclosed in curly braces `{` and `}`. No newlines are +# allowed between the curly braces unless they are valid within a value. + +[table.inline] + +name = { first = "Tom", last = "Preston-Werner" } +point = { x = 1, y = 2 } + + +################################################################################ +## String + +# There are four ways to express strings: basic, multi-line basic, literal, and +# multi-line literal. All strings must contain only valid UTF-8 characters. + +[string.basic] + +basic = "I'm a string. \"You can quote me\". Name\tJos\u00E9\nLocation\tSF." + +[string.multiline] + +# The following strings are byte-for-byte equivalent: +key1 = "One\nTwo" +key2 = """One\nTwo""" +key3 = """ +One +Two""" + +[string.multiline.continued] + +# The following strings are byte-for-byte equivalent: +key1 = "The quick brown fox jumps over the lazy dog." + +key2 = """ +The quick brown \ + + + fox jumps over \ + the lazy dog.""" + +key3 = """\ + The quick brown \ + fox jumps over \ + the lazy dog.\ + """ + +[string.literal] + +# What you see is what you get. +winpath = 'C:\Users\nodejs\templates' +winpath2 = '\\ServerX\admin$\system32\' +quoted = 'Tom "Dubs" Preston-Werner' +regex = '<\i\c*\s*>' + + +[string.literal.multiline] + +regex2 = '''I [dw]on't need \d{2} apples''' +lines = ''' +The first newline is +trimmed in raw strings. + All other whitespace + is preserved. +''' + + +################################################################################ +## Integer + +# Integers are whole numbers. Positive numbers may be prefixed with a plus sign. +# Negative numbers are prefixed with a minus sign. + +[integer] + +key1 = +99 +key2 = 42 +key3 = 0 +key4 = -17 + +[integer.underscores] + +# For large numbers, you may use underscores to enhance readability. Each +# underscore must be surrounded by at least one digit. +key1 = 1_000 +key2 = 5_349_221 +key3 = 1_2_3_4_5 # valid but inadvisable + + +################################################################################ +## Float + +# A float consists of an integer part (which may be prefixed with a plus or +# minus sign) followed by a fractional part and/or an exponent part. + +[float.fractional] + +key1 = +1.0 +key2 = 3.1415 +key3 = -0.01 + +[float.exponent] + +key1 = 5e+22 +key2 = 1e6 +key3 = -2E-2 + +[float.both] + +key = 6.626e-34 + +[float.underscores] + +key1 = 9_224_617.445_991_228_313 +key2 = 1e1_00 + + +################################################################################ +## Boolean + +# Booleans are just the tokens you're used to. Always lowercase. + +[boolean] + +True = true +False = false + + +################################################################################ +## Datetime + +# Datetimes are RFC 3339 dates. + +[datetime] + +key1 = 1979-05-27T07:32:00Z +key2 = 1979-05-27T00:32:00-07:00 +key3 = 1979-05-27T00:32:00.999999-07:00 + + +################################################################################ +## Array + +# Arrays are square brackets with other primitives inside. Whitespace is +# ignored. Elements are separated by commas. Data types may not be mixed. + +[array] + +key1 = [ 1, 2, 3 ] +key2 = [ "red", "yellow", "green" ] +key3 = [ [ 1, 2 ], [3, 4, 5] ] +#key4 = [ [ 1, 2 ], ["a", "b", "c"] ] # this is ok + +# Arrays can also be multiline. So in addition to ignoring whitespace, arrays +# also ignore newlines between the brackets. Terminating commas are ok before +# the closing bracket. + +key5 = [ + 1, 2, 3 +] +key6 = [ + 1, + 2, # this is ok +] + + +################################################################################ +## Array of Tables + +# These can be expressed by using a table name in double brackets. Each table +# with the same double bracketed name will be an element in the array. The +# tables are inserted in the order encountered. + +[[products]] + +name = "Hammer" +sku = 738594937 + +[[products]] + +[[products]] + +name = "Nail" +sku = 284758393 +color = "gray" + + +# You can create nested arrays of tables as well. + +[[fruit]] + name = "apple" + + [fruit.physical] + color = "red" + shape = "round" + + [[fruit.variety]] + name = "red delicious" + + [[fruit.variety]] + name = "granny smith" + +[[fruit]] + name = "banana" + + [[fruit.variety]] + name = "plantain" diff --git a/benchmark/benchmark_test.go b/benchmark/benchmark_test.go new file mode 100644 index 00000000..5d3d2ed3 --- /dev/null +++ b/benchmark/benchmark_test.go @@ -0,0 +1,178 @@ +package benchmark_test + +import ( + "io/ioutil" + "testing" + "time" + + tomlbs "github.com/BurntSushi/toml" + tomlv1 "github.com/pelletier/go-toml-v1" + "github.com/pelletier/go-toml/v2" + "github.com/stretchr/testify/require" +) + +type runner struct { + name string + unmarshal func([]byte, interface{}) error +} + +var runners = []runner{ + {"v2", toml.Unmarshal}, + {"v1", tomlv1.Unmarshal}, + {"bs", tomlbs.Unmarshal}, +} + +func bench(b *testing.B, f func(r runner, b *testing.B)) { + for _, r := range runners { + b.Run(r.name, func(b *testing.B) { + f(r, b) + }) + } +} + +func BenchmarkUnmarshalSimple(b *testing.B) { + bench(b, func(r runner, b *testing.B) { + d := struct { + A string + }{} + doc := []byte(`A = "hello"`) + for i := 0; i < b.N; i++ { + err := r.unmarshal(doc, &d) + if err != nil { + panic(err) + } + } + }) +} + +type benchmarkDoc struct { + Table struct { + Key string + Subtable struct { + Key string + } + Inline struct { + Name struct { + First string + Last string + } + Point struct { + X int64 + U int64 + } + } + } + String struct { + Basic struct { + Basic string + } + Multiline struct { + Key1 string + Key2 string + Key3 string + Continued struct { + Key1 string + Key2 string + Key3 string + } + } + Literal struct { + Winpath string + Winpath2 string + Quoted string + Regex string + Multiline struct { + Regex2 string + Lines string + } + } + } + Integer struct { + Key1 int64 + Key2 int64 + Key3 int64 + Key4 int64 + Underscores struct { + Key1 int64 + Key2 int64 + Key3 int64 + } + } + Float struct { + Fractional struct { + Key1 float64 + Key2 float64 + Key3 float64 + } + Exponent struct { + Key1 float64 + Key2 float64 + Key3 float64 + } + Both struct { + Key float64 + } + Underscores struct { + Key1 float64 + Key2 float64 + } + } + Boolean struct { + True bool + False bool + } + Datetime struct { + Key1 time.Time + Key2 time.Time + Key3 time.Time + } + Array struct { + Key1 []int64 + Key2 []string + Key3 [][]int64 + // TODO: Key4 not supported by go-toml's Unmarshal + Key5 []int64 + Key6 []int64 + } + Products []struct { + Name string + Sku int64 + Color string + } + Fruit []struct { + Name string + Physical struct { + Color string + Shape string + Variety []struct { + Name string + } + } + } +} + +func BenchmarkReferenceFile(b *testing.B) { + bench(b, func(r runner, b *testing.B) { + bytes, err := ioutil.ReadFile("benchmark.toml") + if err != nil { + b.Fatal(err) + } + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + d := benchmarkDoc{} + err := r.unmarshal(bytes, &d) + if err != nil { + panic(err) + } + } + }) +} + +func TestReferenceFile(t *testing.T) { + bytes, err := ioutil.ReadFile("benchmark.toml") + require.NoError(t, err) + d := benchmarkDoc{} + err = toml.Unmarshal(bytes, &d) + require.NoError(t, err) +} diff --git a/benchmark/go.mod b/benchmark/go.mod new file mode 100644 index 00000000..9e09e18c --- /dev/null +++ b/benchmark/go.mod @@ -0,0 +1,15 @@ +module github.com/pelletier/go-toml/v2/benchmark + +go 1.16 + +replace github.com/pelletier/go-toml/v2 => ../ + +replace github.com/pelletier/go-toml-v1 => /home/thomas/src/github.com/pelletier/go-toml-v1 + +require ( + github.com/BurntSushi/toml v0.3.1 + github.com/pelletier/go-toml v1.8.1 // indirect + github.com/pelletier/go-toml-v1 v0.0.0-00010101000000-000000000000 + github.com/pelletier/go-toml/v2 v2.0.0-00010101000000-000000000000 + github.com/stretchr/testify v1.7.0 +) diff --git a/benchmark/go.sum b/benchmark/go.sum new file mode 100644 index 00000000..bcc29496 --- /dev/null +++ b/benchmark/go.sum @@ -0,0 +1,16 @@ +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM= +github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/go.mod b/go.mod index 3825359e..6745f0a5 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,5 @@ module github.com/pelletier/go-toml/v2 -go 1.14 +go 1.15 require github.com/stretchr/testify v1.7.0 diff --git a/scanner.go b/scanner.go index 9ee67faf..ba857c62 100644 --- a/scanner.go +++ b/scanner.go @@ -65,7 +65,7 @@ func scanMultilineLiteralString(b []byte) ([]byte, []byte, error) { switch b[i] { case '\'': if scanFollowsMultilineLiteralStringDelimiter(b[i:]) { - return b[:i+3], b[:i+3], nil + return b[:i+3], b[i+3:], nil } } } From 1bae751a45273e9ddaa1550bce40e0a50c287dc5 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Thu, 25 Mar 2021 19:56:02 -0400 Subject: [PATCH 112/228] Linear array storage for AST --- internal/ast/ast.go | 261 ++++++++++++---------------------- internal/ast/builder.go | 58 ++++++++ internal/ast/kind.go | 69 +++++++++ parser.go | 308 +++++++++++++++++++++++----------------- parser_test.go | 184 +++++++++++++++++------- unmarshaler.go | 74 ++++++---- unmarshaler_test.go | 149 +++++++++---------- 7 files changed, 651 insertions(+), 452 deletions(-) create mode 100644 internal/ast/builder.go create mode 100644 internal/ast/kind.go diff --git a/internal/ast/ast.go b/internal/ast/ast.go index e927dd08..d8d18431 100644 --- a/internal/ast/ast.go +++ b/internal/ast/ast.go @@ -2,182 +2,103 @@ package ast import ( "fmt" - "strings" ) -type Kind int - -const ( - // meta - Invalid Kind = iota - Comment - Key - - // top level structures - Table - ArrayTable - KeyValue - - // containers values - Array - InlineTable - - // values - String - Bool - Float - Integer - LocalDate - LocalDateTime - DateTime - Time -) - -func (k Kind) String() string { - switch k { - case Invalid: - return "Invalid" - case Comment: - return "Comment" - case Key: - return "Key" - case Table: - return "Table" - case ArrayTable: - return "ArrayTable" - case KeyValue: - return "KeyValue" - case Array: - return "Array" - case InlineTable: - return "InlineTable" - case String: - return "String" - case Bool: - return "Bool" - case Float: - return "Float" - case Integer: - return "Integer" - case LocalDate: - return "LocalDate" - case LocalDateTime: - return "LocalDateTime" - case DateTime: - return "DateTime" - case Time: - return "Time" - } - panic(fmt.Errorf("Kind.String() not implemented for '%d'", k)) +// Iterator starts uninitialized, you need to call Next() first. +// +// For example: +// +// it := n.Children() +// for it.Next() { +// it.Node() +// } +type Iterator interface { + // Next moves the iterator forward and returns true if points to a node, false + // otherwise. + Next() bool + // Node returns a copy of the node pointed at by the iterator. + Node() Node } -type Root []Node - -// Dot returns a dot representation of the AST for debugging. -func (r Root) Sdot() string { - type edge struct { - from int - childIdx int - to int - } - - var nodes []Node - var edges []edge // indexes into nodes - - nodes = append(nodes, Node{ - Kind: Invalid, - Data: []byte(`ROOT`), - Children: r, - }) - - var processNode func(int, int, *Node) - processNode = func(parentIdx int, childIdx int, node *Node) { - idx := len(nodes) - nodes = append(nodes, *node) - edges = append(edges, edge{ - from: parentIdx, - childIdx: childIdx, - to: idx, - }) - - for i, c := range node.Children { - processNode(idx, i, &c) - } - } +type chainIterator struct { + started bool + node Node +} - for i, n := range r { - processNode(0, i, &n) +func (c *chainIterator) Next() bool { + if !c.started { + c.started = true + } else if c.node.Valid() { + c.node = c.node.Next() } + return c.node.Valid() +} - var b strings.Builder - - b.WriteString("digraph tree {\n") - b.WriteString("\tnode [shape=record];\n") - - for i, node := range nodes { - label := "" - attrs := map[string]string{} - - if i == 0 { - var ports []string - for i := 0; i < len(node.Children); i++ { - ports = append(ports, fmt.Sprintf(" %d", i, i)) - } - joinedPorts := strings.Join(ports, "|") - label = fmt.Sprintf("{ROOT|{%s}}", joinedPorts) - } else { - fields := []string{node.Kind.String()} - if len(node.Data) > 0 { - fields = append(fields, string(node.Data)) - } - - var ports []string - for i := 0; i < len(node.Children); i++ { - ports = append(ports, fmt.Sprintf(" %d", i, i)) - } - joinedPorts := strings.Join(ports, "|") - - joinedFields := strings.Join(fields, "|") - label = fmt.Sprintf("{{%s}", joinedFields) - if len(ports) > 0 { - label += fmt.Sprintf("|{%s}", joinedPorts) - } - label += "}" - if node.Kind == Invalid { - attrs["style"] = "filled" - attrs["fillcolor"] = "red" - } - } - - _, _ = fmt.Fprintf(&b, "\tnode%d [label=\"%s\"", i, label) - for k, v := range attrs { - _, _ = fmt.Fprintf(&b, ", %s=\"%s\"", k, v) - } - _, _ = fmt.Fprintf(&b, "];\n") - } +func (c *chainIterator) Node() Node { + return c.node +} - b.WriteString("\n") +type Root struct { + nodes []Node +} - for _, e := range edges { - _, _ = fmt.Fprintf(&b, "\tnode%d:f%d -> node%d;\n", e.from, e.childIdx, e.to) +func (r *Root) Iterator() Iterator { + it := &chainIterator{} + if len(r.nodes) > 0 { + it.node = r.nodes[0] } + return it +} - b.WriteString("}") +func (r *Root) Reset() { + r.nodes = r.nodes[:0] +} - return b.String() +func (r *Root) at(idx int) Node { + // TODO: unsafe to point to the node directly + return r.nodes[idx] } +// Arrays have one child per element in the array. +// InlineTables have one child per key-value pair in the table. +// KeyValues have at least two children. The first one is the value. The +// rest make a potentially dotted key. +// Table and Array table have one child per element of the key they +// represent (same as KeyValue, but without the last node being the value). +// children []Node type Node struct { Kind Kind Data []byte // Raw bytes from the input - // Arrays have one child per element in the array. - // InlineTables have one child per key-value pair in the table. - // KeyValues have at least two children. The last one is the value. The - // rest make a potentially dotted key. - // Table and Array table have one child per element of the key they - // represent (same as KeyValue, but without the last node being the value). - Children []Node + // next idx (in the root array). 0 if last of the collection. + next int + // child idx (in the root array). 0 if no child. + child int + // pointer to the root array + root *Root +} + +// Next returns a copy of the next node, or an invalid Node if there is no +// next node. +func (n Node) Next() Node { + if n.next <= 0 { + return NoNode + } + return n.root.at(n.next) +} + +// Child returns a copy of the first child node of this node. Other children +// can be accessed calling Next on the first child. +// Returns an invalid Node if there is none. +func (n Node) Child() Node { + if n.child <= 0 { + return NoNode + } + return n.root.at(n.child) +} + +func (n Node) Valid() bool { + return n.Kind != Invalid } var NoNode = Node{} @@ -186,15 +107,16 @@ var NoNode = Node{} // otherwise. // They are guaranteed to be all be of the Kind Key. A simple key would return // just one element. -func (n *Node) Key() []Node { +func (n *Node) Key() Iterator { switch n.Kind { case KeyValue: - if len(n.Children) < 2 { - panic(fmt.Errorf("KeyValue should have at least two children, not %d", len(n.Children))) + value := n.Child() + if !value.Valid() { + panic(fmt.Errorf("KeyValue should have at least two children")) } - return n.Children[:len(n.Children)-1] + return &chainIterator{node: value.Next()} case Table, ArrayTable: - return n.Children + return &chainIterator{node: n.Child()} default: panic(fmt.Errorf("Key() is not supported on a %s", n.Kind)) } @@ -203,15 +125,16 @@ func (n *Node) Key() []Node { // Value returns a pointer to the value node of a KeyValue. // Guaranteed to be non-nil. // Panics if not called on a KeyValue node, or if the Children are malformed. -func (n *Node) Value() *Node { +func (n Node) Value() Node { assertKind(KeyValue, n) - if len(n.Children) < 2 { - panic(fmt.Errorf("KeyValue should have at least two children, not %d", len(n.Children))) - } - return &n.Children[len(n.Children)-1] + return n.Child() +} + +func (n Node) Children() Iterator { + return &chainIterator{node: n.Child()} } -func assertKind(k Kind, n *Node) { +func assertKind(k Kind, n Node) { if n.Kind != k { panic(fmt.Errorf("method was expecting a %s, not a %s", k, n.Kind)) } diff --git a/internal/ast/builder.go b/internal/ast/builder.go new file mode 100644 index 00000000..a6868cd9 --- /dev/null +++ b/internal/ast/builder.go @@ -0,0 +1,58 @@ +package ast + +type Builder struct { + nodes []Node + lastIdx int +} + +type Reference struct { + idx int + set bool +} + +func (r Reference) Valid() bool { + return r.set +} + +func (b *Builder) Finish() *Root { + r := &Root{ + nodes: b.nodes, + } + b.nodes = nil + + for i := range r.nodes { + r.nodes[i].root = r + } + + return r +} + +func (b *Builder) Push(n Node) Reference { + b.lastIdx = len(b.nodes) + b.nodes = append(b.nodes, n) + return Reference{ + idx: b.lastIdx, + set: true, + } +} + +func (b *Builder) PushAndChain(n Node) Reference { + newIdx := len(b.nodes) + b.nodes = append(b.nodes, n) + if b.lastIdx >= 0 { + b.nodes[b.lastIdx].next = newIdx + } + b.lastIdx = newIdx + return Reference{ + idx: b.lastIdx, + set: true, + } +} + +func (b *Builder) AttachChild(parent Reference, child Reference) { + b.nodes[parent.idx].child = child.idx +} + +func (b *Builder) Chain(from Reference, to Reference) { + b.nodes[from.idx].next = to.idx +} diff --git a/internal/ast/kind.go b/internal/ast/kind.go new file mode 100644 index 00000000..bcf6c91c --- /dev/null +++ b/internal/ast/kind.go @@ -0,0 +1,69 @@ +package ast + +import "fmt" + +type Kind int + +const ( + // meta + Invalid Kind = iota + Comment + Key + + // top level structures + Table + ArrayTable + KeyValue + + // containers values + Array + InlineTable + + // values + String + Bool + Float + Integer + LocalDate + LocalDateTime + DateTime + Time +) + +func (k Kind) String() string { + switch k { + case Invalid: + return "Invalid" + case Comment: + return "Comment" + case Key: + return "Key" + case Table: + return "Table" + case ArrayTable: + return "ArrayTable" + case KeyValue: + return "KeyValue" + case Array: + return "Array" + case InlineTable: + return "InlineTable" + case String: + return "String" + case Bool: + return "Bool" + case Float: + return "Float" + case Integer: + return "Integer" + case LocalDate: + return "LocalDate" + case LocalDateTime: + return "LocalDateTime" + case DateTime: + return "DateTime" + case Time: + return "Time" + } + panic(fmt.Errorf("Kind.String() not implemented for '%d'", k)) +} diff --git a/parser.go b/parser.go index ce45e55f..f2ca2dfc 100644 --- a/parser.go +++ b/parser.go @@ -10,11 +10,11 @@ import ( ) type parser struct { - tree ast.Root + builder ast.Builder } func (p *parser) parse(b []byte) error { - b, err := p.parseExpression(b) + last, b, err := p.parseExpression(b) if err != nil { return err } @@ -24,10 +24,15 @@ func (p *parser) parse(b []byte) error { return err } - b, err = p.parseExpression(b) + var next ast.Reference + next, b, err = p.parseExpression(b) if err != nil { return err } + if next.Valid() { + p.builder.Chain(last, next) + last = next + } } return nil } @@ -43,49 +48,48 @@ func (p *parser) parseNewline(b []byte) ([]byte, error) { return nil, fmt.Errorf("expected newline but got %#U", b[0]) } -func (p *parser) parseExpression(b []byte) ([]byte, error) { +func (p *parser) parseExpression(b []byte) (ast.Reference, []byte, error) { //expression = ws [ comment ] //expression =/ ws keyval ws [ comment ] //expression =/ ws table ws [ comment ] + var ref ast.Reference + b = p.parseWhitespace(b) if len(b) == 0 { - return b, nil + return ref, b, nil } if b[0] == '#' { _, rest, err := scanComment(b) - return rest, err + return ref, rest, err } if b[0] == '\n' || b[0] == '\r' { - return b, nil + return ref, b, nil } var err error - var node ast.Node if b[0] == '[' { - node, b, err = p.parseTable(b) + ref, b, err = p.parseTable(b) } else { - node, b, err = p.parseKeyval(b) + ref, b, err = p.parseKeyval(b) } if err != nil { - return nil, err + return ref, nil, err } - p.tree = append(p.tree, node) - b = p.parseWhitespace(b) if len(b) > 0 && b[0] == '#' { _, rest, err := scanComment(b) - return rest, err + return ref, rest, err } - return b, nil + return ref, b, nil } -func (p *parser) parseTable(b []byte) (ast.Node, []byte, error) { +func (p *parser) parseTable(b []byte) (ast.Reference, []byte, error) { //table = std-table / array-table if len(b) > 1 && b[1] == '[' { return p.parseArrayTable(b) @@ -93,90 +97,95 @@ func (p *parser) parseTable(b []byte) (ast.Node, []byte, error) { return p.parseStdTable(b) } -func (p *parser) parseArrayTable(b []byte) (ast.Node, []byte, error) { +func (p *parser) parseArrayTable(b []byte) (ast.Reference, []byte, error) { //array-table = array-table-open key array-table-close //array-table-open = %x5B.5B ws ; [[ Double left square bracket //array-table-close = ws %x5D.5D ; ]] Double right square bracket - node := ast.Node{ + ref := p.builder.Push(ast.Node{ Kind: ast.ArrayTable, - } + }) b = b[2:] b = p.parseWhitespace(b) k, b, err := p.parseKey(b) if err != nil { - return node, nil, err + return ref, nil, err } - node.Children = k + p.builder.AttachChild(ref, k) b = p.parseWhitespace(b) b, err = expect(']', b) if err != nil { - return node, nil, err + return ref, nil, err } b, err = expect(']', b) - return node, b, err + return ref, b, err } -func (p *parser) parseStdTable(b []byte) (ast.Node, []byte, error) { +func (p *parser) parseStdTable(b []byte) (ast.Reference, []byte, error) { //std-table = std-table-open key std-table-close //std-table-open = %x5B ws ; [ Left square bracket //std-table-close = ws %x5D ; ] Right square bracket - node := ast.Node{ + ref := p.builder.Push(ast.Node{ Kind: ast.Table, - } + }) b = b[1:] b = p.parseWhitespace(b) key, b, err := p.parseKey(b) if err != nil { - return ast.NoNode, nil, err + return ref, nil, err } - node.Children = key + + p.builder.AttachChild(ref, key) + b = p.parseWhitespace(b) b, err = expect(']', b) - return node, b, err + return ref, b, err } -func (p *parser) parseKeyval(b []byte) (ast.Node, []byte, error) { +func (p *parser) parseKeyval(b []byte) (ast.Reference, []byte, error) { //keyval = key keyval-sep val - node := ast.Node{ + ref := p.builder.Push(ast.Node{ Kind: ast.KeyValue, - } + }) key, b, err := p.parseKey(b) if err != nil { - return ast.NoNode, nil, err + return ast.Reference{}, nil, err } - node.Children = append(node.Children, key...) //keyval-sep = ws %x3D ws ; = b = p.parseWhitespace(b) b, err = expect('=', b) if err != nil { - return ast.NoNode, nil, err + return ast.Reference{}, nil, err } b = p.parseWhitespace(b) - valNode, b, err := p.parseVal(b) - if err == nil { - node.Children = append(node.Children, valNode) + valRef, b, err := p.parseVal(b) + if err != nil { + return ref, b, err } - return node, b, err + p.builder.Chain(valRef, key) + p.builder.AttachChild(ref, valRef) + + return ref, b, err } -func (p *parser) parseVal(b []byte) (ast.Node, []byte, error) { +func (p *parser) parseVal(b []byte) (ast.Reference, []byte, error) { // val = string / boolean / array / inline-table / date-time / float / integer + var ref ast.Reference + if len(b) == 0 { - return ast.NoNode, nil, fmt.Errorf("expected value, not eof") + return ref, nil, fmt.Errorf("expected value, not eof") } - node := ast.Node{} var err error c := b[0] @@ -189,10 +198,12 @@ func (p *parser) parseVal(b []byte) (ast.Node, []byte, error) { v, b, err = p.parseBasicString(b) } if err == nil { - node.Kind = ast.String - node.Data = v + ref = p.builder.Push(ast.Node{ + Kind: ast.String, + Data: v, + }) } - return node, b, err + return ref, b, err case '\'': var v []byte if scanFollowsMultilineLiteralStringDelimiter(b) { @@ -201,35 +212,36 @@ func (p *parser) parseVal(b []byte) (ast.Node, []byte, error) { v, b, err = p.parseLiteralString(b) } if err == nil { - node.Kind = ast.String - node.Data = v + ref = p.builder.Push(ast.Node{ + Kind: ast.String, + Data: v, + }) } - return node, b, err + return ref, b, err case 't': if !scanFollowsTrue(b) { - return node, nil, fmt.Errorf("expected 'true'") + return ref, nil, fmt.Errorf("expected 'true'") } - node.Kind = ast.Bool - node.Data = b[:4] - return node, b[4:], nil + ref = p.builder.Push(ast.Node{ + Kind: ast.Bool, + Data: b[:4], + }) + return ref, b[4:], nil case 'f': if !scanFollowsFalse(b) { - return node, nil, fmt.Errorf("expected 'false'") + return ast.Reference{}, nil, fmt.Errorf("expected 'false'") } - node.Kind = ast.Bool - node.Data = b[:5] - return node, b[5:], nil + ref = p.builder.Push(ast.Node{ + Kind: ast.Bool, + Data: b[:5], + }) + return ref, b[5:], nil case '[': - node.Kind = ast.Array - b, err := p.parseValArray(&node, b) - return node, b, err + return p.parseValArray(b) case '{': - node.Kind = ast.InlineTable - b, err := p.parseInlineTable(&node, b) - return node, b, err + return p.parseInlineTable(b) default: - b, err = p.parseIntOrFloatOrDateTime(&node, b) - return node, b, err + return p.parseIntOrFloatOrDateTime(b) } } @@ -241,16 +253,22 @@ func (p *parser) parseLiteralString(b []byte) ([]byte, []byte, error) { return v[1 : len(v)-1], rest, nil } -func (p *parser) parseInlineTable(node *ast.Node, b []byte) ([]byte, error) { +func (p *parser) parseInlineTable(b []byte) (ast.Reference, []byte, error) { //inline-table = inline-table-open [ inline-table-keyvals ] inline-table-close //inline-table-open = %x7B ws ; { //inline-table-close = ws %x7D ; } //inline-table-sep = ws %x2C ws ; , Comma //inline-table-keyvals = keyval [ inline-table-sep inline-table-keyvals ] - b = b[1:] + parent := p.builder.Push(ast.Node{ + Kind: ast.InlineTable, + }) first := true + var child ast.Reference + + b = b[1:] + var err error for len(b) > 0 { b = p.parseWhitespace(b) @@ -261,24 +279,32 @@ func (p *parser) parseInlineTable(node *ast.Node, b []byte) ([]byte, error) { if !first { b, err = expect(',', b) if err != nil { - return nil, err + return parent, nil, err } b = p.parseWhitespace(b) } - var kv ast.Node + var kv ast.Reference kv, b, err = p.parseKeyval(b) if err != nil { - return nil, err + return parent, nil, err + } + + if first { + p.builder.AttachChild(parent, kv) + first = false + } else { + p.builder.Chain(child, kv) } - node.Children = append(node.Children, kv) + child = kv first = false } - return expect('}', b) + rest, err := expect('}', b) + return parent, rest, err } -func (p *parser) parseValArray(node *ast.Node, b []byte) ([]byte, error) { +func (p *parser) parseValArray(b []byte) (ast.Reference, []byte, error) { //array = array-open [ array-values ] ws-comment-newline array-close //array-open = %x5B ; [ //array-close = %x5D ; ] @@ -289,16 +315,22 @@ func (p *parser) parseValArray(node *ast.Node, b []byte) ([]byte, error) { b = b[1:] + parent := p.builder.Push(ast.Node{ + Kind: ast.Array, + }) + first := true + var lastChild ast.Reference + var err error for len(b) > 0 { b, err = p.parseOptionalWhitespaceCommentNewline(b) if err != nil { - return nil, err + return parent, nil, err } if len(b) == 0 { - return nil, unexpectedCharacter{b: b} + return parent, nil, unexpectedCharacter{b: b} } if b[0] == ']' { @@ -306,29 +338,38 @@ func (p *parser) parseValArray(node *ast.Node, b []byte) ([]byte, error) { } if b[0] == ',' { if first { - return nil, fmt.Errorf("array cannot start with comma") + return parent, nil, fmt.Errorf("array cannot start with comma") } b = b[1:] b, err = p.parseOptionalWhitespaceCommentNewline(b) if err != nil { - return nil, err + return parent, nil, err } } - var valueNode ast.Node - valueNode, b, err = p.parseVal(b) + var valueRef ast.Reference + valueRef, b, err = p.parseVal(b) if err != nil { - return nil, err + return parent, nil, err } - node.Children = append(node.Children, valueNode) + + if first { + p.builder.AttachChild(parent, valueRef) + first = false + } else { + p.builder.Chain(lastChild, valueRef) + } + lastChild = valueRef + b, err = p.parseOptionalWhitespaceCommentNewline(b) if err != nil { - return nil, err + return parent, nil, err } first = false } - return expect(']', b) + rest, err := expect(']', b) + return parent, rest, err } func (p *parser) parseOptionalWhitespaceCommentNewline(b []byte) ([]byte, error) { @@ -454,7 +495,7 @@ func (p *parser) parseMultilineBasicString(b []byte) ([]byte, []byte, error) { return builder.Bytes(), rest, nil } -func (p *parser) parseKey(b []byte) ([]ast.Node, []byte, error) { +func (p *parser) parseKey(b []byte) (ast.Reference, []byte, error) { //key = simple-key / dotted-key //simple-key = quoted-key / unquoted-key // @@ -464,14 +505,12 @@ func (p *parser) parseKey(b []byte) ([]ast.Node, []byte, error) { // //dot-sep = ws %x2E ws ; . Period - var nodes []ast.Node - key, b, err := p.parseSimpleKey(b) if err != nil { - return nodes, nil, err + return ast.Reference{}, nil, err } - nodes = append(nodes, ast.Node{ + ref := p.builder.Push(ast.Node{ Kind: ast.Key, Data: key, }) @@ -481,14 +520,14 @@ func (p *parser) parseKey(b []byte) ([]ast.Node, []byte, error) { if len(b) > 0 && b[0] == '.' { b, err = expect('.', b) if err != nil { - return nodes, nil, err + return ref, nil, err } b = p.parseWhitespace(b) key, b, err = p.parseSimpleKey(b) if err != nil { - return nodes, nil, err + return ref, nil, err } - nodes = append(nodes, ast.Node{ + p.builder.PushAndChain(ast.Node{ Kind: ast.Key, Data: key, }) @@ -497,7 +536,7 @@ func (p *parser) parseKey(b []byte) ([]ast.Node, []byte, error) { } } - return nodes, b, nil + return ref, b, nil } func (p *parser) parseSimpleKey(b []byte) (key, rest []byte, err error) { @@ -609,28 +648,30 @@ func (p *parser) parseWhitespace(b []byte) []byte { return rest } -func (p *parser) parseIntOrFloatOrDateTime(node *ast.Node, b []byte) ([]byte, error) { +func (p *parser) parseIntOrFloatOrDateTime(b []byte) (ast.Reference, []byte, error) { switch b[0] { case 'i': if !scanFollowsInf(b) { - return nil, fmt.Errorf("expected 'inf'") + return ast.Reference{}, nil, fmt.Errorf("expected 'inf'") } - node.Kind = ast.Float - node.Data = b[:3] - return b[3:], nil + return p.builder.Push(ast.Node{ + Kind: ast.Float, + Data: b[:3], + }), b[3:], nil case 'n': if !scanFollowsNan(b) { - return nil, fmt.Errorf("expected 'nan'") + return ast.Reference{}, nil, fmt.Errorf("expected 'nan'") } - node.Kind = ast.Float - node.Data = b[:3] - return b[3:], nil + return p.builder.Push(ast.Node{ + Kind: ast.Float, + Data: b[:3], + }), b[3:], nil case '+', '-': - return p.scanIntOrFloat(node, b) + return p.scanIntOrFloat(b) } if len(b) < 3 { - return p.scanIntOrFloat(node, b) + return p.scanIntOrFloat(b) } s := 5 if len(b) < s { @@ -641,10 +682,10 @@ func (p *parser) parseIntOrFloatOrDateTime(node *ast.Node, b []byte) ([]byte, er continue } if idx == 2 && c == ':' || (idx == 4 && c == '-') { - return p.scanDateTime(node, b) + return p.scanDateTime(b) } } - return p.scanIntOrFloat(node, b) + return p.scanIntOrFloat(b) } func digitsToInt(b []byte) int { @@ -656,7 +697,7 @@ func digitsToInt(b []byte) int { return x } -func (p *parser) scanDateTime(node *ast.Node, b []byte) ([]byte, error) { +func (p *parser) scanDateTime(b []byte) (ast.Reference, []byte, error) { // scans for contiguous characters in [0-9T:Z.+-], and up to one space if // followed by a digit. @@ -686,22 +727,25 @@ func (p *parser) scanDateTime(node *ast.Node, b []byte) ([]byte, error) { } } + var kind ast.Kind + if hasTime { if hasTz { - node.Kind = ast.DateTime + kind = ast.DateTime } else { - node.Kind = ast.LocalDateTime + kind = ast.LocalDateTime } } else { if hasTz { - return nil, fmt.Errorf("possible DateTime cannot have a timezone but no time component") + return ast.Reference{}, nil, fmt.Errorf("possible DateTime cannot have a timezone but no time component") } - node.Kind = ast.LocalDate + kind = ast.LocalDate } - node.Data = b[:i] - - return b[i:], nil + return p.builder.Push(ast.Node{ + Kind: kind, + Data: b[:i], + }), b[i:], nil } func (p *parser) parseDateTime(b []byte) ([]byte, error) { @@ -964,7 +1008,7 @@ func (p *parser) parseTime(b []byte) ([]byte, error) { return b[idx:], nil } -func (p *parser) scanIntOrFloat(node *ast.Node, b []byte) ([]byte, error) { +func (p *parser) scanIntOrFloat(b []byte) (ast.Reference, []byte, error) { i := 0 if len(b) > 2 && b[0] == '0' { @@ -989,9 +1033,10 @@ func (p *parser) scanIntOrFloat(node *ast.Node, b []byte) ([]byte, error) { } } - node.Kind = ast.Integer - node.Data = b[:i] - return b[i:], nil + return p.builder.Push(ast.Node{ + Kind: ast.Integer, + Data: b[:i], + }), b[i:], nil } isFloat := false @@ -1010,31 +1055,36 @@ func (p *parser) scanIntOrFloat(node *ast.Node, b []byte) ([]byte, error) { if c == 'i' { if scanFollowsInf(b[i:]) { - node.Kind = ast.Float - node.Data = b[:i+3] - return b[i+3:], nil + return p.builder.Push(ast.Node{ + Kind: ast.Float, + Data: b[:i+3], + }), b[i+3:], nil } - return nil, fmt.Errorf("unexpected character i while scanning for a number") + return ast.Reference{}, nil, fmt.Errorf("unexpected character i while scanning for a number") } if c == 'n' { if scanFollowsNan(b[i:]) { - node.Kind = ast.Float - node.Data = b[:i+3] - return b[i+3:], nil + return p.builder.Push(ast.Node{ + Kind: ast.Float, + Data: b[:i+3], + }), b[i+3:], nil } - return nil, fmt.Errorf("unexpected character n while scanning for a number") + return ast.Reference{}, nil, fmt.Errorf("unexpected character n while scanning for a number") } break } + kind := ast.Integer + if isFloat { - node.Kind = ast.Float - } else { - node.Kind = ast.Integer + kind = ast.Float } - node.Data = b[:i] - return b[i:], nil + + return p.builder.Push(ast.Node{ + Kind: kind, + Data: b[:i], + }), b[i:], nil } func isDigit(r byte) bool { diff --git a/parser_test.go b/parser_test.go index 21826539..1f550473 100644 --- a/parser_test.go +++ b/parser_test.go @@ -125,44 +125,128 @@ func TestParser_AST_Numbers(t *testing.T) { } else { require.NoError(t, err) - expected := ast.Root{ - ast.Node{ + expected := astRoot{ + astNode{ Kind: ast.KeyValue, - Children: []ast.Node{ - {Kind: ast.Key, Data: []byte(`A`)}, + Children: []astNode{ {Kind: e.kind, Data: []byte(e.input)}, + {Kind: ast.Key, Data: []byte(`A`)}, }, }, } - require.Equal(t, expected, p.tree) + compareAST(t, expected, p.builder.Finish()) } }) } } +type astRoot []astNode +type astNode struct { + Kind ast.Kind + Data []byte + Children []astNode +} + +func compareAST(t *testing.T, expected astRoot, actual *ast.Root) { + it := actual.Iterator() + compareIterator(t, expected, it) +} + +func compareIterator(t *testing.T, expected []astNode, actual ast.Iterator) { + idx := 0 + + for actual.Next() { + n := actual.Node() + + if idx >= len(expected) { + t.Fatal("extra child in actual tree") + } + e := expected[idx] + + require.Equal(t, e.Kind, n.Kind) + require.Equal(t, e.Data, n.Data) + + compareIterator(t, e.Children, n.Children()) + + idx++ + } + + if idx < len(expected) { + t.Fatal("missing children in actual", "idx =", idx, "expected =", len(expected)) + } +} + +func (r astRoot) toOrig() *ast.Root { + builder := &ast.Builder{} + + var last ast.Reference + + for i, n := range r { + ref := builder.Push(ast.Node{ + Kind: n.Kind, + Data: n.Data, + }) + + if i > 0 { + builder.Chain(last, ref) + } + last = ref + + if len(n.Children) > 0 { + c := childrenToOrig(builder, n.Children) + builder.AttachChild(ref, c) + } + } + + return builder.Finish() +} + +func childrenToOrig(b *ast.Builder, nodes []astNode) ast.Reference { + var first ast.Reference + var last ast.Reference + for i, n := range nodes { + ref := b.Push(ast.Node{ + Kind: n.Kind, + Data: n.Data, + }) + if i == 0 { + first = ref + } else { + b.Chain(last, ref) + } + last = ref + + if len(n.Children) > 0 { + c := childrenToOrig(b, n.Children) + b.AttachChild(ref, c) + } + } + return first +} + func TestParser_AST(t *testing.T) { examples := []struct { desc string input string - ast ast.Root + ast astRoot err bool }{ { desc: "simple string assignment", input: `A = "hello"`, - ast: ast.Root{ - ast.Node{ + ast: astRoot{ + astNode{ Kind: ast.KeyValue, - Children: []ast.Node{ - { - Kind: ast.Key, - Data: []byte(`A`), - }, + Children: []astNode{ { Kind: ast.String, Data: []byte(`hello`), }, + { + Kind: ast.Key, + Data: []byte(`A`), + }, }, }, }, @@ -170,18 +254,18 @@ func TestParser_AST(t *testing.T) { { desc: "simple bool assignment", input: `A = true`, - ast: ast.Root{ - ast.Node{ + ast: astRoot{ + astNode{ Kind: ast.KeyValue, - Children: []ast.Node{ - { - Kind: ast.Key, - Data: []byte(`A`), - }, + Children: []astNode{ { Kind: ast.Bool, Data: []byte(`true`), }, + { + Kind: ast.Key, + Data: []byte(`A`), + }, }, }, }, @@ -189,24 +273,20 @@ func TestParser_AST(t *testing.T) { { desc: "array of strings", input: `A = ["hello", ["world", "again"]]`, - ast: ast.Root{ - ast.Node{ + ast: astRoot{ + astNode{ Kind: ast.KeyValue, - Children: []ast.Node{ - { - Kind: ast.Key, - Data: []byte(`A`), - }, + Children: []astNode{ { Kind: ast.Array, - Children: []ast.Node{ + Children: []astNode{ { Kind: ast.String, Data: []byte(`hello`), }, { Kind: ast.Array, - Children: []ast.Node{ + Children: []astNode{ { Kind: ast.String, Data: []byte(`world`), @@ -219,6 +299,10 @@ func TestParser_AST(t *testing.T) { }, }, }, + { + Kind: ast.Key, + Data: []byte(`A`), + }, }, }, }, @@ -226,17 +310,13 @@ func TestParser_AST(t *testing.T) { { desc: "array of arrays of strings", input: `A = ["hello", "world"]`, - ast: ast.Root{ - ast.Node{ + ast: astRoot{ + astNode{ Kind: ast.KeyValue, - Children: []ast.Node{ - { - Kind: ast.Key, - Data: []byte(`A`), - }, + Children: []astNode{ { Kind: ast.Array, - Children: []ast.Node{ + Children: []astNode{ { Kind: ast.String, Data: []byte(`hello`), @@ -247,6 +327,10 @@ func TestParser_AST(t *testing.T) { }, }, }, + { + Kind: ast.Key, + Data: []byte(`A`), + }, }, }, }, @@ -254,33 +338,33 @@ func TestParser_AST(t *testing.T) { { desc: "inline table", input: `name = { first = "Tom", last = "Preston-Werner" }`, - ast: ast.Root{ - ast.Node{ + ast: astRoot{ + astNode{ Kind: ast.KeyValue, - Children: []ast.Node{ - { - Kind: ast.Key, - Data: []byte(`name`), - }, + Children: []astNode{ { Kind: ast.InlineTable, - Children: []ast.Node{ + Children: []astNode{ { Kind: ast.KeyValue, - Children: []ast.Node{ - {Kind: ast.Key, Data: []byte(`first`)}, + Children: []astNode{ {Kind: ast.String, Data: []byte(`Tom`)}, + {Kind: ast.Key, Data: []byte(`first`)}, }, }, { Kind: ast.KeyValue, - Children: []ast.Node{ - {Kind: ast.Key, Data: []byte(`last`)}, + Children: []astNode{ {Kind: ast.String, Data: []byte(`Preston-Werner`)}, + {Kind: ast.Key, Data: []byte(`last`)}, }, }, }, }, + { + Kind: ast.Key, + Data: []byte(`name`), + }, }, }, }, @@ -295,7 +379,7 @@ func TestParser_AST(t *testing.T) { require.Error(t, err) } else { require.NoError(t, err) - require.Equal(t, e.ast, p.tree) + compareAST(t, e.ast, p.builder.Finish()) } }) } diff --git a/unmarshaler.go b/unmarshaler.go index d8b31d6f..14c66b41 100644 --- a/unmarshaler.go +++ b/unmarshaler.go @@ -17,7 +17,7 @@ func Unmarshal(data []byte, v interface{}) error { } d := decoder{} - return d.fromAst(p.tree, v) + return d.fromAst(p.builder.Finish(), v) } type decoder struct { @@ -41,7 +41,7 @@ func (d *decoder) arrayIndex(append bool, v reflect.Value) int { return idx } -func (d *decoder) fromAst(tree ast.Root, v interface{}) error { +func (d *decoder) fromAst(tree *ast.Root, v interface{}) error { r := reflect.ValueOf(v) if r.Kind() != reflect.Ptr { return fmt.Errorf("need to target a pointer, not %s", r.Kind()) @@ -54,14 +54,17 @@ func (d *decoder) fromAst(tree ast.Root, v interface{}) error { var skipUntilTable bool var root target = valueTarget(r.Elem()) current := root - for _, node := range tree { + + it := tree.Iterator() + for it.Next() { + node := it.Node() var found bool switch node.Kind { case ast.KeyValue: if skipUntilTable { continue } - err = d.unmarshalKeyValue(current, &node) + err = d.unmarshalKeyValue(current, node) found = true case ast.Table: current, found, err = d.scopeWithKey(root, node.Key()) @@ -91,10 +94,12 @@ func (d *decoder) fromAst(tree ast.Root, v interface{}) error { // // When encountering slices, it should always use its last element, and error // if the slice does not have any. -func (d *decoder) scopeWithKey(x target, key []ast.Node) (target, bool, error) { +func (d *decoder) scopeWithKey(x target, key ast.Iterator) (target, bool, error) { var err error found := true - for _, n := range key { + + for key.Next() { + n := key.Node() x, found, err = d.scopeTableTarget(false, x, string(n.Data)) if err != nil || !found { return nil, found, err @@ -108,18 +113,21 @@ func (d *decoder) scopeWithKey(x target, key []ast.Node) (target, bool, error) { // // It is the same as scopeWithKey, but when scoping the last part of the key // it creates a new element in the array instead of using the last one. -func (d *decoder) scopeWithArrayTable(x target, key []ast.Node) (target, bool, error) { +func (d *decoder) scopeWithArrayTable(x target, key ast.Iterator) (target, bool, error) { var err error found := true - if len(key) > 1 { - for _, n := range key[:len(key)-1] { - x, found, err = d.scopeTableTarget(false, x, string(n.Data)) - if err != nil || !found { - return nil, found, err - } + for key.Next() { + n := key.Node() + if !n.Next().Valid() { // want to stop at one before last + break + } + x, found, err = d.scopeTableTarget(false, x, string(n.Data)) + if err != nil || !found { + return nil, found, err } } - x, found, err = d.scopeTableTarget(false, x, string(key[len(key)-1].Data)) + n := key.Node() + x, found, err = d.scopeTableTarget(false, x, string(n.Data)) if err != nil || !found { return x, found, err } @@ -152,7 +160,7 @@ func (d *decoder) scopeWithArrayTable(x target, key []ast.Node) (target, bool, e return x, found, err } -func (d *decoder) unmarshalKeyValue(x target, node *ast.Node) error { +func (d *decoder) unmarshalKeyValue(x target, node ast.Node) error { assertNode(ast.KeyValue, node) x, found, err := d.scopeWithKey(x, node.Key()) @@ -170,7 +178,7 @@ func (d *decoder) unmarshalKeyValue(x target, node *ast.Node) error { var textUnmarshalerType = reflect.TypeOf(new(encoding.TextUnmarshaler)).Elem() -func tryTextUnmarshaler(x target, node *ast.Node) (bool, error) { +func tryTextUnmarshaler(x target, node ast.Node) (bool, error) { v := x.get() if v.Kind() != reflect.Struct { @@ -185,7 +193,7 @@ func tryTextUnmarshaler(x target, node *ast.Node) (bool, error) { return false, nil } -func (d *decoder) unmarshalValue(x target, node *ast.Node) error { +func (d *decoder) unmarshalValue(x target, node ast.Node) error { v := x.get() if v.Kind() == reflect.Ptr { if !v.Elem().IsValid() { @@ -225,7 +233,7 @@ func (d *decoder) unmarshalValue(x target, node *ast.Node) error { } } -func unmarshalLocalDateTime(x target, node *ast.Node) error { +func unmarshalLocalDateTime(x target, node ast.Node) error { assertNode(ast.LocalDateTime, node) v, rest, err := parseLocalDateTime(node.Data) if err != nil { @@ -237,7 +245,7 @@ func unmarshalLocalDateTime(x target, node *ast.Node) error { return setLocalDateTime(x, v) } -func unmarshalDateTime(x target, node *ast.Node) error { +func unmarshalDateTime(x target, node ast.Node) error { assertNode(ast.DateTime, node) v, err := parseDateTime(node.Data) if err != nil { @@ -254,18 +262,18 @@ func setDateTime(x target, v time.Time) error { return x.set(reflect.ValueOf(v)) } -func unmarshalString(x target, node *ast.Node) error { +func unmarshalString(x target, node ast.Node) error { assertNode(ast.String, node) return setString(x, string(node.Data)) } -func unmarshalBool(x target, node *ast.Node) error { +func unmarshalBool(x target, node ast.Node) error { assertNode(ast.Bool, node) v := node.Data[0] == 't' return setBool(x, v) } -func unmarshalInteger(x target, node *ast.Node) error { +func unmarshalInteger(x target, node ast.Node) error { assertNode(ast.Integer, node) v, err := parseInteger(node.Data) if err != nil { @@ -274,7 +282,7 @@ func unmarshalInteger(x target, node *ast.Node) error { return setInt64(x, v) } -func unmarshalFloat(x target, node *ast.Node) error { +func unmarshalFloat(x target, node ast.Node) error { assertNode(ast.Float, node) v, err := parseFloat(node.Data) if err != nil { @@ -283,11 +291,13 @@ func unmarshalFloat(x target, node *ast.Node) error { return setFloat64(x, v) } -func (d *decoder) unmarshalInlineTable(x target, node *ast.Node) error { +func (d *decoder) unmarshalInlineTable(x target, node ast.Node) error { assertNode(ast.InlineTable, node) - for _, kv := range node.Children { - err := d.unmarshalKeyValue(x, &kv) + it := node.Children() + for it.Next() { + n := it.Node() + err := d.unmarshalKeyValue(x, n) if err != nil { return err } @@ -295,7 +305,7 @@ func (d *decoder) unmarshalInlineTable(x target, node *ast.Node) error { return nil } -func (d *decoder) unmarshalArray(x target, node *ast.Node) error { +func (d *decoder) unmarshalArray(x target, node ast.Node) error { assertNode(ast.Array, node) err := ensureValueIndexable(x) @@ -303,7 +313,10 @@ func (d *decoder) unmarshalArray(x target, node *ast.Node) error { return err } - for idx, n := range node.Children { + it := node.Children() + idx := 0 + for it.Next() { + n := it.Node() v, err := elementAt(x, idx) if err != nil { return err @@ -313,15 +326,16 @@ func (d *decoder) unmarshalArray(x target, node *ast.Node) error { // mimic encoding/json break } - err = d.unmarshalValue(v, &n) + err = d.unmarshalValue(v, n) if err != nil { return err } + idx++ } return nil } -func assertNode(expected ast.Kind, node *ast.Node) { +func assertNode(expected ast.Kind, node ast.Node) { if node.Kind != expected { panic(fmt.Errorf("expected node of kind %s, not %s", expected, node.Kind)) } diff --git a/unmarshaler_test.go b/unmarshaler_test.go index af1592ab..8558e256 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -660,18 +660,18 @@ B = "data"`, } func TestFromAst_KV(t *testing.T) { - root := ast.Root{ - ast.Node{ + root := astRoot{ + astNode{ Kind: ast.KeyValue, - Children: []ast.Node{ - { - Kind: ast.Key, - Data: []byte(`Foo`), - }, + Children: []astNode{ { Kind: ast.String, Data: []byte(`hello`), }, + { + Kind: ast.Key, + Data: []byte(`Foo`), + }, }, }, } @@ -682,44 +682,44 @@ func TestFromAst_KV(t *testing.T) { x := Doc{} d := decoder{} - err := d.fromAst(root, &x) + err := d.fromAst(root.toOrig(), &x) require.NoError(t, err) assert.Equal(t, Doc{Foo: "hello"}, x) } func TestFromAst_Table(t *testing.T) { t.Run("one level table on struct", func(t *testing.T) { - root := ast.Root{ - ast.Node{ + root := astRoot{ + astNode{ Kind: ast.Table, - Children: []ast.Node{ + Children: []astNode{ {Kind: ast.Key, Data: []byte(`Level1`)}, }, }, - ast.Node{ + astNode{ Kind: ast.KeyValue, - Children: []ast.Node{ - { - Kind: ast.Key, - Data: []byte(`A`), - }, + Children: []astNode{ { Kind: ast.String, Data: []byte(`hello`), }, - }, - }, - ast.Node{ - Kind: ast.KeyValue, - Children: []ast.Node{ { Kind: ast.Key, - Data: []byte(`B`), + Data: []byte(`A`), }, + }, + }, + astNode{ + Kind: ast.KeyValue, + Children: []astNode{ { Kind: ast.String, Data: []byte(`world`), }, + { + Kind: ast.Key, + Data: []byte(`B`), + }, }, }, } @@ -735,7 +735,7 @@ func TestFromAst_Table(t *testing.T) { x := Doc{} d := decoder{} - err := d.fromAst(root, &x) + err := d.fromAst(root.toOrig(), &x) require.NoError(t, err) assert.Equal(t, Doc{ Level1: Level1{ @@ -745,25 +745,25 @@ func TestFromAst_Table(t *testing.T) { }, x) }) t.Run("one level table on struct", func(t *testing.T) { - root := ast.Root{ - ast.Node{ + root := astRoot{ + astNode{ Kind: ast.Table, - Children: []ast.Node{ + Children: []astNode{ {Kind: ast.Key, Data: []byte(`A`)}, {Kind: ast.Key, Data: []byte(`B`)}, }, }, - ast.Node{ + astNode{ Kind: ast.KeyValue, - Children: []ast.Node{ - { - Kind: ast.Key, - Data: []byte(`C`), - }, + Children: []astNode{ { Kind: ast.String, Data: []byte(`value`), }, + { + Kind: ast.Key, + Data: []byte(`C`), + }, }, }, } @@ -782,7 +782,7 @@ func TestFromAst_Table(t *testing.T) { x := Doc{} d := decoder{} - err := d.fromAst(root, &x) + err := d.fromAst(root.toOrig(), &x) require.NoError(t, err) assert.Equal(t, Doc{ A: A{B: B{C: "value"}}, @@ -792,32 +792,33 @@ func TestFromAst_Table(t *testing.T) { func TestFromAst_InlineTable(t *testing.T) { t.Run("one level of strings", func(t *testing.T) { - root := ast.Root{ - ast.Node{ + root := astRoot{ + astNode{ Kind: ast.KeyValue, - Children: []ast.Node{ - { - Kind: ast.Key, - Data: []byte(`Name`)}, + Children: []astNode{ { Kind: ast.InlineTable, - Children: []ast.Node{ + Children: []astNode{ { Kind: ast.KeyValue, - Children: []ast.Node{ - {Kind: ast.Key, Data: []byte(`First`)}, + Children: []astNode{ {Kind: ast.String, Data: []byte(`Tom`)}, + {Kind: ast.Key, Data: []byte(`First`)}, }, }, { Kind: ast.KeyValue, - Children: []ast.Node{ - {Kind: ast.Key, Data: []byte(`Last`)}, + Children: []astNode{ {Kind: ast.String, Data: []byte(`Preston-Werner`)}, + {Kind: ast.Key, Data: []byte(`Last`)}, }, }, }, }, + { + Kind: ast.Key, + Data: []byte(`Name`), + }, }, }, } @@ -833,7 +834,7 @@ func TestFromAst_InlineTable(t *testing.T) { x := Doc{} d := decoder{} - err := d.fromAst(root, &x) + err := d.fromAst(root.toOrig(), &x) require.NoError(t, err) assert.Equal(t, Doc{ Name: Name{ @@ -847,17 +848,13 @@ func TestFromAst_InlineTable(t *testing.T) { func TestFromAst_Slice(t *testing.T) { t.Run("slice of string", func(t *testing.T) { - root := ast.Root{ - ast.Node{ + root := astRoot{ + astNode{ Kind: ast.KeyValue, - Children: []ast.Node{ - { - Kind: ast.Key, - Data: []byte(`Foo`), - }, + Children: []astNode{ { Kind: ast.Array, - Children: []ast.Node{ + Children: []astNode{ { Kind: ast.String, Data: []byte(`hello`), @@ -868,6 +865,10 @@ func TestFromAst_Slice(t *testing.T) { }, }, }, + { + Kind: ast.Key, + Data: []byte(`Foo`), + }, }, }, } @@ -878,23 +879,19 @@ func TestFromAst_Slice(t *testing.T) { x := Doc{} d := decoder{} - err := d.fromAst(root, &x) + err := d.fromAst(root.toOrig(), &x) require.NoError(t, err) assert.Equal(t, Doc{Foo: []string{"hello", "world"}}, x) }) t.Run("slice of interfaces for strings", func(t *testing.T) { - root := ast.Root{ - ast.Node{ + root := astRoot{ + astNode{ Kind: ast.KeyValue, - Children: []ast.Node{ - { - Kind: ast.Key, - Data: []byte(`Foo`), - }, + Children: []astNode{ { Kind: ast.Array, - Children: []ast.Node{ + Children: []astNode{ { Kind: ast.String, Data: []byte(`hello`), @@ -905,6 +902,10 @@ func TestFromAst_Slice(t *testing.T) { }, }, }, + { + Kind: ast.Key, + Data: []byte(`Foo`), + }, }, }, } @@ -915,30 +916,26 @@ func TestFromAst_Slice(t *testing.T) { x := Doc{} d := decoder{} - err := d.fromAst(root, &x) + err := d.fromAst(root.toOrig(), &x) require.NoError(t, err) assert.Equal(t, Doc{Foo: []interface{}{"hello", "world"}}, x) }) t.Run("slice of interfaces with slices", func(t *testing.T) { - root := ast.Root{ - ast.Node{ + root := astRoot{ + astNode{ Kind: ast.KeyValue, - Children: []ast.Node{ - { - Kind: ast.Key, - Data: []byte(`Foo`), - }, + Children: []astNode{ { Kind: ast.Array, - Children: []ast.Node{ + Children: []astNode{ { Kind: ast.String, Data: []byte(`hello`), }, { Kind: ast.Array, - Children: []ast.Node{ + Children: []astNode{ { Kind: ast.String, Data: []byte(`inner1`), @@ -951,6 +948,10 @@ func TestFromAst_Slice(t *testing.T) { }, }, }, + { + Kind: ast.Key, + Data: []byte(`Foo`), + }, }, }, } @@ -961,7 +962,7 @@ func TestFromAst_Slice(t *testing.T) { x := Doc{} d := decoder{} - err := d.fromAst(root, &x) + err := d.fromAst(root.toOrig(), &x) require.NoError(t, err) assert.Equal(t, Doc{Foo: []interface{}{"hello", []interface{}{"inner1", "inner2"}}}, x) }) From 17299c937b6964bf8a0f807469dd2da41ce81bb2 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Thu, 25 Mar 2021 19:56:40 -0400 Subject: [PATCH 113/228] Update readme --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 167f57fe..beed31ce 100644 --- a/README.md +++ b/README.md @@ -14,15 +14,15 @@ Development branch. Probably does not work. - [x] Support Arrays. - [x] Support Unmarshaler interface. - [x] Original go-toml unmarshal tests pass. -- [ ] Benchmark! -- [ ] Abstract AST. +- [x] Benchmark! +- [x] Abstract AST. - [ ] Attach comments to AST (gated by parser flag). - [ ] Track file position (line, column) for errors. - [ ] Benchmark again! ## Further work -- [ ] Rewrite AST to use a single array as storage instead of one allocation per +- [x] Rewrite AST to use a single array as storage instead of one allocation per node. - [ ] Provide "minimal allocations" option that uses `unsafe` to reuse the input byte array as storage for strings. From 1da2fc7e28f73a7be57401450f5b20b0c2ef1bc5 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Thu, 25 Mar 2021 20:19:58 -0400 Subject: [PATCH 114/228] Minimal shared cache for struct field paths --- targets.go | 87 ++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 59 insertions(+), 28 deletions(-) diff --git a/targets.go b/targets.go index 2fd410d4..d229020f 100644 --- a/targets.go +++ b/targets.go @@ -5,6 +5,7 @@ import ( "math" "reflect" "strings" + "sync" ) type target interface { @@ -466,41 +467,71 @@ func scopeMap(v reflect.Value, name string) (target, bool, error) { }, true, nil } +type fieldPathsMap = map[string][]int + +type fieldPathsCache struct { + m map[reflect.Type]fieldPathsMap + l sync.RWMutex +} + +func (c *fieldPathsCache) get(t reflect.Type) (fieldPathsMap, bool) { + c.l.RLock() + paths, ok := c.m[t] + c.l.RUnlock() + return paths, ok +} + +func (c *fieldPathsCache) set(t reflect.Type, m fieldPathsMap) { + c.l.Lock() + c.m[t] = m + c.l.Unlock() +} + +var globalFieldPathsCache = fieldPathsCache{ + m: map[reflect.Type]fieldPathsMap{}, + l: sync.RWMutex{}, +} + func scopeStruct(v reflect.Value, name string) (target, bool, error) { // TODO: cache this, and reduce allocations - fieldPaths := map[string][]int{} - - path := make([]int, 0, 16) - var walk func(reflect.Value) - walk = func(v reflect.Value) { - t := v.Type() - for i := 0; i < t.NumField(); i++ { - l := len(path) - path = append(path, i) - f := t.Field(i) - if f.PkgPath != "" { - // only consider exported fields - } else if f.Anonymous { - walk(v.Field(i)) - } else { - fieldName, ok := f.Tag.Lookup("toml") - if !ok { - fieldName = f.Name + fieldPaths, ok := globalFieldPathsCache.get(v.Type()) + if !ok { + fieldPaths = map[string][]int{} + + path := make([]int, 0, 16) + var walk func(reflect.Value) + walk = func(v reflect.Value) { + t := v.Type() + for i := 0; i < t.NumField(); i++ { + l := len(path) + path = append(path, i) + f := t.Field(i) + if f.PkgPath != "" { + // only consider exported fields + } else if f.Anonymous { + walk(v.Field(i)) + } else { + fieldName, ok := f.Tag.Lookup("toml") + if !ok { + fieldName = f.Name + } + + pathCopy := make([]int, len(path)) + copy(pathCopy, path) + + fieldPaths[fieldName] = pathCopy + // extra copy for the case-insensitive match + fieldPaths[strings.ToLower(fieldName)] = pathCopy } - - pathCopy := make([]int, len(path)) - copy(pathCopy, path) - - fieldPaths[fieldName] = pathCopy - // extra copy for the case-insensitive match - fieldPaths[strings.ToLower(fieldName)] = pathCopy + path = path[:l] } - path = path[:l] } - } - walk(v) + walk(v) + + globalFieldPathsCache.set(v.Type(), fieldPaths) + } path, ok := fieldPaths[name] if !ok { From 9d3a912da0c0c2b69bc2720c4c0d8c3d217d1cf1 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Thu, 25 Mar 2021 20:38:45 -0400 Subject: [PATCH 115/228] Remove unused interface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Comparing: old: v2-wip/17299c9 (2021-03-25 20:19:40 -0400 -0400) run: v2-wip/1da2fc7 (2021-03-25 20:38:05 -0400 -0400) ----------------------------------------------------------- name old time/op new time/op delta UnmarshalSimple/v2-8 755ns ± 3% 700ns ± 3% -7.26% (p=0.008 n=5+5) UnmarshalSimple/v1-8 3.87µs ± 0% 3.85µs ± 1% ~ (p=0.254 n=4+5) UnmarshalSimple/bs-8 2.44µs ± 4% 2.34µs ± 2% ~ (p=0.056 n=5+5) ReferenceFile/v2-8 33.5µs ± 7% 32.2µs ±13% ~ (p=0.421 n=5+5) ReferenceFile/v1-8 269µs ± 3% 270µs ± 2% ~ (p=1.000 n=5+5) ReferenceFile/bs-8 296µs ± 2% 291µs ± 0% ~ (p=0.095 n=5+5) name old alloc/op new alloc/op delta ReferenceFile/v2-8 38.9kB ± 0% 37.1kB ± 0% -4.77% (p=0.008 n=5+5) ReferenceFile/v1-8 131kB ± 0% 131kB ± 0% ~ (all equal) ReferenceFile/bs-8 80.8kB ± 0% 80.8kB ± 0% ~ (p=0.841 n=5+5) name old allocs/op new allocs/op delta ReferenceFile/v2-8 181 ± 0% 152 ± 0% -16.02% (p=0.008 n=5+5) ReferenceFile/v1-8 2.65k ± 0% 2.65k ± 0% ~ (all equal) ReferenceFile/bs-8 1.73k ± 0% 1.73k ± 0% ~ (all equal) --- internal/ast/ast.go | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/internal/ast/ast.go b/internal/ast/ast.go index d8d18431..d565226c 100644 --- a/internal/ast/ast.go +++ b/internal/ast/ast.go @@ -12,20 +12,14 @@ import ( // for it.Next() { // it.Node() // } -type Iterator interface { - // Next moves the iterator forward and returns true if points to a node, false - // otherwise. - Next() bool - // Node returns a copy of the node pointed at by the iterator. - Node() Node -} - -type chainIterator struct { +type Iterator struct { started bool node Node } -func (c *chainIterator) Next() bool { +// Next moves the iterator forward and returns true if points to a node, false +// otherwise. +func (c *Iterator) Next() bool { if !c.started { c.started = true } else if c.node.Valid() { @@ -34,7 +28,8 @@ func (c *chainIterator) Next() bool { return c.node.Valid() } -func (c *chainIterator) Node() Node { +// Node returns a copy of the node pointed at by the iterator. +func (c *Iterator) Node() Node { return c.node } @@ -43,7 +38,7 @@ type Root struct { } func (r *Root) Iterator() Iterator { - it := &chainIterator{} + it := Iterator{} if len(r.nodes) > 0 { it.node = r.nodes[0] } @@ -114,9 +109,9 @@ func (n *Node) Key() Iterator { if !value.Valid() { panic(fmt.Errorf("KeyValue should have at least two children")) } - return &chainIterator{node: value.Next()} + return Iterator{node: value.Next()} case Table, ArrayTable: - return &chainIterator{node: n.Child()} + return Iterator{node: n.Child()} default: panic(fmt.Errorf("Key() is not supported on a %s", n.Kind)) } @@ -131,7 +126,7 @@ func (n Node) Value() Node { } func (n Node) Children() Iterator { - return &chainIterator{node: n.Child()} + return Iterator{node: n.Child()} } func assertKind(k Kind, n Node) { From 1d332cd112fe1c89ddb7ff2f86698a2aa50dc4a9 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Thu, 25 Mar 2021 20:46:31 -0400 Subject: [PATCH 116/228] Add documentation for the AST --- internal/ast/ast.go | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/internal/ast/ast.go b/internal/ast/ast.go index d565226c..ba2729e0 100644 --- a/internal/ast/ast.go +++ b/internal/ast/ast.go @@ -33,10 +33,14 @@ func (c *Iterator) Node() Node { return c.node } +// Root contains a full AST. +// +// It is immutable once constructed with Builder. type Root struct { nodes []Node } +// Iterator over the top level nodes. func (r *Root) Iterator() Iterator { it := Iterator{} if len(r.nodes) > 0 { @@ -45,10 +49,6 @@ func (r *Root) Iterator() Iterator { return it } -func (r *Root) Reset() { - r.nodes = r.nodes[:0] -} - func (r *Root) at(idx int) Node { // TODO: unsafe to point to the node directly return r.nodes[idx] @@ -77,7 +77,7 @@ type Node struct { // next node. func (n Node) Next() Node { if n.next <= 0 { - return NoNode + return noNode } return n.root.at(n.next) } @@ -87,16 +87,17 @@ func (n Node) Next() Node { // Returns an invalid Node if there is none. func (n Node) Child() Node { if n.child <= 0 { - return NoNode + return noNode } return n.root.at(n.child) } +// Valid returns true if the node's kind is set (not to Invalid). func (n Node) Valid() bool { return n.Kind != Invalid } -var NoNode = Node{} +var noNode = Node{} // Key returns the child nodes making the Key on a supported node. Panics // otherwise. @@ -125,6 +126,7 @@ func (n Node) Value() Node { return n.Child() } +// Children returns an iterator over a node's children. func (n Node) Children() Iterator { return Iterator{node: n.Child()} } From 0fcf06e374e515095f018b20645dff0ca708dde6 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Thu, 25 Mar 2021 20:49:27 -0400 Subject: [PATCH 117/228] Update todo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index beed31ce..23d44a30 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Development branch. Probably does not work. node. - [ ] Provide "minimal allocations" option that uses `unsafe` to reuse the input byte array as storage for strings. -- [ ] Cache reflection operations per type. +- [x] Cache reflection operations per type. ## Ideas From 4efec6b76abb125e74d90142a6fcfbef314aa992 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Thu, 25 Mar 2021 21:05:07 -0400 Subject: [PATCH 118/228] Add github actions workflow --- .github/workflows/workflow.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .github/workflows/workflow.yml diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml new file mode 100644 index 00000000..d5c25dd0 --- /dev/null +++ b/.github/workflows/workflow.yml @@ -0,0 +1,20 @@ +name: build-test +on: + push: + branches: + - v2-wip + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + go: [ '1.15', '1.16' ] + name: Test on ${{ matrix.go }} + steps: + - uses: actions/checkout@master + - name: Setup go + uses: actions/setup-go@master + with: + go-version: ${{ matrix.go }} + - run: go test ./... From ffc7d3ba6eca1e47a15a3e72a093f935042a631a Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Thu, 25 Mar 2021 21:06:34 -0400 Subject: [PATCH 119/228] Port codeql --- .github/workflows/codeql-analysis.yml | 67 +++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 .github/workflows/codeql-analysis.yml diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 00000000..4be22110 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,67 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ master ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ master ] + schedule: + - cron: '26 19 * * 0' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + language: [ 'go' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] + # Learn more: + # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 From 6c8adbcb17b0911720170256bd2c01d3c9f38127 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Thu, 25 Mar 2021 21:06:54 -0400 Subject: [PATCH 120/228] Remove azure pipeline --- azure-pipelines.yml | 189 -------------------------------------------- 1 file changed, 189 deletions(-) delete mode 100644 azure-pipelines.yml diff --git a/azure-pipelines.yml b/azure-pipelines.yml deleted file mode 100644 index 8c683165..00000000 --- a/azure-pipelines.yml +++ /dev/null @@ -1,189 +0,0 @@ -trigger: -- master - -stages: -- stage: run_checks - displayName: "Check" - dependsOn: [] - jobs: - - job: fmt - displayName: "fmt" - pool: - vmImage: ubuntu-latest - steps: - - task: GoTool@0 - displayName: "Install Go 1.15" - inputs: - version: "1.15" - - task: Go@0 - displayName: "go fmt ./..." - inputs: - command: 'custom' - customCommand: 'fmt' - arguments: './...' - - job: coverage - displayName: "coverage" - condition: ne(variables['Build.SourceBranchName'], 'master') - pool: - vmImage: ubuntu-latest - steps: - - task: GoTool@0 - displayName: "Install Go 1.15" - inputs: - version: "1.15" - - task: Go@0 - displayName: "Generate coverage" - inputs: - command: 'test' - arguments: "-race -coverprofile=coverage.txt -covermode=atomic" - - task: Bash@3 - inputs: - targetType: 'inline' - script: 'bash <(curl -s https://codecov.io/bash) -t ${CODECOV_TOKEN}' - env: - CODECOV_TOKEN: $(CODECOV_TOKEN) - - job: benchmark - displayName: "benchmark" - pool: - vmImage: ubuntu-latest - steps: - - task: GoTool@0 - displayName: "Install Go 1.15" - inputs: - version: "1.15" - - script: echo "##vso[task.setvariable variable=PATH]${PATH}:/home/vsts/go/bin/" - - task: Bash@3 - inputs: - filePath: './benchmark.sh' - arguments: "master $(Build.Repository.Uri)" - - - job: go_unit_tests - displayName: "unit tests" - strategy: - matrix: - linux 1.15: - goVersion: '1.15' - imageName: 'ubuntu-latest' - mac 1.15: - goVersion: '1.15' - imageName: 'macOS-latest' - windows 1.15: - goVersion: '1.15' - imageName: 'windows-latest' - linux 1.14: - goVersion: '1.14' - imageName: 'ubuntu-latest' - mac 1.14: - goVersion: '1.14' - imageName: 'macOS-latest' - windows 1.14: - goVersion: '1.14' - imageName: 'windows-latest' - pool: - vmImage: $(imageName) - steps: - - task: GoTool@0 - displayName: "Install Go $(goVersion)" - inputs: - version: $(goVersion) - - task: Go@0 - displayName: "go test ./..." - inputs: - command: 'test' - arguments: './...' -- stage: build_binaries - displayName: "Build binaries" - dependsOn: run_checks - jobs: - - job: build_binary - displayName: "Build binary" - strategy: - matrix: - linux_amd64: - GOOS: linux - GOARCH: amd64 - darwin_amd64: - GOOS: darwin - GOARCH: amd64 - windows_amd64: - GOOS: windows - GOARCH: amd64 - pool: - vmImage: ubuntu-latest - steps: - - task: GoTool@0 - displayName: "Install Go" - inputs: - version: 1.15 - - task: Bash@3 - inputs: - targetType: inline - script: "make dist" - env: - go.goos: $(GOOS) - go.goarch: $(GOARCH) - - task: CopyFiles@2 - inputs: - sourceFolder: '$(Build.SourcesDirectory)' - contents: '*.tar.xz' - TargetFolder: '$(Build.ArtifactStagingDirectory)' - - task: PublishBuildArtifacts@1 - inputs: - pathtoPublish: '$(Build.ArtifactStagingDirectory)' - artifactName: binaries -- stage: build_binaries_manifest - displayName: "Build binaries manifest" - dependsOn: build_binaries - jobs: - - job: build_manifest - displayName: "Build binaries manifest" - steps: - - task: DownloadBuildArtifacts@0 - inputs: - buildType: 'current' - downloadType: 'single' - artifactName: 'binaries' - downloadPath: '$(Build.SourcesDirectory)' - - task: Bash@3 - inputs: - targetType: inline - script: "cd binaries && sha256sum --binary *.tar.xz | tee $(Build.ArtifactStagingDirectory)/sha256sums.txt" - - task: PublishBuildArtifacts@1 - inputs: - pathtoPublish: '$(Build.ArtifactStagingDirectory)' - artifactName: manifest - -- stage: build_docker_image - displayName: "Build Docker image" - dependsOn: run_checks - jobs: - - job: build - displayName: "Build" - pool: - vmImage: ubuntu-latest - steps: - - task: Docker@2 - inputs: - command: 'build' - Dockerfile: 'Dockerfile' - buildContext: '.' - addPipelineData: false - -- stage: publish_docker_image - displayName: "Publish Docker image" - dependsOn: build_docker_image - condition: and(succeeded(), eq(variables['Build.SourceBranchName'], 'master')) - jobs: - - job: publish - displayName: "Publish" - pool: - vmImage: ubuntu-latest - steps: - - task: Docker@2 - inputs: - containerRegistry: 'DockerHub' - repository: 'pelletier/go-toml' - command: 'buildAndPush' - Dockerfile: 'Dockerfile' - buildContext: '.' - tags: 'latest' From e75f23188d83daa0311484ccd5609cf55883c307 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Thu, 25 Mar 2021 21:08:03 -0400 Subject: [PATCH 121/228] Add v2-wip to codeql branches --- .github/workflows/codeql-analysis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 4be22110..5e8a42c6 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -13,7 +13,7 @@ name: "CodeQL" on: push: - branches: [ master ] + branches: [ master, v2-wip ] pull_request: # The branches below must be a subset of the branches above branches: [ master ] From f4ac7f7bfab53b7805ef391b2e3aa8a2e4f7ca60 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Thu, 25 Mar 2021 21:12:19 -0400 Subject: [PATCH 122/228] Multi OS testing --- .github/workflows/workflow.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index d5c25dd0..2befb82a 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -6,11 +6,12 @@ on: jobs: build: - runs-on: ubuntu-latest strategy: matrix: + os: [ 'ubuntu-latest', 'windows-latest', 'macos-latest'] go: [ '1.15', '1.16' ] - name: Test on ${{ matrix.go }} + runs-on: ${{ matrix.os }} + name: Test on ${{ matrix.os }} with Go ${{ matrix.go }} steps: - uses: actions/checkout@master - name: Setup go From 47611ff9eaaeca8c6df41b63647ec04312304165 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Thu, 25 Mar 2021 21:14:59 -0400 Subject: [PATCH 123/228] Shorter names for CI --- .github/workflows/workflow.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 2befb82a..586748e9 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -1,4 +1,4 @@ -name: build-test +name: test on: push: branches: @@ -11,10 +11,10 @@ jobs: os: [ 'ubuntu-latest', 'windows-latest', 'macos-latest'] go: [ '1.15', '1.16' ] runs-on: ${{ matrix.os }} - name: Test on ${{ matrix.os }} with Go ${{ matrix.go }} + name: ${{ matrix.os }}/${{ matrix.go }} steps: - uses: actions/checkout@master - - name: Setup go + - name: Setup go ${{ matrix.go }} uses: actions/setup-go@master with: go-version: ${{ matrix.go }} From 3f23ab97e0cbf086ffb552c69d42cb313894c07e Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Thu, 25 Mar 2021 21:16:31 -0400 Subject: [PATCH 124/228] Revert os/go version to fit in github pop-up --- .github/workflows/workflow.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 586748e9..d9c384da 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -11,7 +11,7 @@ jobs: os: [ 'ubuntu-latest', 'windows-latest', 'macos-latest'] go: [ '1.15', '1.16' ] runs-on: ${{ matrix.os }} - name: ${{ matrix.os }}/${{ matrix.go }} + name: ${{ matrix.go }}/${{ matrix.os }} steps: - uses: actions/checkout@master - name: Setup go ${{ matrix.go }} From 390927a0cd2a2bb84d8aa15d9360798e4fcdc2d4 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Thu, 25 Mar 2021 22:37:16 -0400 Subject: [PATCH 125/228] Reuse AST storage between top-level expressions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ``` Comparing: old: v2-wip/1da2fc7 (2021-03-25 20:38:05 -0400 -0400) run: v2-wip/3f23ab9 (2021-03-25 22:35:06 -0400 -0400) ----------------------------------------------------------- name old time/op new time/op delta UnmarshalSimple/v2-8 700ns ± 3% 705ns ± 2% ~ (p=0.690 n=5+5) UnmarshalSimple/v1-8 3.85µs ± 1% 4.02µs ± 4% +4.19% (p=0.032 n=5+5) UnmarshalSimple/bs-8 2.34µs ± 2% 2.38µs ± 3% ~ (p=0.310 n=5+5) ReferenceFile/v2-8 32.2µs ±13% 23.9µs ± 1% -25.79% (p=0.008 n=5+5) ReferenceFile/v1-8 270µs ± 2% 264µs ± 2% ~ (p=0.095 n=5+5) ReferenceFile/bs-8 291µs ± 0% 294µs ± 0% +0.88% (p=0.008 n=5+5) name old alloc/op new alloc/op delta ReferenceFile/v2-8 37.1kB ± 0% 6.7kB ± 0% -81.91% (p=0.008 n=5+5) ReferenceFile/v1-8 131kB ± 0% 131kB ± 0% ~ (p=0.444 n=5+5) ReferenceFile/bs-8 80.8kB ± 0% 80.8kB ± 0% ~ (p=0.571 n=5+5) name old allocs/op new allocs/op delta ReferenceFile/v2-8 152 ± 0% 148 ± 0% -2.63% (p=0.008 n=5+5) ReferenceFile/v1-8 2.65k ± 0% 2.65k ± 0% ~ (all equal) ReferenceFile/bs-8 1.73k ± 0% 1.73k ± 0% ~ (all equal) ~/s/g/p/g/benchmark$ go test -bench=. goos: linux goarch: amd64 pkg: github.com/pelletier/go-toml/v2/benchmark cpu: Intel(R) Core(TM) i7-7700 CPU @ 3.60GHz BenchmarkUnmarshalSimple/v2-8 1692444 710.7 ns/op BenchmarkUnmarshalSimple/v1-8 307609 3862 ns/op BenchmarkUnmarshalSimple/bs-8 520429 2285 ns/op BenchmarkReferenceFile/v2-8 50395 24006 ns/op 6704 B/op 148 allocs/op BenchmarkReferenceFile/v1-8 4144 264655 ns/op 130567 B/op 2649 allocs/op BenchmarkReferenceFile/bs-8 3969 293635 ns/op 80784 B/op 1729 allocs/op PASS ok github.com/pelletier/go-toml/v2/benchmark 8.143s ``` --- internal/ast/builder.go | 44 +++--- parser.go | 65 ++++++-- parser_test.go | 215 +++++++++++++-------------- unmarshaler.go | 17 +-- unmarshaler_test.go | 320 +--------------------------------------- 5 files changed, 188 insertions(+), 473 deletions(-) diff --git a/internal/ast/builder.go b/internal/ast/builder.go index a6868cd9..796b7f1d 100644 --- a/internal/ast/builder.go +++ b/internal/ast/builder.go @@ -1,10 +1,5 @@ package ast -type Builder struct { - nodes []Node - lastIdx int -} - type Reference struct { idx int set bool @@ -14,22 +9,28 @@ func (r Reference) Valid() bool { return r.set } -func (b *Builder) Finish() *Root { - r := &Root{ - nodes: b.nodes, - } - b.nodes = nil +type Builder struct { + tree Root + lastIdx int +} - for i := range r.nodes { - r.nodes[i].root = r - } +func (b *Builder) Tree() *Root { + return &b.tree +} + +func (b *Builder) NodeAt(ref Reference) Node { + return b.tree.at(ref.idx) +} - return r +func (b *Builder) Reset() { + b.tree.nodes = b.tree.nodes[:0] + b.lastIdx = 0 } func (b *Builder) Push(n Node) Reference { - b.lastIdx = len(b.nodes) - b.nodes = append(b.nodes, n) + n.root = &b.tree + b.lastIdx = len(b.tree.nodes) + b.tree.nodes = append(b.tree.nodes, n) return Reference{ idx: b.lastIdx, set: true, @@ -37,10 +38,11 @@ func (b *Builder) Push(n Node) Reference { } func (b *Builder) PushAndChain(n Node) Reference { - newIdx := len(b.nodes) - b.nodes = append(b.nodes, n) + n.root = &b.tree + newIdx := len(b.tree.nodes) + b.tree.nodes = append(b.tree.nodes, n) if b.lastIdx >= 0 { - b.nodes[b.lastIdx].next = newIdx + b.tree.nodes[b.lastIdx].next = newIdx } b.lastIdx = newIdx return Reference{ @@ -50,9 +52,9 @@ func (b *Builder) PushAndChain(n Node) Reference { } func (b *Builder) AttachChild(parent Reference, child Reference) { - b.nodes[parent.idx].child = child.idx + b.tree.nodes[parent.idx].child = child.idx } func (b *Builder) Chain(from Reference, to Reference) { - b.nodes[from.idx].next = to.idx + b.tree.nodes[from.idx].next = to.idx } diff --git a/parser.go b/parser.go index f2ca2dfc..25726bd2 100644 --- a/parser.go +++ b/parser.go @@ -11,30 +11,63 @@ import ( type parser struct { builder ast.Builder + ref ast.Reference + data []byte + left []byte + err error + first bool } -func (p *parser) parse(b []byte) error { - last, b, err := p.parseExpression(b) - if err != nil { - return err +func (p *parser) Reset(b []byte) { + p.builder.Reset() + p.ref = ast.Reference{} + p.data = b + p.left = b + p.err = nil + p.first = true +} + +func (p *parser) NextExpression() bool { + if len(p.left) == 0 || p.err != nil { + return false } - for len(b) > 0 { - b, err = p.parseNewline(b) - if err != nil { - return err + + p.builder.Reset() + p.ref = ast.Reference{} + + for { + if len(p.left) == 0 || p.err != nil { + return false } - var next ast.Reference - next, b, err = p.parseExpression(b) - if err != nil { - return err + if !p.first { + p.left, p.err = p.parseNewline(p.left) } - if next.Valid() { - p.builder.Chain(last, next) - last = next + + if len(p.left) == 0 || p.err != nil { + return false } + + p.ref, p.left, p.err = p.parseExpression(p.left) + + if p.err != nil { + return false + } + + if p.ref.Valid() { + return true + } + + p.first = false } - return nil +} + +func (p *parser) Expression() ast.Node { + return p.builder.NodeAt(p.ref) +} + +func (p *parser) Error() error { + return p.err } func (p *parser) parseNewline(b []byte) ([]byte, error) { diff --git a/parser_test.go b/parser_test.go index 1f550473..0bd9be78 100644 --- a/parser_test.go +++ b/parser_test.go @@ -119,23 +119,22 @@ func TestParser_AST_Numbers(t *testing.T) { for _, e := range examples { t.Run(e.desc, func(t *testing.T) { p := parser{} - err := p.parse([]byte(`A = ` + e.input)) + p.Reset([]byte(`A = ` + e.input)) + p.NextExpression() + err := p.Error() if e.err { require.Error(t, err) } else { require.NoError(t, err) - expected := astRoot{ - astNode{ - Kind: ast.KeyValue, - Children: []astNode{ - {Kind: e.kind, Data: []byte(e.input)}, - {Kind: ast.Key, Data: []byte(`A`)}, - }, + expected := astNode{ + Kind: ast.KeyValue, + Children: []astNode{ + {Kind: e.kind, Data: []byte(e.input)}, + {Kind: ast.Key, Data: []byte(`A`)}, }, } - - compareAST(t, expected, p.builder.Finish()) + compareNode(t, expected, p.Expression()) } }) } @@ -153,6 +152,13 @@ func compareAST(t *testing.T, expected astRoot, actual *ast.Root) { compareIterator(t, expected, it) } +func compareNode(t *testing.T, e astNode, n ast.Node) { + require.Equal(t, e.Kind, n.Kind) + require.Equal(t, e.Data, n.Data) + + compareIterator(t, e.Children, n.Children()) +} + func compareIterator(t *testing.T, expected []astNode, actual ast.Iterator) { idx := 0 @@ -164,10 +170,7 @@ func compareIterator(t *testing.T, expected []astNode, actual ast.Iterator) { } e := expected[idx] - require.Equal(t, e.Kind, n.Kind) - require.Equal(t, e.Data, n.Data) - - compareIterator(t, e.Children, n.Children()) + compareNode(t, e, n) idx++ } @@ -199,7 +202,7 @@ func (r astRoot) toOrig() *ast.Root { } } - return builder.Finish() + return builder.Tree() } func childrenToOrig(b *ast.Builder, nodes []astNode) ast.Reference { @@ -229,24 +232,22 @@ func TestParser_AST(t *testing.T) { examples := []struct { desc string input string - ast astRoot + ast astNode err bool }{ { desc: "simple string assignment", input: `A = "hello"`, - ast: astRoot{ - astNode{ - Kind: ast.KeyValue, - Children: []astNode{ - { - Kind: ast.String, - Data: []byte(`hello`), - }, - { - Kind: ast.Key, - Data: []byte(`A`), - }, + ast: astNode{ + Kind: ast.KeyValue, + Children: []astNode{ + { + Kind: ast.String, + Data: []byte(`hello`), + }, + { + Kind: ast.Key, + Data: []byte(`A`), }, }, }, @@ -254,18 +255,16 @@ func TestParser_AST(t *testing.T) { { desc: "simple bool assignment", input: `A = true`, - ast: astRoot{ - astNode{ - Kind: ast.KeyValue, - Children: []astNode{ - { - Kind: ast.Bool, - Data: []byte(`true`), - }, - { - Kind: ast.Key, - Data: []byte(`A`), - }, + ast: astNode{ + Kind: ast.KeyValue, + Children: []astNode{ + { + Kind: ast.Bool, + Data: []byte(`true`), + }, + { + Kind: ast.Key, + Data: []byte(`A`), }, }, }, @@ -273,36 +272,34 @@ func TestParser_AST(t *testing.T) { { desc: "array of strings", input: `A = ["hello", ["world", "again"]]`, - ast: astRoot{ - astNode{ - Kind: ast.KeyValue, - Children: []astNode{ - { - Kind: ast.Array, - Children: []astNode{ - { - Kind: ast.String, - Data: []byte(`hello`), - }, - { - Kind: ast.Array, - Children: []astNode{ - { - Kind: ast.String, - Data: []byte(`world`), - }, - { - Kind: ast.String, - Data: []byte(`again`), - }, + ast: astNode{ + Kind: ast.KeyValue, + Children: []astNode{ + { + Kind: ast.Array, + Children: []astNode{ + { + Kind: ast.String, + Data: []byte(`hello`), + }, + { + Kind: ast.Array, + Children: []astNode{ + { + Kind: ast.String, + Data: []byte(`world`), + }, + { + Kind: ast.String, + Data: []byte(`again`), }, }, }, }, - { - Kind: ast.Key, - Data: []byte(`A`), - }, + }, + { + Kind: ast.Key, + Data: []byte(`A`), }, }, }, @@ -310,27 +307,25 @@ func TestParser_AST(t *testing.T) { { desc: "array of arrays of strings", input: `A = ["hello", "world"]`, - ast: astRoot{ - astNode{ - Kind: ast.KeyValue, - Children: []astNode{ - { - Kind: ast.Array, - Children: []astNode{ - { - Kind: ast.String, - Data: []byte(`hello`), - }, - { - Kind: ast.String, - Data: []byte(`world`), - }, + ast: astNode{ + Kind: ast.KeyValue, + Children: []astNode{ + { + Kind: ast.Array, + Children: []astNode{ + { + Kind: ast.String, + Data: []byte(`hello`), + }, + { + Kind: ast.String, + Data: []byte(`world`), }, }, - { - Kind: ast.Key, - Data: []byte(`A`), - }, + }, + { + Kind: ast.Key, + Data: []byte(`A`), }, }, }, @@ -338,33 +333,31 @@ func TestParser_AST(t *testing.T) { { desc: "inline table", input: `name = { first = "Tom", last = "Preston-Werner" }`, - ast: astRoot{ - astNode{ - Kind: ast.KeyValue, - Children: []astNode{ - { - Kind: ast.InlineTable, - Children: []astNode{ - { - Kind: ast.KeyValue, - Children: []astNode{ - {Kind: ast.String, Data: []byte(`Tom`)}, - {Kind: ast.Key, Data: []byte(`first`)}, - }, + ast: astNode{ + Kind: ast.KeyValue, + Children: []astNode{ + { + Kind: ast.InlineTable, + Children: []astNode{ + { + Kind: ast.KeyValue, + Children: []astNode{ + {Kind: ast.String, Data: []byte(`Tom`)}, + {Kind: ast.Key, Data: []byte(`first`)}, }, - { - Kind: ast.KeyValue, - Children: []astNode{ - {Kind: ast.String, Data: []byte(`Preston-Werner`)}, - {Kind: ast.Key, Data: []byte(`last`)}, - }, + }, + { + Kind: ast.KeyValue, + Children: []astNode{ + {Kind: ast.String, Data: []byte(`Preston-Werner`)}, + {Kind: ast.Key, Data: []byte(`last`)}, }, }, }, - { - Kind: ast.Key, - Data: []byte(`name`), - }, + }, + { + Kind: ast.Key, + Data: []byte(`name`), }, }, }, @@ -374,12 +367,14 @@ func TestParser_AST(t *testing.T) { for _, e := range examples { t.Run(e.desc, func(t *testing.T) { p := parser{} - err := p.parse([]byte(e.input)) + p.Reset([]byte(e.input)) + p.NextExpression() + err := p.Error() if e.err { require.Error(t, err) } else { require.NoError(t, err) - compareAST(t, e.ast, p.builder.Finish()) + compareNode(t, e.ast, p.Expression()) } }) } diff --git a/unmarshaler.go b/unmarshaler.go index 14c66b41..31d12e6f 100644 --- a/unmarshaler.go +++ b/unmarshaler.go @@ -11,13 +11,9 @@ import ( func Unmarshal(data []byte, v interface{}) error { p := parser{} - err := p.parse(data) - if err != nil { - return err - } - + p.Reset(data) d := decoder{} - return d.fromAst(p.builder.Finish(), v) + return d.FromParser(&p, v) } type decoder struct { @@ -41,7 +37,7 @@ func (d *decoder) arrayIndex(append bool, v reflect.Value) int { return idx } -func (d *decoder) fromAst(tree *ast.Root, v interface{}) error { +func (d *decoder) FromParser(p *parser, v interface{}) error { r := reflect.ValueOf(v) if r.Kind() != reflect.Ptr { return fmt.Errorf("need to target a pointer, not %s", r.Kind()) @@ -55,9 +51,8 @@ func (d *decoder) fromAst(tree *ast.Root, v interface{}) error { var root target = valueTarget(r.Elem()) current := root - it := tree.Iterator() - for it.Next() { - node := it.Node() + for p.NextExpression() { + node := p.Expression() var found bool switch node.Kind { case ast.KeyValue: @@ -83,7 +78,7 @@ func (d *decoder) fromAst(tree *ast.Root, v interface{}) error { } } - return nil + return p.Error() } // scopeWithKey performs target scoping when unmarshaling an ast.KeyValue node. diff --git a/unmarshaler_test.go b/unmarshaler_test.go index 8558e256..9acb3be5 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -1,13 +1,12 @@ -package toml +package toml_test import ( "math" "testing" + "github.com/pelletier/go-toml/v2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - - "github.com/pelletier/go-toml/v2/internal/ast" ) func TestUnmarshal_Integers(t *testing.T) { @@ -61,7 +60,7 @@ func TestUnmarshal_Integers(t *testing.T) { for _, e := range examples { t.Run(e.desc, func(t *testing.T) { doc := doc{} - err := Unmarshal([]byte(`A = `+e.input), &doc) + err := toml.Unmarshal([]byte(`A = `+e.input), &doc) require.NoError(t, err) assert.Equal(t, e.expected, doc.A) }) @@ -157,7 +156,7 @@ func TestUnmarshal_Floats(t *testing.T) { for _, e := range examples { t.Run(e.desc, func(t *testing.T) { doc := doc{} - err := Unmarshal([]byte(`A = `+e.input), &doc) + err := toml.Unmarshal([]byte(`A = `+e.input), &doc) require.NoError(t, err) if e.testFn != nil { e.testFn(t, doc.A) @@ -648,7 +647,7 @@ B = "data"`, if test.err && test.expected != nil { panic("invalid test: cannot expect both an error and a value") } - err := Unmarshal([]byte(e.input), test.target) + err := toml.Unmarshal([]byte(e.input), test.target) if test.err { require.Error(t, err) } else { @@ -658,312 +657,3 @@ B = "data"`, }) } } - -func TestFromAst_KV(t *testing.T) { - root := astRoot{ - astNode{ - Kind: ast.KeyValue, - Children: []astNode{ - { - Kind: ast.String, - Data: []byte(`hello`), - }, - { - Kind: ast.Key, - Data: []byte(`Foo`), - }, - }, - }, - } - - type Doc struct { - Foo string - } - - x := Doc{} - d := decoder{} - err := d.fromAst(root.toOrig(), &x) - require.NoError(t, err) - assert.Equal(t, Doc{Foo: "hello"}, x) -} - -func TestFromAst_Table(t *testing.T) { - t.Run("one level table on struct", func(t *testing.T) { - root := astRoot{ - astNode{ - Kind: ast.Table, - Children: []astNode{ - {Kind: ast.Key, Data: []byte(`Level1`)}, - }, - }, - astNode{ - Kind: ast.KeyValue, - Children: []astNode{ - { - Kind: ast.String, - Data: []byte(`hello`), - }, - { - Kind: ast.Key, - Data: []byte(`A`), - }, - }, - }, - astNode{ - Kind: ast.KeyValue, - Children: []astNode{ - { - Kind: ast.String, - Data: []byte(`world`), - }, - { - Kind: ast.Key, - Data: []byte(`B`), - }, - }, - }, - } - - type Level1 struct { - A string - B string - } - - type Doc struct { - Level1 Level1 - } - - x := Doc{} - d := decoder{} - err := d.fromAst(root.toOrig(), &x) - require.NoError(t, err) - assert.Equal(t, Doc{ - Level1: Level1{ - A: "hello", - B: "world", - }, - }, x) - }) - t.Run("one level table on struct", func(t *testing.T) { - root := astRoot{ - astNode{ - Kind: ast.Table, - Children: []astNode{ - {Kind: ast.Key, Data: []byte(`A`)}, - {Kind: ast.Key, Data: []byte(`B`)}, - }, - }, - astNode{ - Kind: ast.KeyValue, - Children: []astNode{ - { - Kind: ast.String, - Data: []byte(`value`), - }, - { - Kind: ast.Key, - Data: []byte(`C`), - }, - }, - }, - } - - type B struct { - C string - } - - type A struct { - B B - } - - type Doc struct { - A A - } - - x := Doc{} - d := decoder{} - err := d.fromAst(root.toOrig(), &x) - require.NoError(t, err) - assert.Equal(t, Doc{ - A: A{B: B{C: "value"}}, - }, x) - }) -} - -func TestFromAst_InlineTable(t *testing.T) { - t.Run("one level of strings", func(t *testing.T) { - root := astRoot{ - astNode{ - Kind: ast.KeyValue, - Children: []astNode{ - { - Kind: ast.InlineTable, - Children: []astNode{ - { - Kind: ast.KeyValue, - Children: []astNode{ - {Kind: ast.String, Data: []byte(`Tom`)}, - {Kind: ast.Key, Data: []byte(`First`)}, - }, - }, - { - Kind: ast.KeyValue, - Children: []astNode{ - {Kind: ast.String, Data: []byte(`Preston-Werner`)}, - {Kind: ast.Key, Data: []byte(`Last`)}, - }, - }, - }, - }, - { - Kind: ast.Key, - Data: []byte(`Name`), - }, - }, - }, - } - - type Name struct { - First string - Last string - } - - type Doc struct { - Name Name - } - - x := Doc{} - d := decoder{} - err := d.fromAst(root.toOrig(), &x) - require.NoError(t, err) - assert.Equal(t, Doc{ - Name: Name{ - First: "Tom", - Last: "Preston-Werner", - }, - }, x) - - }) -} - -func TestFromAst_Slice(t *testing.T) { - t.Run("slice of string", func(t *testing.T) { - root := astRoot{ - astNode{ - Kind: ast.KeyValue, - Children: []astNode{ - { - Kind: ast.Array, - Children: []astNode{ - { - Kind: ast.String, - Data: []byte(`hello`), - }, - { - Kind: ast.String, - Data: []byte(`world`), - }, - }, - }, - { - Kind: ast.Key, - Data: []byte(`Foo`), - }, - }, - }, - } - - type Doc struct { - Foo []string - } - - x := Doc{} - d := decoder{} - err := d.fromAst(root.toOrig(), &x) - require.NoError(t, err) - assert.Equal(t, Doc{Foo: []string{"hello", "world"}}, x) - }) - - t.Run("slice of interfaces for strings", func(t *testing.T) { - root := astRoot{ - astNode{ - Kind: ast.KeyValue, - Children: []astNode{ - { - Kind: ast.Array, - Children: []astNode{ - { - Kind: ast.String, - Data: []byte(`hello`), - }, - { - Kind: ast.String, - Data: []byte(`world`), - }, - }, - }, - { - Kind: ast.Key, - Data: []byte(`Foo`), - }, - }, - }, - } - - type Doc struct { - Foo []interface{} - } - - x := Doc{} - d := decoder{} - err := d.fromAst(root.toOrig(), &x) - require.NoError(t, err) - assert.Equal(t, Doc{Foo: []interface{}{"hello", "world"}}, x) - }) - - t.Run("slice of interfaces with slices", func(t *testing.T) { - root := astRoot{ - astNode{ - Kind: ast.KeyValue, - Children: []astNode{ - { - Kind: ast.Array, - Children: []astNode{ - { - Kind: ast.String, - Data: []byte(`hello`), - }, - { - Kind: ast.Array, - Children: []astNode{ - { - Kind: ast.String, - Data: []byte(`inner1`), - }, - { - Kind: ast.String, - Data: []byte(`inner2`), - }, - }, - }, - }, - }, - { - Kind: ast.Key, - Data: []byte(`Foo`), - }, - }, - }, - } - - type Doc struct { - Foo []interface{} - } - - x := Doc{} - d := decoder{} - err := d.fromAst(root.toOrig(), &x) - require.NoError(t, err) - assert.Equal(t, Doc{Foo: []interface{}{"hello", []interface{}{"inner1", "inner2"}}}, x) - }) -} From 636a75f3168dfd0ed406b3e1505cd91154d0aaf7 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Fri, 26 Mar 2021 09:51:35 -0400 Subject: [PATCH 126/228] Import tomltestgen Handful are failing. --- README.md | 1 + decode.go | 6 + parser.go | 4 + toml_testgen_support_test.go | 133 +++++ toml_testgen_test.go | 928 +++++++++++++++++++++++++++++++++++ 5 files changed, 1072 insertions(+) create mode 100644 toml_testgen_support_test.go create mode 100644 toml_testgen_test.go diff --git a/README.md b/README.md index 23d44a30..6a1ecb2c 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ Development branch. Probably does not work. - [x] Original go-toml unmarshal tests pass. - [x] Benchmark! - [x] Abstract AST. +- [ ] Original go-toml testgen tests pass. - [ ] Attach comments to AST (gated by parser flag). - [ ] Track file position (line, column) for errors. - [ ] Benchmark again! diff --git a/decode.go b/decode.go index b016d309..df43327b 100644 --- a/decode.go +++ b/decode.go @@ -200,6 +200,12 @@ func parseFloat(b []byte) (float64, error) { return 0, err } cleanedVal := cleanupNumberToken(tok) + if cleanedVal[0] == '.' { + return 0, fmt.Errorf("float cannot start with a dot") + } + if cleanedVal[len(cleanedVal)-1] == '.' { + return 0, fmt.Errorf("float cannot end with a dot") + } return strconv.ParseFloat(cleanedVal, 64) } diff --git a/parser.go b/parser.go index 25726bd2..8434fb92 100644 --- a/parser.go +++ b/parser.go @@ -1108,6 +1108,10 @@ func (p *parser) scanIntOrFloat(b []byte) (ast.Reference, []byte, error) { break } + if i == 0 { + return ast.Reference{}, b, fmt.Errorf("expected integer or float") + } + kind := ast.Integer if isFloat { diff --git a/toml_testgen_support_test.go b/toml_testgen_support_test.go new file mode 100644 index 00000000..3eef9df3 --- /dev/null +++ b/toml_testgen_support_test.go @@ -0,0 +1,133 @@ +// This is a support file for toml_testgen_test.go +package toml_test + +import ( + "encoding/json" + "fmt" + "log" + "strconv" + "testing" + "time" + + "github.com/pelletier/go-toml/v2" + "github.com/stretchr/testify/require" +) + +func testgenInvalid(t *testing.T, input string) { + t.Logf("Input TOML:\n%s", input) + + doc := map[string]interface{}{} + err := toml.Unmarshal([]byte(input), &doc) + + if err == nil { + t.Log(json.Marshal(doc)) + t.Fatalf("test did not fail") + } +} + +func testgenValid(t *testing.T, input string, jsonRef string) { + t.Logf("Input TOML:\n%s", input) + + doc := map[string]interface{}{} + err := toml.Unmarshal([]byte(input), &doc) + if err != nil { + t.Fatalf("failed parsing toml: %s", err) + } + + refDoc := testgenBuildRefDoc(jsonRef) + + require.Equal(t, refDoc, doc) +} + +type testGenDescNode struct { + Type string + Value interface{} +} + +func testgenBuildRefDoc(jsonRef string) map[string]interface{} { + descTree := map[string]interface{}{} + err := json.Unmarshal([]byte(jsonRef), &descTree) + if err != nil { + panic(fmt.Errorf("reference doc should be valid JSON: %s", err)) + } + + doc := testGenTranslateDesc(descTree) + if doc == nil { + return map[string]interface{}{} + } + return doc.(map[string]interface{}) +} + +func testGenTranslateDesc(input interface{}) interface{} { + a, ok := input.([]interface{}) + if ok { + xs := make([]interface{}, len(a)) + for i, v := range a { + xs[i] = testGenTranslateDesc(v) + } + return xs + } + + d := input.(map[string]interface{}) + + log.Printf("%+v", d) + var dtype string + var dvalue interface{} + + if len(d) == 2 { + dtypeiface, ok := d["type"] + if ok { + dvalue, ok = d["value"] + if ok { + dtype = dtypeiface.(string) + switch dtype { + case "string": + return dvalue.(string) + case "float": + v, err := strconv.ParseFloat(dvalue.(string), 64) + if err != nil { + panic(fmt.Errorf("invalid float '%s': %s", dvalue, err)) + } + return v + case "integer": + v, err := strconv.ParseInt(dvalue.(string), 10, 64) + if err != nil { + panic(fmt.Errorf("invalid int '%s': %s", dvalue, err)) + } + return v + case "bool": + return dvalue.(string) == "true" + case "datetime": + dt, err := time.Parse("2006-01-02T15:04:05Z", dvalue.(string)) + if err != nil { + panic(fmt.Errorf("invalid datetime '%s': %s", dvalue, err)) + } + return dt + case "array": + if dvalue == nil { + return nil + } + a := dvalue.([]interface{}) + xs := make([]interface{}, len(a)) + + for i, v := range a { + xs[i] = testGenTranslateDesc(v) + } + + return xs + } + panic(fmt.Errorf("unknown type: %s", dtype)) + } + } + } + + var dest interface{} + if len(d) > 0 { + x := map[string]interface{}{} + for k, v := range d { + x[k] = testGenTranslateDesc(v) + } + dest = x + } + return dest +} diff --git a/toml_testgen_test.go b/toml_testgen_test.go new file mode 100644 index 00000000..c850b0a4 --- /dev/null +++ b/toml_testgen_test.go @@ -0,0 +1,928 @@ +// Generated by tomltestgen for toml-test ref 39e37e6 on 2019-03-19T23:58:45-07:00 +package toml_test + +import ( + "testing" +) + +func TestInvalidDatetimeMalformedNoLeads(t *testing.T) { + input := `no-leads = 1987-7-05T17:45:00Z` + testgenInvalid(t, input) +} + +func TestInvalidDatetimeMalformedNoSecs(t *testing.T) { + input := `no-secs = 1987-07-05T17:45Z` + testgenInvalid(t, input) +} + +func TestInvalidDatetimeMalformedNoT(t *testing.T) { + input := `no-t = 1987-07-0517:45:00Z` + testgenInvalid(t, input) +} + +func TestInvalidDatetimeMalformedWithMilli(t *testing.T) { + input := `with-milli = 1987-07-5T17:45:00.12Z` + testgenInvalid(t, input) +} + +func TestInvalidDuplicateKeyTable(t *testing.T) { + input := `[fruit] +type = "apple" + +[fruit.type] +apple = "yes"` + testgenInvalid(t, input) +} + +func TestInvalidDuplicateKeys(t *testing.T) { + input := `dupe = false +dupe = true` + testgenInvalid(t, input) +} + +func TestInvalidDuplicateTables(t *testing.T) { + input := `[a] +[a]` + testgenInvalid(t, input) +} + +func TestInvalidEmptyImplicitTable(t *testing.T) { + input := `[naughty..naughty]` + testgenInvalid(t, input) +} + +func TestInvalidEmptyTable(t *testing.T) { + input := `[]` + testgenInvalid(t, input) +} + +func TestInvalidFloatNoLeadingZero(t *testing.T) { + input := `answer = .12345 +neganswer = -.12345` + testgenInvalid(t, input) +} + +func TestInvalidFloatNoTrailingDigits(t *testing.T) { + input := `answer = 1. +neganswer = -1.` + testgenInvalid(t, input) +} + +func TestInvalidKeyEmpty(t *testing.T) { + input := ` = 1` + testgenInvalid(t, input) +} + +func TestInvalidKeyHash(t *testing.T) { + input := `a# = 1` + testgenInvalid(t, input) +} + +func TestInvalidKeyNewline(t *testing.T) { + input := `a += 1` + testgenInvalid(t, input) +} + +func TestInvalidKeyOpenBracket(t *testing.T) { + input := `[abc = 1` + testgenInvalid(t, input) +} + +func TestInvalidKeySingleOpenBracket(t *testing.T) { + input := `[` + testgenInvalid(t, input) +} + +func TestInvalidKeySpace(t *testing.T) { + input := `a b = 1` + testgenInvalid(t, input) +} + +func TestInvalidKeyStartBracket(t *testing.T) { + input := `[a] +[xyz = 5 +[b]` + testgenInvalid(t, input) +} + +func TestInvalidKeyTwoEquals(t *testing.T) { + input := `key= = 1` + testgenInvalid(t, input) +} + +func TestInvalidStringBadByteEscape(t *testing.T) { + input := `naughty = "\xAg"` + testgenInvalid(t, input) +} + +func TestInvalidStringBadEscape(t *testing.T) { + input := `invalid-escape = "This string has a bad \a escape character."` + testgenInvalid(t, input) +} + +func TestInvalidStringByteEscapes(t *testing.T) { + input := `answer = "\x33"` + testgenInvalid(t, input) +} + +func TestInvalidStringNoClose(t *testing.T) { + input := `no-ending-quote = "One time, at band camp` + testgenInvalid(t, input) +} + +func TestInvalidTableArrayImplicit(t *testing.T) { + input := "# This test is a bit tricky. It should fail because the first use of\n" + + "# `[[albums.songs]]` without first declaring `albums` implies that `albums`\n" + + "# must be a table. The alternative would be quite weird. Namely, it wouldn't\n" + + "# comply with the TOML spec: \"Each double-bracketed sub-table will belong to \n" + + "# the most *recently* defined table element *above* it.\"\n" + + "#\n" + + "# This is in contrast to the *valid* test, table-array-implicit where\n" + + "# `[[albums.songs]]` works by itself, so long as `[[albums]]` isn't declared\n" + + "# later. (Although, `[albums]` could be.)\n" + + "[[albums.songs]]\n" + + "name = \"Glory Days\"\n" + + "\n" + + "[[albums]]\n" + + "name = \"Born in the USA\"\n" + testgenInvalid(t, input) +} + +func TestInvalidTableArrayMalformedBracket(t *testing.T) { + input := `[[albums] +name = "Born to Run"` + testgenInvalid(t, input) +} + +func TestInvalidTableArrayMalformedEmpty(t *testing.T) { + input := `[[]] +name = "Born to Run"` + testgenInvalid(t, input) +} + +func TestInvalidTableEmpty(t *testing.T) { + input := `[]` + testgenInvalid(t, input) +} + +func TestInvalidTableNestedBracketsClose(t *testing.T) { + input := `[a]b] +zyx = 42` + testgenInvalid(t, input) +} + +func TestInvalidTableNestedBracketsOpen(t *testing.T) { + input := `[a[b] +zyx = 42` + testgenInvalid(t, input) +} + +func TestInvalidTableWhitespace(t *testing.T) { + input := `[invalid key]` + testgenInvalid(t, input) +} + +func TestInvalidTableWithPound(t *testing.T) { + input := `[key#group] +answer = 42` + testgenInvalid(t, input) +} + +func TestInvalidTextAfterArrayEntries(t *testing.T) { + input := `array = [ + "Is there life after an array separator?", No + "Entry" +]` + testgenInvalid(t, input) +} + +func TestInvalidTextAfterInteger(t *testing.T) { + input := `answer = 42 the ultimate answer?` + testgenInvalid(t, input) +} + +func TestInvalidTextAfterString(t *testing.T) { + input := `string = "Is there life after strings?" No.` + testgenInvalid(t, input) +} + +func TestInvalidTextAfterTable(t *testing.T) { + input := `[error] this shouldn't be here` + testgenInvalid(t, input) +} + +func TestInvalidTextBeforeArraySeparator(t *testing.T) { + input := `array = [ + "Is there life before an array separator?" No, + "Entry" +]` + testgenInvalid(t, input) +} + +func TestInvalidTextInArray(t *testing.T) { + input := `array = [ + "Entry 1", + I don't belong, + "Entry 2", +]` + testgenInvalid(t, input) +} + +func TestValidArrayEmpty(t *testing.T) { + input := `thevoid = [[[[[]]]]]` + jsonRef := `{ + "thevoid": { "type": "array", "value": [ + {"type": "array", "value": [ + {"type": "array", "value": [ + {"type": "array", "value": [ + {"type": "array", "value": []} + ]} + ]} + ]} + ]} +}` + testgenValid(t, input, jsonRef) +} + +func TestValidArrayNospaces(t *testing.T) { + input := `ints = [1,2,3]` + jsonRef := `{ + "ints": { + "type": "array", + "value": [ + {"type": "integer", "value": "1"}, + {"type": "integer", "value": "2"}, + {"type": "integer", "value": "3"} + ] + } +}` + testgenValid(t, input, jsonRef) +} + +func TestValidArraysHetergeneous(t *testing.T) { + input := `mixed = [[1, 2], ["a", "b"], [1.1, 2.1]]` + jsonRef := `{ + "mixed": { + "type": "array", + "value": [ + {"type": "array", "value": [ + {"type": "integer", "value": "1"}, + {"type": "integer", "value": "2"} + ]}, + {"type": "array", "value": [ + {"type": "string", "value": "a"}, + {"type": "string", "value": "b"} + ]}, + {"type": "array", "value": [ + {"type": "float", "value": "1.1"}, + {"type": "float", "value": "2.1"} + ]} + ] + } +}` + testgenValid(t, input, jsonRef) +} + +func TestValidArraysNested(t *testing.T) { + input := `nest = [["a"], ["b"]]` + jsonRef := `{ + "nest": { + "type": "array", + "value": [ + {"type": "array", "value": [ + {"type": "string", "value": "a"} + ]}, + {"type": "array", "value": [ + {"type": "string", "value": "b"} + ]} + ] + } +}` + testgenValid(t, input, jsonRef) +} + +func TestValidArrays(t *testing.T) { + input := `ints = [1, 2, 3] +floats = [1.1, 2.1, 3.1] +strings = ["a", "b", "c"] +dates = [ + 1987-07-05T17:45:00Z, + 1979-05-27T07:32:00Z, + 2006-06-01T11:00:00Z, +]` + jsonRef := `{ + "ints": { + "type": "array", + "value": [ + {"type": "integer", "value": "1"}, + {"type": "integer", "value": "2"}, + {"type": "integer", "value": "3"} + ] + }, + "floats": { + "type": "array", + "value": [ + {"type": "float", "value": "1.1"}, + {"type": "float", "value": "2.1"}, + {"type": "float", "value": "3.1"} + ] + }, + "strings": { + "type": "array", + "value": [ + {"type": "string", "value": "a"}, + {"type": "string", "value": "b"}, + {"type": "string", "value": "c"} + ] + }, + "dates": { + "type": "array", + "value": [ + {"type": "datetime", "value": "1987-07-05T17:45:00Z"}, + {"type": "datetime", "value": "1979-05-27T07:32:00Z"}, + {"type": "datetime", "value": "2006-06-01T11:00:00Z"} + ] + } +}` + testgenValid(t, input, jsonRef) +} + +func TestValidBool(t *testing.T) { + input := `t = true +f = false` + jsonRef := `{ + "f": {"type": "bool", "value": "false"}, + "t": {"type": "bool", "value": "true"} +}` + testgenValid(t, input, jsonRef) +} + +func TestValidCommentsEverywhere(t *testing.T) { + input := `# Top comment. + # Top comment. +# Top comment. + +# [no-extraneous-groups-please] + +[group] # Comment +answer = 42 # Comment +# no-extraneous-keys-please = 999 +# Inbetween comment. +more = [ # Comment + # What about multiple # comments? + # Can you handle it? + # + # Evil. +# Evil. + 42, 42, # Comments within arrays are fun. + # What about multiple # comments? + # Can you handle it? + # + # Evil. +# Evil. +# ] Did I fool you? +] # Hopefully not.` + jsonRef := `{ + "group": { + "answer": {"type": "integer", "value": "42"}, + "more": { + "type": "array", + "value": [ + {"type": "integer", "value": "42"}, + {"type": "integer", "value": "42"} + ] + } + } +}` + testgenValid(t, input, jsonRef) +} + +func TestValidDatetime(t *testing.T) { + input := `bestdayever = 1987-07-05T17:45:00Z` + jsonRef := `{ + "bestdayever": {"type": "datetime", "value": "1987-07-05T17:45:00Z"} +}` + testgenValid(t, input, jsonRef) +} + +func TestValidEmpty(t *testing.T) { + input := `` + jsonRef := `{}` + testgenValid(t, input, jsonRef) +} + +func TestValidExample(t *testing.T) { + input := `best-day-ever = 1987-07-05T17:45:00Z + +[numtheory] +boring = false +perfection = [6, 28, 496]` + jsonRef := `{ + "best-day-ever": {"type": "datetime", "value": "1987-07-05T17:45:00Z"}, + "numtheory": { + "boring": {"type": "bool", "value": "false"}, + "perfection": { + "type": "array", + "value": [ + {"type": "integer", "value": "6"}, + {"type": "integer", "value": "28"}, + {"type": "integer", "value": "496"} + ] + } + } +}` + testgenValid(t, input, jsonRef) +} + +func TestValidFloat(t *testing.T) { + input := `pi = 3.14 +negpi = -3.14` + jsonRef := `{ + "pi": {"type": "float", "value": "3.14"}, + "negpi": {"type": "float", "value": "-3.14"} +}` + testgenValid(t, input, jsonRef) +} + +func TestValidImplicitAndExplicitAfter(t *testing.T) { + input := `[a.b.c] +answer = 42 + +[a] +better = 43` + jsonRef := `{ + "a": { + "better": {"type": "integer", "value": "43"}, + "b": { + "c": { + "answer": {"type": "integer", "value": "42"} + } + } + } +}` + testgenValid(t, input, jsonRef) +} + +func TestValidImplicitAndExplicitBefore(t *testing.T) { + input := `[a] +better = 43 + +[a.b.c] +answer = 42` + jsonRef := `{ + "a": { + "better": {"type": "integer", "value": "43"}, + "b": { + "c": { + "answer": {"type": "integer", "value": "42"} + } + } + } +}` + testgenValid(t, input, jsonRef) +} + +func TestValidImplicitGroups(t *testing.T) { + input := `[a.b.c] +answer = 42` + jsonRef := `{ + "a": { + "b": { + "c": { + "answer": {"type": "integer", "value": "42"} + } + } + } +}` + testgenValid(t, input, jsonRef) +} + +func TestValidInteger(t *testing.T) { + input := `answer = 42 +neganswer = -42` + jsonRef := `{ + "answer": {"type": "integer", "value": "42"}, + "neganswer": {"type": "integer", "value": "-42"} +}` + testgenValid(t, input, jsonRef) +} + +func TestValidKeyEqualsNospace(t *testing.T) { + input := `answer=42` + jsonRef := `{ + "answer": {"type": "integer", "value": "42"} +}` + testgenValid(t, input, jsonRef) +} + +func TestValidKeySpace(t *testing.T) { + input := `"a b" = 1` + jsonRef := `{ + "a b": {"type": "integer", "value": "1"} +}` + testgenValid(t, input, jsonRef) +} + +func TestValidKeySpecialChars(t *testing.T) { + input := "\"~!@$^&*()_+-`1234567890[]|/?><.,;:'\" = 1\n" + jsonRef := "{\n" + + " \"~!@$^&*()_+-`1234567890[]|/?><.,;:'\": {\n" + + " \"type\": \"integer\", \"value\": \"1\"\n" + + " }\n" + + "}\n" + testgenValid(t, input, jsonRef) +} + +func TestValidLongFloat(t *testing.T) { + input := `longpi = 3.141592653589793 +neglongpi = -3.141592653589793` + jsonRef := `{ + "longpi": {"type": "float", "value": "3.141592653589793"}, + "neglongpi": {"type": "float", "value": "-3.141592653589793"} +}` + testgenValid(t, input, jsonRef) +} + +func TestValidLongInteger(t *testing.T) { + input := `answer = 9223372036854775807 +neganswer = -9223372036854775808` + jsonRef := `{ + "answer": {"type": "integer", "value": "9223372036854775807"}, + "neganswer": {"type": "integer", "value": "-9223372036854775808"} +}` + testgenValid(t, input, jsonRef) +} + +func TestValidMultilineString(t *testing.T) { + input := `multiline_empty_one = """""" +multiline_empty_two = """ +""" +multiline_empty_three = """\ + """ +multiline_empty_four = """\ + \ + \ + """ + +equivalent_one = "The quick brown fox jumps over the lazy dog." +equivalent_two = """ +The quick brown \ + + + fox jumps over \ + the lazy dog.""" + +equivalent_three = """\ + The quick brown \ + fox jumps over \ + the lazy dog.\ + """` + jsonRef := `{ + "multiline_empty_one": { + "type": "string", + "value": "" + }, + "multiline_empty_two": { + "type": "string", + "value": "" + }, + "multiline_empty_three": { + "type": "string", + "value": "" + }, + "multiline_empty_four": { + "type": "string", + "value": "" + }, + "equivalent_one": { + "type": "string", + "value": "The quick brown fox jumps over the lazy dog." + }, + "equivalent_two": { + "type": "string", + "value": "The quick brown fox jumps over the lazy dog." + }, + "equivalent_three": { + "type": "string", + "value": "The quick brown fox jumps over the lazy dog." + } +}` + testgenValid(t, input, jsonRef) +} + +func TestValidRawMultilineString(t *testing.T) { + input := `oneline = '''This string has a ' quote character.''' +firstnl = ''' +This string has a ' quote character.''' +multiline = ''' +This string +has ' a quote character +and more than +one newline +in it.'''` + jsonRef := `{ + "oneline": { + "type": "string", + "value": "This string has a ' quote character." + }, + "firstnl": { + "type": "string", + "value": "This string has a ' quote character." + }, + "multiline": { + "type": "string", + "value": "This string\nhas ' a quote character\nand more than\none newline\nin it." + } +}` + testgenValid(t, input, jsonRef) +} + +func TestValidRawString(t *testing.T) { + input := `backspace = 'This string has a \b backspace character.' +tab = 'This string has a \t tab character.' +newline = 'This string has a \n new line character.' +formfeed = 'This string has a \f form feed character.' +carriage = 'This string has a \r carriage return character.' +slash = 'This string has a \/ slash character.' +backslash = 'This string has a \\ backslash character.'` + jsonRef := `{ + "backspace": { + "type": "string", + "value": "This string has a \\b backspace character." + }, + "tab": { + "type": "string", + "value": "This string has a \\t tab character." + }, + "newline": { + "type": "string", + "value": "This string has a \\n new line character." + }, + "formfeed": { + "type": "string", + "value": "This string has a \\f form feed character." + }, + "carriage": { + "type": "string", + "value": "This string has a \\r carriage return character." + }, + "slash": { + "type": "string", + "value": "This string has a \\/ slash character." + }, + "backslash": { + "type": "string", + "value": "This string has a \\\\ backslash character." + } +}` + testgenValid(t, input, jsonRef) +} + +func TestValidStringEmpty(t *testing.T) { + input := `answer = ""` + jsonRef := `{ + "answer": { + "type": "string", + "value": "" + } +}` + testgenValid(t, input, jsonRef) +} + +func TestValidStringEscapes(t *testing.T) { + input := `backspace = "This string has a \b backspace character." +tab = "This string has a \t tab character." +newline = "This string has a \n new line character." +formfeed = "This string has a \f form feed character." +carriage = "This string has a \r carriage return character." +quote = "This string has a \" quote character." +backslash = "This string has a \\ backslash character." +notunicode1 = "This string does not have a unicode \\u escape." +notunicode2 = "This string does not have a unicode \u005Cu escape." +notunicode3 = "This string does not have a unicode \\u0075 escape." +notunicode4 = "This string does not have a unicode \\\u0075 escape."` + jsonRef := `{ + "backspace": { + "type": "string", + "value": "This string has a \u0008 backspace character." + }, + "tab": { + "type": "string", + "value": "This string has a \u0009 tab character." + }, + "newline": { + "type": "string", + "value": "This string has a \u000A new line character." + }, + "formfeed": { + "type": "string", + "value": "This string has a \u000C form feed character." + }, + "carriage": { + "type": "string", + "value": "This string has a \u000D carriage return character." + }, + "quote": { + "type": "string", + "value": "This string has a \u0022 quote character." + }, + "backslash": { + "type": "string", + "value": "This string has a \u005C backslash character." + }, + "notunicode1": { + "type": "string", + "value": "This string does not have a unicode \\u escape." + }, + "notunicode2": { + "type": "string", + "value": "This string does not have a unicode \u005Cu escape." + }, + "notunicode3": { + "type": "string", + "value": "This string does not have a unicode \\u0075 escape." + }, + "notunicode4": { + "type": "string", + "value": "This string does not have a unicode \\\u0075 escape." + } +}` + testgenValid(t, input, jsonRef) +} + +func TestValidStringSimple(t *testing.T) { + input := `answer = "You are not drinking enough whisky."` + jsonRef := `{ + "answer": { + "type": "string", + "value": "You are not drinking enough whisky." + } +}` + testgenValid(t, input, jsonRef) +} + +func TestValidStringWithPound(t *testing.T) { + input := `pound = "We see no # comments here." +poundcomment = "But there are # some comments here." # Did I # mess you up?` + jsonRef := `{ + "pound": {"type": "string", "value": "We see no # comments here."}, + "poundcomment": { + "type": "string", + "value": "But there are # some comments here." + } +}` + testgenValid(t, input, jsonRef) +} + +func TestValidTableArrayImplicit(t *testing.T) { + input := `[[albums.songs]] +name = "Glory Days"` + jsonRef := `{ + "albums": { + "songs": [ + {"name": {"type": "string", "value": "Glory Days"}} + ] + } +}` + testgenValid(t, input, jsonRef) +} + +func TestValidTableArrayMany(t *testing.T) { + input := `[[people]] +first_name = "Bruce" +last_name = "Springsteen" + +[[people]] +first_name = "Eric" +last_name = "Clapton" + +[[people]] +first_name = "Bob" +last_name = "Seger"` + jsonRef := `{ + "people": [ + { + "first_name": {"type": "string", "value": "Bruce"}, + "last_name": {"type": "string", "value": "Springsteen"} + }, + { + "first_name": {"type": "string", "value": "Eric"}, + "last_name": {"type": "string", "value": "Clapton"} + }, + { + "first_name": {"type": "string", "value": "Bob"}, + "last_name": {"type": "string", "value": "Seger"} + } + ] +}` + testgenValid(t, input, jsonRef) +} + +func TestValidTableArrayNest(t *testing.T) { + input := `[[albums]] +name = "Born to Run" + + [[albums.songs]] + name = "Jungleland" + + [[albums.songs]] + name = "Meeting Across the River" + +[[albums]] +name = "Born in the USA" + + [[albums.songs]] + name = "Glory Days" + + [[albums.songs]] + name = "Dancing in the Dark"` + jsonRef := `{ + "albums": [ + { + "name": {"type": "string", "value": "Born to Run"}, + "songs": [ + {"name": {"type": "string", "value": "Jungleland"}}, + {"name": {"type": "string", "value": "Meeting Across the River"}} + ] + }, + { + "name": {"type": "string", "value": "Born in the USA"}, + "songs": [ + {"name": {"type": "string", "value": "Glory Days"}}, + {"name": {"type": "string", "value": "Dancing in the Dark"}} + ] + } + ] +}` + testgenValid(t, input, jsonRef) +} + +func TestValidTableArrayOne(t *testing.T) { + input := `[[people]] +first_name = "Bruce" +last_name = "Springsteen"` + jsonRef := `{ + "people": [ + { + "first_name": {"type": "string", "value": "Bruce"}, + "last_name": {"type": "string", "value": "Springsteen"} + } + ] +}` + testgenValid(t, input, jsonRef) +} + +func TestValidTableEmpty(t *testing.T) { + input := `[a]` + jsonRef := `{ + "a": {} +}` + testgenValid(t, input, jsonRef) +} + +func TestValidTableSubEmpty(t *testing.T) { + input := `[a] +[a.b]` + jsonRef := `{ + "a": { "b": {} } +}` + testgenValid(t, input, jsonRef) +} + +func TestValidTableWhitespace(t *testing.T) { + input := `["valid key"]` + jsonRef := `{ + "valid key": {} +}` + testgenValid(t, input, jsonRef) +} + +func TestValidTableWithPound(t *testing.T) { + input := `["key#group"] +answer = 42` + jsonRef := `{ + "key#group": { + "answer": {"type": "integer", "value": "42"} + } +}` + testgenValid(t, input, jsonRef) +} + +func TestValidUnicodeEscape(t *testing.T) { + input := `answer4 = "\u03B4" +answer8 = "\U000003B4"` + jsonRef := `{ + "answer4": {"type": "string", "value": "\u03B4"}, + "answer8": {"type": "string", "value": "\u03B4"} +}` + testgenValid(t, input, jsonRef) +} + +func TestValidUnicodeLiteral(t *testing.T) { + input := `answer = "δ"` + jsonRef := `{ + "answer": {"type": "string", "value": "δ"} +}` + testgenValid(t, input, jsonRef) +} From 317b36b24b862609de3f851e52ddc281c6db0212 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Fri, 26 Mar 2021 09:53:21 -0400 Subject: [PATCH 127/228] Add back license --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..3a38ac28 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2013 - 2021 Thomas Pelletier, Eric Anderton + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From e5a091a092705a18edab537abfa0b87f7ac6eec3 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Sat, 27 Mar 2021 23:43:24 -0400 Subject: [PATCH 128/228] Don't depend on my computer path --- benchmark/go.mod | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/benchmark/go.mod b/benchmark/go.mod index 9e09e18c..c1bf154e 100644 --- a/benchmark/go.mod +++ b/benchmark/go.mod @@ -4,11 +4,10 @@ go 1.16 replace github.com/pelletier/go-toml/v2 => ../ -replace github.com/pelletier/go-toml-v1 => /home/thomas/src/github.com/pelletier/go-toml-v1 +replace github.com/pelletier/go-toml-v1 => github.com/pelletier/go-toml v1.8.1 require ( github.com/BurntSushi/toml v0.3.1 - github.com/pelletier/go-toml v1.8.1 // indirect github.com/pelletier/go-toml-v1 v0.0.0-00010101000000-000000000000 github.com/pelletier/go-toml/v2 v2.0.0-00010101000000-000000000000 github.com/stretchr/testify v1.7.0 From 72c999ecbf4d4109e87288c22382f35f037afeb1 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Sun, 28 Mar 2021 00:04:25 -0400 Subject: [PATCH 129/228] Fix trailing commas in arrays --- parser.go | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/parser.go b/parser.go index 8434fb92..6e5b36c4 100644 --- a/parser.go +++ b/parser.go @@ -380,6 +380,11 @@ func (p *parser) parseValArray(b []byte) (ast.Reference, []byte, error) { } } + // TOML allows trailing commas in arrays. + if len(b) > 0 && b[0] == ']' { + break + } + var valueRef ast.Reference valueRef, b, err = p.parseVal(b) if err != nil { @@ -406,18 +411,25 @@ func (p *parser) parseValArray(b []byte) (ast.Reference, []byte, error) { } func (p *parser) parseOptionalWhitespaceCommentNewline(b []byte) ([]byte, error) { - var err error - b = p.parseWhitespace(b) - if len(b) > 0 && b[0] == '#' { - _, b, err = scanComment(b) - if err != nil { - return nil, err + for len(b) > 0 { + var err error + b = p.parseWhitespace(b) + if len(b) > 0 && b[0] == '#' { + _, b, err = scanComment(b) + if err != nil { + return nil, err + } } - } - if len(b) > 0 && (b[0] == '\n' || b[0] == '\r') { - b, err = p.parseNewline(b) - if err != nil { - return nil, err + if len(b) == 0 { + break + } + if b[0] == '\n' || b[0] == '\r' { + b, err = p.parseNewline(b) + if err != nil { + return nil, err + } + } else { + break } } return b, nil From 9a436c7eeb91e0bd910e8191020191b6fbc13678 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Sun, 28 Mar 2021 00:06:40 -0400 Subject: [PATCH 130/228] Remove logging in test --- toml_testgen_support_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/toml_testgen_support_test.go b/toml_testgen_support_test.go index 3eef9df3..fa83cdb6 100644 --- a/toml_testgen_support_test.go +++ b/toml_testgen_support_test.go @@ -4,7 +4,6 @@ package toml_test import ( "encoding/json" "fmt" - "log" "strconv" "testing" "time" @@ -70,7 +69,6 @@ func testGenTranslateDesc(input interface{}) interface{} { d := input.(map[string]interface{}) - log.Printf("%+v", d) var dtype string var dvalue interface{} From 7dc5550057dd9971f02a5689c16f5660ad32565a Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Sun, 28 Mar 2021 00:17:58 -0400 Subject: [PATCH 131/228] Fix multiline basic string parsing --- parser.go | 1 + unmarshaler_test.go | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/parser.go b/parser.go index 6e5b36c4..867ef0f5 100644 --- a/parser.go +++ b/parser.go @@ -493,6 +493,7 @@ func (p *parser) parseMultilineBasicString(b []byte) ([]byte, []byte, error) { for ; i < len(token)-3; i++ { c := token[i] if !(c == '\n' || c == '\r' || c == ' ' || c == '\t') { + i-- break } } diff --git a/unmarshaler_test.go b/unmarshaler_test.go index 9acb3be5..c84e866a 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -192,6 +192,20 @@ func TestUnmarshal(t *testing.T) { } }, }, + { + desc: "multiline basic string", + input: `A = """\ + Test"""`, + gen: func() test { + type doc struct { + A string + } + return test{ + target: &doc{}, + expected: &doc{A: "Test"}, + } + }, + }, { desc: "kv bool true", input: `A = true`, From b24eb93e8e1ba0a7463f19a10e1a6665fdb877b9 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Sun, 28 Mar 2021 00:23:50 -0400 Subject: [PATCH 132/228] Fix literal multiline parsing --- parser.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/parser.go b/parser.go index 867ef0f5..79924d84 100644 --- a/parser.go +++ b/parser.go @@ -449,8 +449,7 @@ func (p *parser) parseMultilineLiteralString(b []byte) ([]byte, []byte, error) { } else if token[i] == '\r' && token[i+1] == '\n' { i += 2 } - - return token[i : len(b)-3], rest, err + return token[i : len(token)-3], rest, err } func (p *parser) parseMultilineBasicString(b []byte) ([]byte, []byte, error) { From 829c0057842cd9c69cbcb7103fcefc2369ceecc8 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Sun, 28 Mar 2021 11:03:43 -0400 Subject: [PATCH 133/228] Fix unicode decoding --- parser.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/parser.go b/parser.go index 79924d84..8f867ca4 100644 --- a/parser.go +++ b/parser.go @@ -2,8 +2,8 @@ package toml import ( "bytes" - "encoding/hex" "fmt" + "strconv" "time" "github.com/pelletier/go-toml/v2/internal/ast" @@ -381,7 +381,7 @@ func (p *parser) parseValArray(b []byte) (ast.Reference, []byte, error) { } // TOML allows trailing commas in arrays. - if len(b) > 0 && b[0] == ']' { + if len(b) > 0 && b[0] == ']' { break } @@ -677,11 +677,11 @@ func hexToString(b []byte, length int) (string, error) { return "", fmt.Errorf("unicode point needs %d hex characters", length) } // TODO: slow - b, err := hex.DecodeString(string(b[:length])) + intcode, err := strconv.ParseInt(string(b[:length]), 16, 32) if err != nil { return "", err } - return string(b), nil + return string(rune(intcode)), nil } func (p *parser) parseWhitespace(b []byte) []byte { From da21b0aecfa1c8f3616dc80c6d944f9320c7f7aa Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Sun, 28 Mar 2021 22:12:19 -0400 Subject: [PATCH 134/228] wip: correctness pass on the AST --- internal/tracker/tracker.go | 98 +++++++++++++++++++++++++++++++++++++ unmarshaler.go | 18 +++++-- 2 files changed, 112 insertions(+), 4 deletions(-) create mode 100644 internal/tracker/tracker.go diff --git a/internal/tracker/tracker.go b/internal/tracker/tracker.go new file mode 100644 index 00000000..8359e2a0 --- /dev/null +++ b/internal/tracker/tracker.go @@ -0,0 +1,98 @@ +package tracker + +import ( + "fmt" + + "github.com/pelletier/go-toml/v2/internal/ast" +) + +type keyKind uint8 + +const ( + invalid keyKind = iota // also used for the root key + value + table + arrayTable +) + +type key string + +type builder struct { + prefix [][]byte + local [][]byte +} + +func (b *builder) Reset(prefix [][]byte) { + b.prefix = prefix + b.local = b.local[:0] +} + +// Computes the number of bytes required to store the full key. +func (b *builder) size() int { + size := len(b.prefix) + len(b.local) - 1 + for _, p := range b.prefix { + size += len(p) + } + for _, p := range b.local { + size += len(p) + } + return size +} + +func (b *builder) copy(firstJoin bool, from [][]byte, to []byte) int { + offset := 0 + for i, p := range from { + if i > 0 || firstJoin { + to[offset] = 0x1E + offset++ + } + copy(to[offset:], p) + offset += len(p) + } + return offset +} + +func (b *builder) MakeKey() key { + k := make([]byte, b.size()) + b.copy(false, b.prefix, k) + b.copy(len(b.prefix) > 0, b.local, k) + return key(k) +} + +func (b *builder) Append(k []byte) { + b.local = append(b.local, k) +} + +// Tracks which keys have been seen with which TOML type to flag duplicates +// and mismatches according to the spec. +type Seen struct { + keys map[key]keyKind + + // scoping from the previous CheckExpression call. + current [][]byte + + // key builder + builder builder +} + +// CheckExpression takes a top-level node and checks that it does not contain keys +// that have been seen in previous calls, and validates that types are consistent. +func (s *Seen) CheckExpression(node ast.Node) error { + s.builder.Reset(s.current) + switch node.Kind { + case ast.KeyValue: + return s.checkKeyValue(node) + case ast.Table: + case ast.ArrayTable: + default: + panic(fmt.Errorf("this should not be a top level node type: %s", node.Kind)) + } + return nil +} + +func (s *Seen) checkKeyValue(node ast.Node) error { + it := node.Key() + for it.Next() { + s.builder.Append(it.Node().Data) + } +} diff --git a/unmarshaler.go b/unmarshaler.go index 31d12e6f..59c63348 100644 --- a/unmarshaler.go +++ b/unmarshaler.go @@ -7,6 +7,7 @@ import ( "time" "github.com/pelletier/go-toml/v2/internal/ast" + "github.com/pelletier/go-toml/v2/internal/tracker" ) func Unmarshal(data []byte, v interface{}) error { @@ -19,6 +20,9 @@ func Unmarshal(data []byte, v interface{}) error { type decoder struct { // Tracks position in Go arrays. arrayIndexes map[reflect.Value]int + + // Tracks keys that have been seen, with which type. + seen tracker.Seen } func (d *decoder) arrayIndex(append bool, v reflect.Value) int { @@ -46,19 +50,25 @@ func (d *decoder) FromParser(p *parser, v interface{}) error { return fmt.Errorf("target pointer must be non-nil") } - var err error var skipUntilTable bool var root target = valueTarget(r.Elem()) current := root for p.NextExpression() { node := p.Expression() + + if node.Kind == ast.KeyValue && skipUntilTable { + continue + } + + err := d.seen.CheckExpression(node) + if err != nil { + return err + } + var found bool switch node.Kind { case ast.KeyValue: - if skipUntilTable { - continue - } err = d.unmarshalKeyValue(current, node) found = true case ast.Table: From 2ddbf6be6dfa4692a010ac2f255f3e04fd866c0d Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Mon, 29 Mar 2021 10:45:50 -0400 Subject: [PATCH 135/228] Implement duplicate and key types check --- README.md | 3 +- internal/tracker/tracker.go | 204 +++++++++++++++++++++++++++--------- 2 files changed, 155 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index 6a1ecb2c..6da36e88 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Development branch. Probably does not work. - [x] Original go-toml unmarshal tests pass. - [x] Benchmark! - [x] Abstract AST. -- [ ] Original go-toml testgen tests pass. +- [x] Original go-toml testgen tests pass. - [ ] Attach comments to AST (gated by parser flag). - [ ] Track file position (line, column) for errors. - [ ] Benchmark again! @@ -28,6 +28,7 @@ Development branch. Probably does not work. - [ ] Provide "minimal allocations" option that uses `unsafe` to reuse the input byte array as storage for strings. - [x] Cache reflection operations per type. +- [ ] Optimize tracker pass. ## Ideas diff --git a/internal/tracker/tracker.go b/internal/tracker/tracker.go index 8359e2a0..a6f6252e 100644 --- a/internal/tracker/tracker.go +++ b/internal/tracker/tracker.go @@ -9,90 +9,192 @@ import ( type keyKind uint8 const ( - invalid keyKind = iota // also used for the root key - value - table - arrayTable + invalidKind keyKind = iota + valueKind + tableKind + arrayTableKind ) -type key string +func (k keyKind) String() string { + switch k { + case invalidKind: + return "invalid" + case valueKind: + return "value" + case tableKind: + return "table" + case arrayTableKind: + return "array table" + } + panic("missing keyKind string mapping") +} -type builder struct { - prefix [][]byte - local [][]byte +// Tracks which keys have been seen with which TOML type to flag duplicates +// and mismatches according to the spec. +type Seen struct { + root *info + current *info } -func (b *builder) Reset(prefix [][]byte) { - b.prefix = prefix - b.local = b.local[:0] +type info struct { + parent *info + kind keyKind + children map[string]*info + explicit bool } -// Computes the number of bytes required to store the full key. -func (b *builder) size() int { - size := len(b.prefix) + len(b.local) - 1 - for _, p := range b.prefix { - size += len(p) - } - for _, p := range b.local { - size += len(p) - } - return size +func (i *info) Clear() { + i.children = nil } -func (b *builder) copy(firstJoin bool, from [][]byte, to []byte) int { - offset := 0 - for i, p := range from { - if i > 0 || firstJoin { - to[offset] = 0x1E - offset++ - } - copy(to[offset:], p) - offset += len(p) - } - return offset +func (i *info) Has(k string) (*info, bool) { + c, ok := i.children[k] + return c, ok } -func (b *builder) MakeKey() key { - k := make([]byte, b.size()) - b.copy(false, b.prefix, k) - b.copy(len(b.prefix) > 0, b.local, k) - return key(k) +func (i *info) SetKind(kind keyKind) { + i.kind = kind } -func (b *builder) Append(k []byte) { - b.local = append(b.local, k) +func (i *info) CreateTable(k string, explicit bool) *info { + return i.createChild(k, tableKind, explicit) } -// Tracks which keys have been seen with which TOML type to flag duplicates -// and mismatches according to the spec. -type Seen struct { - keys map[key]keyKind +func (i *info) CreateArrayTable(k string, explicit bool) *info { + return i.createChild(k, arrayTableKind, explicit) +} - // scoping from the previous CheckExpression call. - current [][]byte +func (i *info) createChild(k string, kind keyKind, explicit bool) *info { + if i.children == nil { + i.children = make(map[string]*info, 1) + } - // key builder - builder builder + x := &info{ + parent: i, + kind: kind, + explicit: explicit, + } + i.children[k] = x + return x } // CheckExpression takes a top-level node and checks that it does not contain keys // that have been seen in previous calls, and validates that types are consistent. func (s *Seen) CheckExpression(node ast.Node) error { - s.builder.Reset(s.current) + if s.root == nil { + s.root = &info{ + kind: tableKind, + } + s.current = s.root + } switch node.Kind { case ast.KeyValue: - return s.checkKeyValue(node) + return s.checkKeyValue(s.current, node) case ast.Table: + return s.checkTable(node) case ast.ArrayTable: + return s.checkArrayTable(node) default: panic(fmt.Errorf("this should not be a top level node type: %s", node.Kind)) } return nil } +func (s *Seen) checkTable(node ast.Node) error { + s.current = s.root + + it := node.Key() + // handle the first parts of the key, excluding the last one + for it.Next() { + if !it.Node().Next().Valid() { + break + } + + k := string(it.Node().Data) + child, found := s.current.Has(k) + if !found { + child = s.current.CreateTable(k, false) + } + s.current = child + } + + // handle the last part of the key + k := string(it.Node().Data) + + i, found := s.current.Has(k) + if found { + if i.kind != tableKind { + return fmt.Errorf("key %s should be a table", k) + } + if i.explicit { + return fmt.Errorf("table %s already exists", k) + } + i.explicit = true + s.current = i + } else { + s.current = s.current.CreateTable(k, true) + } + + return nil +} + +func (s *Seen) checkArrayTable(node ast.Node) error { + s.current = s.root -func (s *Seen) checkKeyValue(node ast.Node) error { it := node.Key() + + // handle the first parts of the key, excluding the last one for it.Next() { - s.builder.Append(it.Node().Data) + if !it.Node().Next().Valid() { + break + } + + k := string(it.Node().Data) + child, found := s.current.Has(k) + if !found { + child = s.current.CreateTable(k, false) + } + s.current = child + } + + // handle the last part of the key + k := string(it.Node().Data) + + info, found := s.current.Has(k) + if found { + if info.kind != arrayTableKind { + return fmt.Errorf("key %s already exists but is not an array table", k) + } + info.Clear() + } else { + info = s.current.CreateArrayTable(k, true) } + + s.current = info + return nil +} + +func (s *Seen) checkKeyValue(context *info, node ast.Node) error { + it := node.Key() + + // handle the first parts of the key, excluding the last one + for it.Next() { + k := string(it.Node().Data) + child, found := context.Has(k) + if found { + if child.kind != tableKind { + return fmt.Errorf("expected %s to be a table, not a %s", k, child.kind) + } + } else { + child = context.CreateTable(k, false) + } + context = child + } + + if node.Value().Kind == ast.InlineTable { + context.SetKind(tableKind) + } else { + context.SetKind(valueKind) + } + + return nil } From 6165b9454fb5cb7d59a6538d654f68affc833a3b Mon Sep 17 00:00:00 2001 From: Cameron Moore Date: Mon, 29 Mar 2021 19:06:46 -0500 Subject: [PATCH 136/228] Identify test helper functions (#487) --- toml_testgen_support_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/toml_testgen_support_test.go b/toml_testgen_support_test.go index fa83cdb6..5d4b0a03 100644 --- a/toml_testgen_support_test.go +++ b/toml_testgen_support_test.go @@ -13,6 +13,7 @@ import ( ) func testgenInvalid(t *testing.T, input string) { + t.Helper() t.Logf("Input TOML:\n%s", input) doc := map[string]interface{}{} @@ -25,6 +26,7 @@ func testgenInvalid(t *testing.T, input string) { } func testgenValid(t *testing.T, input string, jsonRef string) { + t.Helper() t.Logf("Input TOML:\n%s", input) doc := map[string]interface{}{} From 7d8ea80dc392118748c5bc60524135933de8d830 Mon Sep 17 00:00:00 2001 From: Cameron Moore Date: Mon, 29 Mar 2021 19:07:26 -0500 Subject: [PATCH 137/228] Fix scanning of float with leading zero (#486) --- parser.go | 2 +- parser_test.go | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/parser.go b/parser.go index 8f867ca4..09414d69 100644 --- a/parser.go +++ b/parser.go @@ -1056,7 +1056,7 @@ func (p *parser) parseTime(b []byte) ([]byte, error) { func (p *parser) scanIntOrFloat(b []byte) (ast.Reference, []byte, error) { i := 0 - if len(b) > 2 && b[0] == '0' { + if len(b) > 2 && b[0] == '0' && b[1] != '.' { var isValidRune validRuneFn switch b[1] { case 'x': diff --git a/parser_test.go b/parser_test.go index 0bd9be78..bdae78ab 100644 --- a/parser_test.go +++ b/parser_test.go @@ -49,6 +49,21 @@ func TestParser_AST_Numbers(t *testing.T) { input: `0b11010110`, kind: ast.Integer, }, + { + desc: "float zero", + input: `0.0`, + kind: ast.Float, + }, + { + desc: "float positive zero", + input: `+0.0`, + kind: ast.Float, + }, + { + desc: "float negative zero", + input: `-0.0`, + kind: ast.Float, + }, { desc: "float pi", input: `3.1415`, From 269b742eb2c7b218488cad45adb9742ef73b80ce Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Mon, 29 Mar 2021 20:17:05 -0400 Subject: [PATCH 138/228] Enable race condition detector in CI --- .github/workflows/workflow.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index d9c384da..6b2767ad 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -18,4 +18,4 @@ jobs: uses: actions/setup-go@master with: go-version: ${{ matrix.go }} - - run: go test ./... + - run: go test -race ./... From 7f016efe0331b53af8b1aae5a6f1f6609defc887 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Mon, 29 Mar 2021 20:28:51 -0400 Subject: [PATCH 139/228] Test for #484 --- unmarshaler_test.go | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/unmarshaler_test.go b/unmarshaler_test.go index c84e866a..a5af0b51 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -2,6 +2,7 @@ package toml_test import ( "math" + "strconv" "testing" "github.com/pelletier/go-toml/v2" @@ -671,3 +672,34 @@ B = "data"`, }) } } + + +type Integer484 struct { + Value int +} + +func (i Integer484) MarshalText() ([]byte, error) { + return []byte(strconv.Itoa(i.Value)), nil +} +func (i *Integer484) UnmarshalText(data []byte) error { + conv, err := strconv.Atoi(string(data)) + if err != nil { + return err + } + i.Value = conv + return nil +} + +type Config struct { + Integers []Integer484 `toml:"integers"` +} + +func TestIssue484(t *testing.T) { + raw := []byte(`integers = ["1","2","3","100"]`) + var cfg Config + err := toml.Unmarshal(raw, &cfg) + require.NoError(t, err) + assert.Equal(t, Config{ + Integers: []Integer484{{1}, {2}, {3}, {100}}, + }, cfg) +} From c3fc668f27bfaf613524c3496d830765a4845223 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Mon, 29 Mar 2021 20:38:48 -0400 Subject: [PATCH 140/228] Test for #458 --- unmarshaler_test.go | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/unmarshaler_test.go b/unmarshaler_test.go index a5af0b51..d36121eb 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -690,16 +690,41 @@ func (i *Integer484) UnmarshalText(data []byte) error { return nil } -type Config struct { +type Config484 struct { Integers []Integer484 `toml:"integers"` } func TestIssue484(t *testing.T) { raw := []byte(`integers = ["1","2","3","100"]`) - var cfg Config + var cfg Config484 err := toml.Unmarshal(raw, &cfg) require.NoError(t, err) - assert.Equal(t, Config{ + assert.Equal(t, Config484{ Integers: []Integer484{{1}, {2}, {3}, {100}}, }, cfg) } + +type Map458 map[string]interface{} +type Slice458 []interface{} + +func (m Map458) A(s string) Slice458 { + return m[s].([]interface{}) +} + +func TestIssue458(t *testing.T) { + s := []byte(`[[package]] +dependencies = ["regex"] +name = "decode" +version = "0.1.0"`) + m := Map458{} + err := toml.Unmarshal(s, &m) + require.NoError(t, err) + a := m.A("package") + expected := Slice458{ + map[string]interface {}{ + "dependencies": []interface {}{"regex"}, + "name":"decode", + "version":"0.1.0"}, + } + assert.Equal(t, expected, a) +} From 78389c641a0c1d14657b0a33fbc8ee3565da4cc0 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Mon, 29 Mar 2021 20:46:09 -0400 Subject: [PATCH 141/228] Test for #475 --- unmarshaler_test.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/unmarshaler_test.go b/unmarshaler_test.go index d36121eb..367f2236 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -193,6 +193,23 @@ func TestUnmarshal(t *testing.T) { } }, }, + { + desc: "issue 475 - space between dots in key", + input: `fruit. color = "yellow" + fruit . flavor = "banana"`, + gen: func() test { + m := map[string]interface{}{} + return test{ + target: &m, + expected: &map[string]interface{}{ + "fruit": map[string]interface{}{ + "color": "yellow", + "flavor": "banana", + }, + }, + } + }, + }, { desc: "multiline basic string", input: `A = """\ From 51d78a5f0c0b170b2408c6e5e910cdda750cd645 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Mon, 29 Mar 2021 20:58:51 -0400 Subject: [PATCH 142/228] Fix unmarshaling of literal keys Ref #427. --- parser.go | 2 +- unmarshaler_test.go | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/parser.go b/parser.go index 09414d69..31e69cf0 100644 --- a/parser.go +++ b/parser.go @@ -594,7 +594,7 @@ func (p *parser) parseSimpleKey(b []byte) (key, rest []byte, err error) { } if b[0] == '\'' { - key, rest, err = scanLiteralString(b) + key, rest, err = p.parseLiteralString(b) } else if b[0] == '"' { key, rest, err = p.parseBasicString(b) } else if isUnquotedKeyChar(b[0]) { diff --git a/unmarshaler_test.go b/unmarshaler_test.go index 367f2236..d0c1aaa2 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -210,6 +210,21 @@ func TestUnmarshal(t *testing.T) { } }, }, + { + desc: "issue 427 - quotation marks in key", + input: `'"a"' = 1 + "\"b\"" = 2`, + gen: func() test { + m := map[string]interface{}{} + return test{ + target: &m, + expected: &map[string]interface{}{ + `"a"`: int64(1), + `"b"`: int64(2), + }, + } + }, + }, { desc: "multiline basic string", input: `A = """\ From 2714786b3791d1eed1361c877ab8fea6b4421af7 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Mon, 29 Mar 2021 21:30:41 -0400 Subject: [PATCH 143/228] Add decoder interface --- unmarshaler.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/unmarshaler.go b/unmarshaler.go index 59c63348..bb1aff28 100644 --- a/unmarshaler.go +++ b/unmarshaler.go @@ -3,6 +3,8 @@ package toml import ( "encoding" "fmt" + "io" + "io/ioutil" "reflect" "time" @@ -17,6 +19,28 @@ func Unmarshal(data []byte, v interface{}) error { return d.FromParser(&p, v) } +// Decoder reads and decode a TOML document from an input stream. +type Decoder struct { + r io.Reader +} + +// NewDecoder creates a new Decoder that will read from r. +func NewDecoder(r io.Reader) *Decoder { + return &Decoder{r: r} +} + +// Decode the whole content of r into v. +func (d *Decoder) Decode(v interface{}) error { + b, err := ioutil.ReadAll(d.r) + if err != nil { + return err + } + p := parser{} + p.Reset(b) + dec := decoder{} + return dec.FromParser(&p, v) +} + type decoder struct { // Tracks position in Go arrays. arrayIndexes map[reflect.Value]int From 72a1afdcb21465fa2a68d76df0d50ff4d0e8bf4b Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Mon, 29 Mar 2021 22:33:28 -0400 Subject: [PATCH 144/228] Add some unsafe helper to track errors --- internal/errors/unsafe.go | 37 ++++++++++++++++ internal/errors/unsafe_test.go | 79 ++++++++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 internal/errors/unsafe.go create mode 100644 internal/errors/unsafe_test.go diff --git a/internal/errors/unsafe.go b/internal/errors/unsafe.go new file mode 100644 index 00000000..626c01b1 --- /dev/null +++ b/internal/errors/unsafe.go @@ -0,0 +1,37 @@ +package errors + +import ( + "fmt" + "reflect" + "unsafe" +) + +const maxInt = uintptr(int(^uint(0) >> 1)) + + +func UnsafeSubsliceOffset(data []byte, subslice []byte) int { + datap := (*reflect.SliceHeader)(unsafe.Pointer(&data)) + hlp := (*reflect.SliceHeader)(unsafe.Pointer(&subslice)) + + + if hlp.Data < datap.Data { + panic(fmt.Errorf("subslice address (%d) is before data address (%d)", hlp.Data, datap.Data)) + } + offset := hlp.Data - datap.Data + + if offset > maxInt { + panic(fmt.Errorf("slice offset larger than int (%d)", offset)) + } + + intoffset := int(offset) + + if intoffset >= datap.Len { + panic(fmt.Errorf("slice offset (%d) is farther than data length (%d)", intoffset, datap.Len)) + } + + if intoffset + hlp.Len > datap.Len { + panic(fmt.Errorf("slice ends (%d+%d) is farther than data length (%d)", intoffset, hlp.Len, datap.Len)) + } + + return intoffset +} diff --git a/internal/errors/unsafe_test.go b/internal/errors/unsafe_test.go new file mode 100644 index 00000000..5c3b50b6 --- /dev/null +++ b/internal/errors/unsafe_test.go @@ -0,0 +1,79 @@ +package errors_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/pelletier/go-toml/v2/internal/errors" +) + +func TestUnsafeSubsliceOffsetValid(t *testing.T) { + examples := []struct{ + desc string + test func() ([]byte, []byte) + offset int + }{ + { + desc: "simple", + test: func() ([]byte, []byte) { + data := []byte("hello") + return data, data[1:] + }, + offset: 1, + }, + } + + for _, e := range examples { + t.Run(e.desc, func(t *testing.T) { + d, s := e.test() + offset := errors.UnsafeSubsliceOffset(d, s) + assert.Equal(t, e.offset, offset) + }) + } +} + +func TestUnsafeSubsliceOffsetInvalid(t *testing.T) { + examples := []struct{ + desc string + test func() ([]byte, []byte) + }{ + { + desc: "unrelated arrays", + test: func() ([]byte, []byte) { + return []byte("one"), []byte("two") + }, + }, + { + desc: "slice starts before data", + test: func() ([]byte, []byte) { + full := []byte("hello world") + return full[5:], full[1:] + }, + }, + { + desc: "slice starts after data", + test: func() ([]byte, []byte) { + full := []byte("hello world") + return full[:3], full[5:] + }, + }, + { + desc: "slice ends after data", + test: func() ([]byte, []byte) { + full := []byte("hello world") + return full[:5], full[3:8] + }, + }, + } + + for _, e := range examples { + t.Run(e.desc, func(t *testing.T) { + d, s := e.test() + require.Panics(t, func() { + errors.UnsafeSubsliceOffset(d, s) + }) + }) + } +} From cf288a51c5ff4e7e6790d9f4918443977a2242d4 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Tue, 30 Mar 2021 10:59:35 -0400 Subject: [PATCH 145/228] Wip errors reporting --- README.md | 2 +- errors.go | 147 +++++++++++++++++++++ errors_test.go | 144 ++++++++++++++++++++ internal/{errors => unsafe}/unsafe.go | 8 +- internal/{errors => unsafe}/unsafe_test.go | 16 +-- 5 files changed, 303 insertions(+), 14 deletions(-) create mode 100644 errors.go create mode 100644 errors_test.go rename internal/{errors => unsafe}/unsafe.go (86%) rename internal/{errors => unsafe}/unsafe_test.go (83%) diff --git a/README.md b/README.md index 6da36e88..04d6b4b3 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,8 @@ Development branch. Probably does not work. - [x] Benchmark! - [x] Abstract AST. - [x] Original go-toml testgen tests pass. -- [ ] Attach comments to AST (gated by parser flag). - [ ] Track file position (line, column) for errors. +- [ ] Attach comments to AST (gated by parser flag). - [ ] Benchmark again! ## Further work diff --git a/errors.go b/errors.go new file mode 100644 index 00000000..80db0043 --- /dev/null +++ b/errors.go @@ -0,0 +1,147 @@ +package toml + +import ( + "fmt" + "strconv" + "strings" + + "github.com/pelletier/go-toml/v2/internal/unsafe" +) + +// DecodeError represents an error encountered during the parsing or decoding +// of a TOML document. +// +// In addition to the error message, it contains the position in the document +// where it happened, as well as a human-readable representation that shows +// where the error occurred in the document. +type DecodeError struct { + message string + line int + column int + + human string +} + +// Error returns the error message contained in the DecodeError. +func (e *DecodeError) Error() string { + return e.message +} + +// String returns the human-readable contextualized error. This string is multi-line. +func (e *DecodeError) String() string { + return e.human +} + +/// Position returns the (line, column) pair indicating where the error +// occurred in the document. Positions are 1-indexed. +func (e *DecodeError) Position() (row int, column int) { + return e.line, e.column +} + +// decodeErrorFromHighlight creates a DecodeError referencing to a highlighted +// range of bytes from document. +// +// highlight needs to be a sub-slice of document, or this function panics. +// +// The function copies all bytes used in DecodeError, so that document and +// highlight can be freely deallocated. +func decodeErrorFromHighlight(document []byte, highlight []byte, message string) error { + err := &DecodeError{ + message: message, + } + + offset := unsafe.SubsliceOffset(document, highlight) + + err.line, err.column = positionAtEnd(document[:offset]) + before, after := linesOfContext(document, highlight, offset, 3) + + var buf strings.Builder + + maxLine := err.line + len(after) - 1 + lineColumnWidth := len(strconv.Itoa(maxLine)) + + for i := len(before) - 1; i > 0; i-- { + line := err.line - i + buf.WriteString(formatLineNumber(line, lineColumnWidth)) + buf.WriteString("| ") + buf.Write(before[i]) + buf.WriteRune('\n') + } + + buf.WriteString(formatLineNumber(err.line, lineColumnWidth)) + buf.WriteString("| ") + + if len(before) > 0 { + buf.Write(before[0]) + } + buf.Write(highlight) + if len(after) > 0 { + buf.Write(after[0]) + } + buf.WriteRune('\n') + buf.WriteString(strings.Repeat(" ", lineColumnWidth)) + buf.WriteString("| ") + if len(before) > 0 { + buf.WriteString(strings.Repeat(" ", len(before[0]))) + } + buf.WriteString(strings.Repeat("~", len(highlight))) + buf.WriteString(" ") + buf.WriteString(err.message) + + for i := 1; i < len(after); i++ { + buf.WriteRune('\n') + line := err.line + i + buf.WriteString(formatLineNumber(line, lineColumnWidth)) + buf.WriteString("| ") + buf.Write(after[i]) + } + + err.human = buf.String() + return err +} + +func formatLineNumber(line int, width int) string { + format := "%" + strconv.Itoa(width) + "d" + return fmt.Sprintf(format, line) +} + +func linesOfContext(document []byte, highlight []byte, offset int, linesAround int) ([][]byte, [][]byte) { + var beforeLines [][]byte + for beforeOffset, lastOffset := offset, offset; beforeOffset >= 0 && len(beforeLines) <= linesAround; beforeOffset-- { + if document[beforeOffset] == '\n' { + beforeLines = append(beforeLines, document[beforeOffset+1:lastOffset]) + lastOffset = beforeOffset + } else if beforeOffset == 0 && beforeOffset != lastOffset { + beforeLines = append(beforeLines, document[beforeOffset:lastOffset]) + } + } + + var afterLines [][]byte + + document = document[offset+len(highlight):] + for afterOffset, lastOffset := 0, 0; afterOffset < len(document) && len(afterLines) <= linesAround; afterOffset++ { + if document[afterOffset] == '\n' { + afterLines = append(afterLines, document[lastOffset:afterOffset]) + afterOffset++ // skip \n + lastOffset = afterOffset + } else if afterOffset == len(document)-1 && lastOffset != afterOffset+1 { + afterLines = append(afterLines, document[lastOffset:afterOffset+1]) + } + } + return beforeLines, afterLines +} + +func positionAtEnd(b []byte) (row int, column int) { + row = 1 + column = 1 + + for _, c := range b { + if c == '\n' { + row++ + column = 1 + } else { + column++ + } + } + return +} diff --git a/errors_test.go b/errors_test.go new file mode 100644 index 00000000..9d0c5a9c --- /dev/null +++ b/errors_test.go @@ -0,0 +1,144 @@ +package toml + +import ( + "bytes" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestDecodeError(t *testing.T) { + examples := []struct { + desc string + doc [3]string + msg string + expected string + }{ + { + desc: "no context", + doc: [3]string{"", "morning", ""}, + msg: "this is wrong", + expected: ` +1| morning + | ~~~~~~~ this is wrong`, + }, + { + desc: "one line", + doc: [3]string{"good ", "morning", " everyone"}, + msg: "this is wrong", + expected: ` +1| good morning everyone + | ~~~~~~~ this is wrong`, + }, + { + desc: "exactly 3 lines", + doc: [3]string{`line1 +line2 +line3 +before `, "highlighted", ` after +post line 1 +post line 2 +post line 3`}, + msg: "this is wrong", + expected: ` +1| line1 +2| line2 +3| line3 +4| before highlighted after + | ~~~~~~~~~~~ this is wrong +5| post line 1 +6| post line 2 +7| post line 3`, + }, + { + desc: "more than 3 lines", + doc: [3]string{`should not be seen1 +should not be seen2 +line1 +line2 +line3 +before `, "highlighted", ` after +post line 1 +post line 2 +post line 3 +should not be seen3 +should not be seen4`}, + msg: "this is wrong", + expected: ` +3| line1 +4| line2 +5| line3 +6| before highlighted after + | ~~~~~~~~~~~ this is wrong +7| post line 1 +8| post line 2 +9| post line 3`, + }, + { + desc: "more than 10 total lines", + doc: [3]string{`should not be seen 0 +should not be seen1 +should not be seen2 +should not be seen3 +line1 +line2 +line3 +before `, "highlighted", ` after +post line 1 +post line 2 +post line 3 +should not be seen3 +should not be seen4`}, + msg: "this is wrong", + expected: ` + 5| line1 + 6| line2 + 7| line3 + 8| before highlighted after + | ~~~~~~~~~~~ this is wrong + 9| post line 1 +10| post line 2 +11| post line 3`, + }, + { + desc: "last line of more than 10", + doc: [3]string{`should not be seen +should not be seen +should not be seen +should not be seen +should not be seen +should not be seen +should not be seen +line1 +line2 +line3 +before `, "highlighted", ``}, + msg: "this is wrong", + expected: ` + 8| line1 + 9| line2 +10| line3 +11| before highlighted + | ~~~~~~~~~~~ this is wrong +`, + }, + } + + for _, e := range examples { + t.Run(e.desc, func(t *testing.T) { + b := bytes.Buffer{} + b.Write([]byte(e.doc[0])) + start := b.Len() + b.Write([]byte(e.doc[1])) + end := b.Len() + b.Write([]byte(e.doc[2])) + doc := b.Bytes() + hl := doc[start:end] + + err := decodeErrorFromHighlight(doc, hl, e.msg) + derr := err.(*DecodeError) + assert.Equal(t, strings.Trim(e.expected, "\n"), derr.String()) + }) + } +} diff --git a/internal/errors/unsafe.go b/internal/unsafe/unsafe.go similarity index 86% rename from internal/errors/unsafe.go rename to internal/unsafe/unsafe.go index 626c01b1..179fd1a2 100644 --- a/internal/errors/unsafe.go +++ b/internal/unsafe/unsafe.go @@ -1,4 +1,4 @@ -package errors +package unsafe import ( "fmt" @@ -8,12 +8,10 @@ import ( const maxInt = uintptr(int(^uint(0) >> 1)) - -func UnsafeSubsliceOffset(data []byte, subslice []byte) int { +func SubsliceOffset(data []byte, subslice []byte) int { datap := (*reflect.SliceHeader)(unsafe.Pointer(&data)) hlp := (*reflect.SliceHeader)(unsafe.Pointer(&subslice)) - if hlp.Data < datap.Data { panic(fmt.Errorf("subslice address (%d) is before data address (%d)", hlp.Data, datap.Data)) } @@ -29,7 +27,7 @@ func UnsafeSubsliceOffset(data []byte, subslice []byte) int { panic(fmt.Errorf("slice offset (%d) is farther than data length (%d)", intoffset, datap.Len)) } - if intoffset + hlp.Len > datap.Len { + if intoffset+hlp.Len > datap.Len { panic(fmt.Errorf("slice ends (%d+%d) is farther than data length (%d)", intoffset, hlp.Len, datap.Len)) } diff --git a/internal/errors/unsafe_test.go b/internal/unsafe/unsafe_test.go similarity index 83% rename from internal/errors/unsafe_test.go rename to internal/unsafe/unsafe_test.go index 5c3b50b6..a184be9b 100644 --- a/internal/errors/unsafe_test.go +++ b/internal/unsafe/unsafe_test.go @@ -1,4 +1,4 @@ -package errors_test +package unsafe_test import ( "testing" @@ -6,13 +6,13 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/pelletier/go-toml/v2/internal/errors" + "github.com/pelletier/go-toml/v2/internal/unsafe" ) func TestUnsafeSubsliceOffsetValid(t *testing.T) { - examples := []struct{ - desc string - test func() ([]byte, []byte) + examples := []struct { + desc string + test func() ([]byte, []byte) offset int }{ { @@ -28,14 +28,14 @@ func TestUnsafeSubsliceOffsetValid(t *testing.T) { for _, e := range examples { t.Run(e.desc, func(t *testing.T) { d, s := e.test() - offset := errors.UnsafeSubsliceOffset(d, s) + offset := unsafe.SubsliceOffset(d, s) assert.Equal(t, e.offset, offset) }) } } func TestUnsafeSubsliceOffsetInvalid(t *testing.T) { - examples := []struct{ + examples := []struct { desc string test func() ([]byte, []byte) }{ @@ -72,7 +72,7 @@ func TestUnsafeSubsliceOffsetInvalid(t *testing.T) { t.Run(e.desc, func(t *testing.T) { d, s := e.test() require.Panics(t, func() { - errors.UnsafeSubsliceOffset(d, s) + unsafe.SubsliceOffset(d, s) }) }) } From e5255a5be2ab9cf580122e1b7810a58c6653cc24 Mon Sep 17 00:00:00 2001 From: Cameron Moore Date: Tue, 30 Mar 2021 11:34:25 -0500 Subject: [PATCH 146/228] Set bytes in ReferenceFile benchmark to show throughput results (#489) --- benchmark/benchmark_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/benchmark/benchmark_test.go b/benchmark/benchmark_test.go index 5d3d2ed3..65797b25 100644 --- a/benchmark/benchmark_test.go +++ b/benchmark/benchmark_test.go @@ -157,6 +157,7 @@ func BenchmarkReferenceFile(b *testing.B) { if err != nil { b.Fatal(err) } + b.SetBytes(int64(len(bytes))) b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { From bcd5333b030705d218f13d785c6666efaf316922 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Tue, 30 Mar 2021 12:38:26 -0400 Subject: [PATCH 147/228] Enable ci on v2-wip branch pull requests --- .github/workflows/workflow.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 6b2767ad..85504dac 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -3,6 +3,9 @@ on: push: branches: - v2-wip + pull_request: + branches: + - v2-wip jobs: build: From 18d45c446bef8e559c7af6296f14d2b517bb3c8d Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Tue, 30 Mar 2021 19:52:02 -0400 Subject: [PATCH 148/228] wip: decoder errors --- decode.go | 2 +- errors.go | 33 +++++++++++++++++++++++++++------ errors_test.go | 5 ++++- unmarshaler.go | 11 +++++++++++ 4 files changed, 43 insertions(+), 8 deletions(-) diff --git a/decode.go b/decode.go index df43327b..58bca713 100644 --- a/decode.go +++ b/decode.go @@ -19,7 +19,7 @@ func parseInteger(b []byte) (int64, error) { case 'o': return parseIntOct(b) default: - return 0, fmt.Errorf("invalid base: '%c'", b[1]) + return 0, newDecodeError(b[1:2], "invalid base: '%c'", b[1]) } } return parseIntDec(b) diff --git a/errors.go b/errors.go index 80db0043..8d315942 100644 --- a/errors.go +++ b/errors.go @@ -22,6 +22,24 @@ type DecodeError struct { human string } +// internal version of DecodeError that is used as the base to create a +// DecodeError with full context. +type decodeError struct { + highlight []byte + message string +} + +func (de *decodeError) Error() string { + return de.message +} + +func newDecodeError(highlight []byte, format string, args ...interface{}) error { + return &decodeError{ + highlight: highlight, + message: fmt.Sprintf(format, args...), + } +} + // Error returns the error message contained in the DecodeError. func (e *DecodeError) Error() string { return e.message @@ -45,15 +63,18 @@ func (e *DecodeError) Position() (row int, column int) { // // The function copies all bytes used in DecodeError, so that document and // highlight can be freely deallocated. -func decodeErrorFromHighlight(document []byte, highlight []byte, message string) error { +func wrapDecodeError(document []byte, de *decodeError) error { + if de == nil { + return nil + } err := &DecodeError{ - message: message, + message: de.message, } - offset := unsafe.SubsliceOffset(document, highlight) + offset := unsafe.SubsliceOffset(document, de.highlight) err.line, err.column = positionAtEnd(document[:offset]) - before, after := linesOfContext(document, highlight, offset, 3) + before, after := linesOfContext(document, de.highlight, offset, 3) var buf strings.Builder @@ -74,7 +95,7 @@ func decodeErrorFromHighlight(document []byte, highlight []byte, message string) if len(before) > 0 { buf.Write(before[0]) } - buf.Write(highlight) + buf.Write(de.highlight) if len(after) > 0 { buf.Write(after[0]) } @@ -84,7 +105,7 @@ func decodeErrorFromHighlight(document []byte, highlight []byte, message string) if len(before) > 0 { buf.WriteString(strings.Repeat(" ", len(before[0]))) } - buf.WriteString(strings.Repeat("~", len(highlight))) + buf.WriteString(strings.Repeat("~", len(de.highlight))) buf.WriteString(" ") buf.WriteString(err.message) diff --git a/errors_test.go b/errors_test.go index 9d0c5a9c..e1354e23 100644 --- a/errors_test.go +++ b/errors_test.go @@ -136,7 +136,10 @@ before `, "highlighted", ``}, doc := b.Bytes() hl := doc[start:end] - err := decodeErrorFromHighlight(doc, hl, e.msg) + err := wrapDecodeError(doc, &decodeError{ + highlight: hl, + message: e.msg, + }) derr := err.(*DecodeError) assert.Equal(t, strings.Trim(e.expected, "\n"), derr.String()) }) diff --git a/unmarshaler.go b/unmarshaler.go index bb1aff28..b95ce5a8 100644 --- a/unmarshaler.go +++ b/unmarshaler.go @@ -66,6 +66,17 @@ func (d *decoder) arrayIndex(append bool, v reflect.Value) int { } func (d *decoder) FromParser(p *parser, v interface{}) error { + err := d.fromParser(p, v) + if err != nil { + de, ok := err.(*decodeError) + if ok { + err = wrapDecodeError(p.data, de) + } + } + return err +} + +func (d *decoder) fromParser(p *parser, v interface{}) error { r := reflect.ValueOf(v) if r.Kind() != reflect.Ptr { return fmt.Errorf("need to target a pointer, not %s", r.Kind()) From 32da85ab11695b140552ebce61d90f0f8635a8b0 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Tue, 30 Mar 2021 21:43:57 -0400 Subject: [PATCH 149/228] Decoding error position tracking --- README.md | 2 +- decode.go | 12 ++--- errors.go | 10 +++- internal/unsafe/unsafe.go | 2 +- parser.go | 13 +++-- scanner.go | 18 +++---- unmarshaler.go | 17 +++++- unmarshaler_test.go | 110 ++++++++++++++++++++++++++++++++++---- 8 files changed, 150 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 04d6b4b3..d4677038 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Development branch. Probably does not work. - [x] Benchmark! - [x] Abstract AST. - [x] Original go-toml testgen tests pass. -- [ ] Track file position (line, column) for errors. +- [x] Track file position (line, column) for errors. - [ ] Attach comments to AST (gated by parser flag). - [ ] Benchmark again! diff --git a/decode.go b/decode.go index 58bca713..d4b85594 100644 --- a/decode.go +++ b/decode.go @@ -34,7 +34,7 @@ func parseLocalDate(b []byte) (LocalDate, error) { date := LocalDate{} if len(b) != 10 || b[4] != '-' || b[7] != '-' { - return date, fmt.Errorf("dates are expected to have the format YYYY-MM-DD") + return date, newDecodeError(b, "dates are expected to have the format YYYY-MM-DD") } var err error @@ -89,7 +89,7 @@ func parseDateTime(b []byte) (time.Time, error) { zone = time.UTC } else { if len(b) != 6 { - return time.Time{}, fmt.Errorf("invalid date-time timezone") + return time.Time{}, newDecodeError(b, "invalid date-time timezone") } direction := 1 switch b[0] { @@ -97,7 +97,7 @@ func parseDateTime(b []byte) (time.Time, error) { case '-': direction = -1 default: - return time.Time{}, fmt.Errorf("invalid timezone offset character") + return time.Time{}, newDecodeError(b[0:1], "invalid timezone offset character") } hours := digitsToInt(b[1:3]) @@ -107,7 +107,7 @@ func parseDateTime(b []byte) (time.Time, error) { } if len(b) > 0 { - return time.Time{}, fmt.Errorf("extra bytes at the end of the timezone") + return time.Time{}, newDecodeError(b, "extra bytes at the end of the timezone") } t := time.Date( @@ -166,14 +166,14 @@ func parseLocalTime(b []byte) (LocalTime, []byte, error) { return t, nil, err } if b[2] != ':' { - return t, nil, fmt.Errorf("expecting colon between hours and minutes") + return t, nil, newDecodeError(b[2:3], "expecting colon between hours and minutes") } t.Minute, err = parseDecimalDigits(b[3:5]) if err != nil { return t, nil, err } if b[5] != ':' { - return t, nil, fmt.Errorf("expecting colon between minutes and seconds") + return t, nil, newDecodeError(b[5:6], "expecting colon between minutes and seconds") } t.Second, err = parseDecimalDigits(b[6:8]) if err != nil { diff --git a/errors.go b/errors.go index 8d315942..cc0baba4 100644 --- a/errors.go +++ b/errors.go @@ -129,8 +129,16 @@ func formatLineNumber(line int, width int) string { func linesOfContext(document []byte, highlight []byte, offset int, linesAround int) ([][]byte, [][]byte) { var beforeLines [][]byte for beforeOffset, lastOffset := offset, offset; beforeOffset >= 0 && len(beforeLines) <= linesAround; beforeOffset-- { + if beforeOffset == len(document) { + beforeLines = append(beforeLines, []byte{}) + continue + } if document[beforeOffset] == '\n' { - beforeLines = append(beforeLines, document[beforeOffset+1:lastOffset]) + if beforeOffset == lastOffset { + beforeLines = append(beforeLines, []byte{}) + } else { + beforeLines = append(beforeLines, document[beforeOffset+1:lastOffset]) + } lastOffset = beforeOffset } else if beforeOffset == 0 && beforeOffset != lastOffset { beforeLines = append(beforeLines, document[beforeOffset:lastOffset]) diff --git a/internal/unsafe/unsafe.go b/internal/unsafe/unsafe.go index 179fd1a2..ce6b955e 100644 --- a/internal/unsafe/unsafe.go +++ b/internal/unsafe/unsafe.go @@ -23,7 +23,7 @@ func SubsliceOffset(data []byte, subslice []byte) int { intoffset := int(offset) - if intoffset >= datap.Len { + if intoffset > datap.Len { panic(fmt.Errorf("slice offset (%d) is farther than data length (%d)", intoffset, datap.Len)) } diff --git a/parser.go b/parser.go index 31e69cf0..bb9a37e5 100644 --- a/parser.go +++ b/parser.go @@ -363,7 +363,7 @@ func (p *parser) parseValArray(b []byte) (ast.Reference, []byte, error) { } if len(b) == 0 { - return parent, nil, unexpectedCharacter{b: b} + return parent, nil, unexpectedCharacter{b: b} // TODO: should be unexpected EOF } if b[0] == ']' { @@ -590,7 +590,7 @@ func (p *parser) parseSimpleKey(b []byte) (key, rest []byte, err error) { //quoted-key = basic-string / literal-string if len(b) == 0 { - return nil, nil, unexpectedCharacter{b: b} + return nil, nil, unexpectedCharacter{b: b} // TODO: should be unexpected EOF } if b[0] == '\'' { @@ -600,7 +600,7 @@ func (p *parser) parseSimpleKey(b []byte) (key, rest []byte, err error) { } else if isUnquotedKeyChar(b[0]) { key, rest, err = scanUnquotedKey(b) } else { - err = unexpectedCharacter{b: b} + err = unexpectedCharacter{b: b} // TODO: should contain expected characters } return } @@ -1158,8 +1158,11 @@ func isValidBinaryRune(r byte) bool { } func expect(x byte, b []byte) ([]byte, error) { - if len(b) == 0 || b[0] != x { - return nil, unexpectedCharacter{r: x, b: b} + if len(b) == 0 { + return nil, newDecodeError(b[:0], "expecting %#U", x) + } + if b[0] != x { + return nil, newDecodeError(b[0:1], "expected character %U", x) } return b[1:], nil } diff --git a/scanner.go b/scanner.go index ba857c62..bad47bcd 100644 --- a/scanner.go +++ b/scanner.go @@ -30,7 +30,7 @@ func scanUnquotedKey(b []byte) ([]byte, []byte, error) { return b[:i], b[i:], nil } } - return b, nil, nil + return b, b[len(b):], nil } func isUnquotedKeyChar(r byte) bool { @@ -46,10 +46,10 @@ func scanLiteralString(b []byte) ([]byte, []byte, error) { case '\'': return b[:i+1], b[i+1:], nil case '\n': - return nil, nil, fmt.Errorf("literal strings cannot have new lines") + return nil, nil, newDecodeError(b[i:i+1], "literal strings cannot have new lines") } } - return nil, nil, fmt.Errorf("unterminated literal string") + return nil, nil, newDecodeError(b[len(b):], "unterminated literal string") } func scanMultilineLiteralString(b []byte) ([]byte, []byte, error) { @@ -70,7 +70,7 @@ func scanMultilineLiteralString(b []byte) ([]byte, []byte, error) { } } - return nil, nil, fmt.Errorf(`multiline literal string not terminated by '''`) + return nil, nil, newDecodeError(b[len(b):], `multiline literal string not terminated by '''`) } func scanWindowsNewline(b []byte) ([]byte, []byte, error) { @@ -92,7 +92,7 @@ func scanWhitespace(b []byte) ([]byte, []byte) { return b[:i], b[i:] } } - return b, nil + return b, b[len(b):] } func scanComment(b []byte) ([]byte, []byte, error) { @@ -125,10 +125,10 @@ func scanBasicString(b []byte) ([]byte, []byte, error) { case '"': return b[:i+1], b[i+1:], nil case '\n': - return nil, nil, fmt.Errorf("basic strings cannot have new lines") + return nil, nil, newDecodeError(b[i:i+1], "basic strings cannot have new lines") case '\\': if len(b) < i+2 { - return nil, nil, fmt.Errorf("need a character after \\") + return nil, nil, newDecodeError(b[i:i+1], "need a character after \\") } i++ // skip the next character } @@ -158,11 +158,11 @@ func scanMultilineBasicString(b []byte) ([]byte, []byte, error) { } case '\\': if len(b) < i+2 { - return nil, nil, fmt.Errorf("need a character after \\") + return nil, nil, newDecodeError(b[len(b):], "need a character after \\") } i++ // skip the next character } } - return nil, nil, fmt.Errorf(`multiline basic string not terminated by """`) + return nil, nil, newDecodeError(b[len(b):], `multiline basic string not terminated by """`) } diff --git a/unmarshaler.go b/unmarshaler.go index b95ce5a8..0f897032 100644 --- a/unmarshaler.go +++ b/unmarshaler.go @@ -268,11 +268,22 @@ func (d *decoder) unmarshalValue(x target, node ast.Node) error { return unmarshalLocalDateTime(x, node) case ast.DateTime: return unmarshalDateTime(x, node) + case ast.LocalDate: + return unmarshalLocalDate(x, node) default: panic(fmt.Errorf("unhandled unmarshalValue kind %s", node.Kind)) } } +func unmarshalLocalDate(x target, node ast.Node) error { + assertNode(ast.LocalDate, node) + v, err := parseLocalDate(node.Data) + if err != nil { + return err + } + return setDate(x, v) +} + func unmarshalLocalDateTime(x target, node ast.Node) error { assertNode(ast.LocalDateTime, node) v, rest, err := parseLocalDateTime(node.Data) @@ -280,7 +291,7 @@ func unmarshalLocalDateTime(x target, node ast.Node) error { return err } if len(rest) > 0 { - return fmt.Errorf("extra characters at the end of a local date time") + return newDecodeError(rest, "extra characters at the end of a local date time") } return setLocalDateTime(x, v) } @@ -302,6 +313,10 @@ func setDateTime(x target, v time.Time) error { return x.set(reflect.ValueOf(v)) } +func setDate(x target, v LocalDate) error { + return x.set(reflect.ValueOf(v)) +} + func unmarshalString(x target, node ast.Node) error { assertNode(ast.String, node) return setString(x, string(node.Data)) diff --git a/unmarshaler_test.go b/unmarshaler_test.go index d0c1aaa2..dab89574 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -200,10 +200,10 @@ func TestUnmarshal(t *testing.T) { gen: func() test { m := map[string]interface{}{} return test{ - target: &m, + target: &m, expected: &map[string]interface{}{ "fruit": map[string]interface{}{ - "color": "yellow", + "color": "yellow", "flavor": "banana", }, }, @@ -217,7 +217,7 @@ func TestUnmarshal(t *testing.T) { gen: func() test { m := map[string]interface{}{} return test{ - target: &m, + target: &m, expected: &map[string]interface{}{ `"a"`: int64(1), `"b"`: int64(2), @@ -226,7 +226,7 @@ func TestUnmarshal(t *testing.T) { }, }, { - desc: "multiline basic string", + desc: "multiline basic string", input: `A = """\ Test"""`, gen: func() test { @@ -705,7 +705,6 @@ B = "data"`, } } - type Integer484 struct { Value int } @@ -726,7 +725,7 @@ type Config484 struct { Integers []Integer484 `toml:"integers"` } -func TestIssue484(t *testing.T) { +func TestIssue484(t *testing.T) { raw := []byte(`integers = ["1","2","3","100"]`) var cfg Config484 err := toml.Unmarshal(raw, &cfg) @@ -753,10 +752,101 @@ version = "0.1.0"`) require.NoError(t, err) a := m.A("package") expected := Slice458{ - map[string]interface {}{ - "dependencies": []interface {}{"regex"}, - "name":"decode", - "version":"0.1.0"}, + map[string]interface{}{ + "dependencies": []interface{}{"regex"}, + "name": "decode", + "version": "0.1.0"}, } assert.Equal(t, expected, a) } + +func TestUnmarshalDecodeErrors(t *testing.T) { + examples := []struct { + desc string + data string + msg string + }{ + { + desc: "int with wrong base", + data: `a = 0f2`, + }, + { + desc: "literal string with new lines", + data: `a = 'hello +world'`, + msg: `literal strings cannot have new lines`, + }, + { + desc: "unterminated literal string", + data: `a = 'hello`, + msg: `unterminated literal string`, + }, + { + desc: "unterminated multiline literal string", + data: `a = '''hello`, + msg: `multiline literal string not terminated by '''`, + }, + { + desc: "basic string with new lines", + data: `a = "hello +"`, + msg: `basic strings cannot have new lines`, + }, + { + desc: "basic string with unfinished escape", + data: `a = "hello \`, + msg: `need a character after \`, + }, + { + desc: "basic unfinished multiline string", + data: `a = """hello`, + msg: `multiline basic string not terminated by """`, + }, + { + desc: "basic unfinished escape in multiline string", + data: `a = """hello \`, + msg: `need a character after \`, + }, + { + desc: "malformed local date", + data: `a = 2021-033-0`, + msg: `dates are expected to have the format YYYY-MM-DD`, + }, + { + desc: "malformed tz", + data: `a = 2021-03-30 21:31:00+1`, + msg: `invalid date-time timezone`, + }, + { + desc: "malformed tz first char", + data: `a = 2021-03-30 21:31:00:1`, + msg: `extra characters at the end of a local date time`, + }, + { + desc: "bad char between hours and minutes", + data: `a = 2021-03-30 213:1:00`, + msg: `expecting colon between hours and minutes`, + }, + { + desc: "bad char between minutes and seconds", + data: `a = 2021-03-30 21:312:0`, + msg: `expecting colon between minutes and seconds`, + }, + } + + for _, e := range examples { + t.Run(e.desc, func(t *testing.T) { + m := map[string]interface{}{} + err := toml.Unmarshal([]byte(e.data), &m) + require.Error(t, err) + de, ok := err.(*toml.DecodeError) + if !ok { + t.Fatalf("err should have been a *toml.DecodeError, but got %s (%T)", err, err) + } + if e.msg != "" { + t.Log("\n" + de.String()) + require.Equal(t, e.msg, de.Error()) + } + }) + } +} From 739ceda96cef686422bfeae4bfe20b1a46bc2294 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Tue, 30 Mar 2021 21:45:43 -0400 Subject: [PATCH 150/228] Move github pull request template --- PULL_REQUEST_TEMPLATE.md => .github/PULL_REQUEST_TEMPLATE.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename PULL_REQUEST_TEMPLATE.md => .github/PULL_REQUEST_TEMPLATE.md (100%) diff --git a/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md similarity index 100% rename from PULL_REQUEST_TEMPLATE.md rename to .github/PULL_REQUEST_TEMPLATE.md From 7ccacf158e39301db5a8015430f6ffce5e87fe1c Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Tue, 30 Mar 2021 21:53:01 -0400 Subject: [PATCH 151/228] Test for #252 --- unmarshaler_test.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/unmarshaler_test.go b/unmarshaler_test.go index dab89574..ac874eb0 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -760,6 +760,26 @@ version = "0.1.0"`) assert.Equal(t, expected, a) } +func TestIssue252(t *testing.T) { + type config struct { + Val1 string `toml:"val1"` + Val2 string `toml:"val2"` + } + + var configFile = []byte( + ` +val1 = "test1" +`) + + cfg := &config{ + Val2: "test2", + } + + err := toml.Unmarshal(configFile, cfg) + require.NoError(t, err) + require.Equal(t, "test2", cfg.Val2) +} + func TestUnmarshalDecodeErrors(t *testing.T) { examples := []struct { desc string From 5d905981cf56799881827f88800209b284f1bc39 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Tue, 30 Mar 2021 22:03:39 -0400 Subject: [PATCH 152/228] CI: add dependabot --- .github/dependabot.yml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..14030fd5 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "gomod" + directory: "/" # Location of package manifests + schedule: + interval: "daily" From 4a4c2c2a5f5d1a73d8a6b80c7ef3b969548ba01d Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Wed, 31 Mar 2021 09:15:33 -0400 Subject: [PATCH 153/228] Update readme --- README.md | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index d4677038..934ae814 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,15 @@ # go-toml V2 -Development branch. Probably does not work. +Development branch. Use at your own risk. [👉 Discussion on github](https://github.com/pelletier/go-toml/discussions/471). +* `toml.Unmarshal()` should work as well as v1. + ## Must do +### Unmarshal + - [x] Unmarshal into maps. - [x] Support Array Tables. - [x] Unmarshal into pointers. @@ -18,11 +22,20 @@ Development branch. Probably does not work. - [x] Abstract AST. - [x] Original go-toml testgen tests pass. - [x] Track file position (line, column) for errors. -- [ ] Attach comments to AST (gated by parser flag). -- [ ] Benchmark again! +- [ ] Strict mode. + +### Marshal + +- [ ] Minimal implementation + +### Document + +- [ ] Gather requirements and design API. -## Further work +## Ideas +- [ ] Allow types to implement a `ASTUnmarshaler` interface to unmarshal + straight from the AST? - [x] Rewrite AST to use a single array as storage instead of one allocation per node. - [ ] Provide "minimal allocations" option that uses `unsafe` to reuse the input @@ -30,10 +43,9 @@ Development branch. Probably does not work. - [x] Cache reflection operations per type. - [ ] Optimize tracker pass. -## Ideas +## Differences with v1 -- [ ] Allow types to implement a `ASTUnmarshaler` interface to unmarshal - straight from the AST? +* [unmarshal](https://github.com/pelletier/go-toml/discussions/488) ## License From 92b16cad912243edb74f26077ee81c6ac8495aea Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Wed, 31 Mar 2021 09:57:19 -0400 Subject: [PATCH 154/228] Simplify context implementation and fix new lines bug --- errors.go | 53 +++++++++++++++++++++++++++++--------------------- errors_test.go | 22 +++++++++++++++++++++ 2 files changed, 53 insertions(+), 22 deletions(-) diff --git a/errors.go b/errors.go index cc0baba4..79b59bb0 100644 --- a/errors.go +++ b/errors.go @@ -128,33 +128,42 @@ func formatLineNumber(line int, width int) string { func linesOfContext(document []byte, highlight []byte, offset int, linesAround int) ([][]byte, [][]byte) { var beforeLines [][]byte - for beforeOffset, lastOffset := offset, offset; beforeOffset >= 0 && len(beforeLines) <= linesAround; beforeOffset-- { - if beforeOffset == len(document) { - beforeLines = append(beforeLines, []byte{}) - continue - } - if document[beforeOffset] == '\n' { - if beforeOffset == lastOffset { - beforeLines = append(beforeLines, []byte{}) - } else { - beforeLines = append(beforeLines, document[beforeOffset+1:lastOffset]) - } - lastOffset = beforeOffset - } else if beforeOffset == 0 && beforeOffset != lastOffset { - beforeLines = append(beforeLines, document[beforeOffset:lastOffset]) + + // Walk the document in reverse from the highlight to find previous lines + // of context. + rest := document[:offset] + for o := len(rest) - 1; o >= 0 && len(beforeLines) <= linesAround && len(rest) > 0; { + if rest[o] == '\n' { + // handle individual lines + beforeLines = append(beforeLines, rest[o+1:]) + rest = rest[:o] + o = len(rest) - 1 + } else if o == 0 { + // add the first line only if it's non-empty + beforeLines = append(beforeLines, rest) + break + } else { + o-- } } var afterLines [][]byte - document = document[offset+len(highlight):] - for afterOffset, lastOffset := 0, 0; afterOffset < len(document) && len(afterLines) <= linesAround; afterOffset++ { - if document[afterOffset] == '\n' { - afterLines = append(afterLines, document[lastOffset:afterOffset]) - afterOffset++ // skip \n - lastOffset = afterOffset - } else if afterOffset == len(document)-1 && lastOffset != afterOffset+1 { - afterLines = append(afterLines, document[lastOffset:afterOffset+1]) + // Walk the document forward from the highlight to find the following + // lines of context. + rest = document[offset+len(highlight):] + for o := 0; o < len(rest) && len(afterLines) <= linesAround; { + if rest[o] == '\n' { + // handle individual lines + afterLines = append(afterLines, rest[:o]) + rest = rest[o+1:] + o = 0 + } else if o == len(rest)-1 && o > 0 { + // add last line only if it's non-empty + afterLines = append(afterLines, rest) + break + } else { + o++ } } return beforeLines, afterLines diff --git a/errors_test.go b/errors_test.go index e1354e23..6f49d568 100644 --- a/errors_test.go +++ b/errors_test.go @@ -121,6 +121,28 @@ before `, "highlighted", ``}, 10| line3 11| before highlighted | ~~~~~~~~~~~ this is wrong +`, + }, + { + desc: "handle empty lines in the before/after blocks", + doc: [3]string{`line1 + +line 2 +before `, "highlighted", ` after +line 3 + +line 4 +line 5`, + }, + expected: ` +1| line1 +2| +3| line 2 +4| before highlighted after + | ~~~~~~~~~~~ +5| line 3 +6| +7| line 4 `, }, } From 5f877c52fdf65fceca9680ab38e6bcc78ef9b02f Mon Sep 17 00:00:00 2001 From: Cameron Moore Date: Thu, 1 Apr 2021 09:13:13 -0500 Subject: [PATCH 155/228] Add additional dataset to benchmarks (#490) Adding several files to stress test the unmarshaller. Most were converted from JSON so they may not be very realistic use cases. ``` goos: linux goarch: amd64 pkg: github.com/pelletier/go-toml/v2/benchmark cpu: Intel(R) Core(TM) i7-7600U CPU @ 2.80GHz BenchmarkUnmarshalDataset/config/v2-2 16 66339063 ns/op 15.81 MB/s 16850159 B/op 645454 allocs/op BenchmarkUnmarshalDataset/config/v1-2 7 147289186 ns/op 7.12 MB/s 60669811 B/op 870486 allocs/op BenchmarkUnmarshalDataset/config/bs-2 12 88966009 ns/op 11.79 MB/s 29949268 B/op 705573 allocs/op BenchmarkUnmarshalDataset/canada/v2-2 8 145433377 ns/op 15.14 MB/s 74282227 B/op 895641 allocs/op BenchmarkUnmarshalDataset/canada/v1-2 3 434913677 ns/op 5.06 MB/s 138664290 B/op 1897300 allocs/op BenchmarkUnmarshalDataset/citm_catalog/v2-2 19 55256979 ns/op 10.10 MB/s 37122952 B/op 369492 allocs/op BenchmarkUnmarshalDataset/citm_catalog/v1-2 10 110343191 ns/op 5.06 MB/s 54743595 B/op 727704 allocs/op BenchmarkUnmarshalDataset/citm_catalog/bs-2 21 51634081 ns/op 10.81 MB/s 17196872 B/op 325830 allocs/op BenchmarkUnmarshalDataset/twitter/v2-2 50 26660937 ns/op 16.57 MB/s 15580238 B/op 156394 allocs/op BenchmarkUnmarshalDataset/twitter/v1-2 30 43128488 ns/op 10.25 MB/s 21203420 B/op 266110 allocs/op BenchmarkUnmarshalDataset/twitter/bs-2 48 27337976 ns/op 16.16 MB/s 8795405 B/op 145370 allocs/op BenchmarkUnmarshalDataset/code/v2-2 4 276279202 ns/op 9.71 MB/s 59293948 B/op 2907227 allocs/op BenchmarkUnmarshalDataset/code/v1-2 3 421910642 ns/op 6.36 MB/s 161733770 B/op 2478194 allocs/op BenchmarkUnmarshalDataset/code/bs-2 4 323158157 ns/op 8.31 MB/s 133056988 B/op 1439475 allocs/op BenchmarkUnmarshalDataset/example/v2-2 2444 479553 ns/op 16.89 MB/s 237606 B/op 3609 allocs/op BenchmarkUnmarshalDataset/example/v1-2 1321 911995 ns/op 8.88 MB/s 377502 B/op 6133 allocs/op BenchmarkUnmarshalDataset/example/bs-2 1898 555649 ns/op 14.58 MB/s 178485 B/op 3362 allocs/op BenchmarkUnmarshalSimple/v2-2 896760 1200 ns/op BenchmarkUnmarshalSimple/v1-2 207364 6070 ns/op BenchmarkUnmarshalSimple/bs-2 420952 2925 ns/op BenchmarkReferenceFile/v2-2 29473 39433 ns/op 132.93 MB/s 11812 B/op 253 allocs/op BenchmarkReferenceFile/v1-2 2823 361383 ns/op 14.51 MB/s 136470 B/op 2745 allocs/op BenchmarkReferenceFile/bs-2 3097 391116 ns/op 13.40 MB/s 80795 B/op 1729 allocs/op PASS ok github.com/pelletier/go-toml/v2/benchmark 34.255s ``` --- benchmark/bench_datasets_test.go | 68 ++++++++++++++++++++++++ benchmark/testdata/canada.toml.gz | Bin 0 -> 555790 bytes benchmark/testdata/citm_catalog.toml.gz | Bin 0 -> 18005 bytes benchmark/testdata/code.toml.gz | Bin 0 -> 128349 bytes benchmark/testdata/config.toml.gz | Bin 0 -> 28280 bytes benchmark/testdata/example.toml.gz | Bin 0 -> 2002 bytes benchmark/testdata/twitter.toml.gz | Bin 0 -> 45302 bytes 7 files changed, 68 insertions(+) create mode 100644 benchmark/bench_datasets_test.go create mode 100644 benchmark/testdata/canada.toml.gz create mode 100644 benchmark/testdata/citm_catalog.toml.gz create mode 100644 benchmark/testdata/code.toml.gz create mode 100644 benchmark/testdata/config.toml.gz create mode 100644 benchmark/testdata/example.toml.gz create mode 100644 benchmark/testdata/twitter.toml.gz diff --git a/benchmark/bench_datasets_test.go b/benchmark/bench_datasets_test.go new file mode 100644 index 00000000..34c892f7 --- /dev/null +++ b/benchmark/bench_datasets_test.go @@ -0,0 +1,68 @@ +package benchmark_test + +import ( + "compress/gzip" + "io/ioutil" + "os" + "path/filepath" + "testing" +) + +var bench_inputs = []string{ + // from https://gist.githubusercontent.com/feeeper/2197d6d734729625a037af1df14cf2aa/raw/2f22b120e476d897179be3c1e2483d18067aa7df/config.toml + "config", + + // converted from https://github.com/miloyip/nativejson-benchmark + "canada", + "citm_catalog", + "twitter", + "code", + + // converted from https://raw.githubusercontent.com/mailru/easyjson/master/benchmark/example.json + "example", +} + +func BenchmarkUnmarshalDataset(b *testing.B) { + for _, tc := range bench_inputs { + buf := fixture(b, tc) + b.Run(tc, func(b *testing.B) { + bench(b, func(r runner, b *testing.B) { + if r.name == "bs" && tc == "canada" { + // bs can't handle the canada dataset due to mixed integer & + // floats values in an array. + b.Skip() + } + + b.SetBytes(int64(len(buf))) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + var v interface{} + check(b, r.unmarshal(buf, &v)) + } + }) + }) + } +} + +// fixture returns the uncompressed contents of path. +func fixture(tb testing.TB, path string) []byte { + f, err := os.Open(filepath.Join("testdata", path+".toml.gz")) + check(tb, err) + defer f.Close() + + gz, err := gzip.NewReader(f) + check(tb, err) + + buf, err := ioutil.ReadAll(gz) + check(tb, err) + + return buf +} + +func check(tb testing.TB, err error) { + if err != nil { + tb.Helper() + tb.Fatal(err) + } +} diff --git a/benchmark/testdata/canada.toml.gz b/benchmark/testdata/canada.toml.gz new file mode 100644 index 0000000000000000000000000000000000000000..eb895baba66ed2e926850d343809964c34e382d0 GIT binary patch literal 555790 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

h8@{a&$iNa#90$OHB%)j4kG^hTl-9#zPUT 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%0AN`|QeUmPtudz^+K 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 literal 0 HcmV?d00001 diff --git a/benchmark/testdata/code.toml.gz b/benchmark/testdata/code.toml.gz new file mode 100644 index 0000000000000000000000000000000000000000..0885720a8f2d177ad2b3dc91b3e92639f4ef26d5 GIT binary patch literal 128349 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�t)&b+fP{|AyWx`jS0Zhsr0QvE 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

Cu}r?o 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 G|NI{vtIv1< literal 0 HcmV?d00001 diff --git a/benchmark/testdata/config.toml.gz b/benchmark/testdata/config.toml.gz new file mode 100644 index 0000000000000000000000000000000000000000..8c95e92fb2d86a00872ff8149dd495c3ef23e97f GIT binary patch literal 28280 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

ZIt1oFN*kq88zTxXrG&oK2fyasANC0Ok9|nrP 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 N+EJKRep~i$`G2g@*opuE literal 0 HcmV?d00001 diff --git a/benchmark/testdata/example.toml.gz b/benchmark/testdata/example.toml.gz new file mode 100644 index 0000000000000000000000000000000000000000..b2a76e256b526beabd1831fe8644c0fe7a484ab1 GIT binary patch literal 2002 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 literal 0 HcmV?d00001 From af00765ca07b2efa7b467d3707b24022a6e48dd6 Mon Sep 17 00:00:00 2001 From: jidicula Date: Mon, 5 Apr 2021 14:30:17 -0400 Subject: [PATCH 156/228] Address golangci-lint warnings in unmarshal_imported_test.go (#493) * refactor(tracker): Remove unreachable return * refactor(unmarshal_imported_test): Mark unused camelCase test golangci-lint indicates `TestUnmarshalCamelCaseKey` as unused (it's currently skipped). * refactor(unmarshal_imported_test): Mark unused type golangci-lint indicates `customPointerMarshaler` as unused. * refactor(unmarshal_imported_test): Mark unused type golangci-lint indicates `textPointerMarshaler` as unused. * refactor(unmarshal_imported_test): Mark unused type golangci-lint indicates `precedentMarshaler` and its methods as unused. * refactor(unmarshal_imported_test): Mark unused var golangci-lint indicates `testDurationToml2` as unused. * refactor(unmarshal_imported_test): Mark unused type golangci-lint indicates `testBadDuration` as unused. * refactor(unmarshal_imported_test): Mark unused var golangci-lint indicates `testDurationToml` as unused. * refactor(unmarshal_imported_test): Mark unused type golangci-lint indicates `testDuration` as unused. * refactor(unmarshal_imported_test): Mark unused type golangci-lint indicates `testDocCustomTag` as unused. * refactor(unmarshal_imported_test): Mark unused var golangci-lint indicates `testDocCustomTagData` as unused. * refactor(unmarshal_imported_test): Mark unused type golangci-lint indicates `testDocBasicsCustomTag` as unused. * refactor(unmarshal_imported_test): Mark unused var golangci-lint indicates `testDocBasicToml` as unused. * refactor(unmarshal_imported_test): Mark unused type golangci-lint indicates `structArrayNoTag` as unused. * refactor(unmarshal_imported_test): Mark unused type golangci-lint incorrectly indicates `check` as unused. * refactor(unmarshal_imported_test): Mark unused struct field golangci-lint indicates `testDoc.err` as unused. * refactor(unmarshal_imported_test): Mark unused var golangci-lint indicates `customMultilineTagTestToml` as unused. * refactor(unmarshal_imported_test): Mark unused var golangci-lint indicates `customCommentedTagTestToml` as unused. * refactor(unmarshal_imported_test): Mark unused var golangci-lint indicates `customCommentTagTestToml` as unused. * refactor(unmarshal_imported_test): Mark unused var golangci-lint indicates `customTagTestToml` as unused. * refactor(unmarshal_imported_test): Mark unused var golangci-lint indicates `mapsTestToml` as unused. * refactor(unmarshal_imported_test): Mark unused var golangci-lint indicates `mapsTestData` as unused. * refactor(unmarshal_imported_test): Mark unused var golangci-lint indicates `commentTestToml` as unused. * refactor(unmarshal_imported_test): Mark unused var golangci-lint indicates `nestedCustomMarshalerToml` as unused. * refactor(unmarshal_imported_test): Mark unused var golangci-lint indicates `nestedCustomMarshalerData` as unused. * refactor(unmarshal_imported_test): Mark unused var golangci-lint indicates `customMarshalerToml` as unused. * refactor(unmarshal_imported_test): Mark unused var golangci-lint indicates `mapTestDoc` as unused. * refactor(unmarshal_imported_test): Mark unused var golangci-lint indicates `quotedKeyMarshalTestToml` as unused. * refactor(unmarshal_imported_test): Mark unused var golangci-lint indicates `quotedKeyMarshalTestData` as unused. --- .../imported_tests/unmarshal_imported_test.go | 67 ++++++++++++++++++- internal/tracker/tracker.go | 2 +- 2 files changed, 65 insertions(+), 4 deletions(-) diff --git a/internal/imported_tests/unmarshal_imported_test.go b/internal/imported_tests/unmarshal_imported_test.go index 5e113abb..59585435 100644 --- a/internal/imported_tests/unmarshal_imported_test.go +++ b/internal/imported_tests/unmarshal_imported_test.go @@ -149,6 +149,8 @@ type quotedKeyMarshalTestStruct struct { SubList []basicMarshalTestSubStruct `toml:"W.sublist-𝟘"` } +// TODO: Remove nolint once var is used by a test +//nolint:deadcode,unused,varcheck var quotedKeyMarshalTestData = quotedKeyMarshalTestStruct{ String: "Hello", Float: 3.5, @@ -156,6 +158,8 @@ var quotedKeyMarshalTestData = quotedKeyMarshalTestStruct{ SubList: []basicMarshalTestSubStruct{{"Two"}, {"Three"}}, } +// TODO: Remove nolint once var is used by a test +//nolint:deadcode,unused,varcheck var quotedKeyMarshalTestToml = []byte(`"Yfloat-𝟘" = 3.5 "Z.string-àéù" = "Hello" @@ -177,7 +181,7 @@ type testDoc struct { Subdocs testDocSubs `toml:"subdoc"` Basics testDocBasics `toml:"basic"` SubDocList []testSubDoc `toml:"subdoclist"` - err int `toml:"shouldntBeHere"` + err int `toml:"shouldntBeHere"` // nolint:structcheck,unused unexported int `toml:"shouldntBeHere"` Unexported2 int `toml:"-"` } @@ -264,6 +268,8 @@ var docData = testDoc{ SubDocPtrs: []*testSubDoc{&subdoc}, } +// TODO: Remove nolint once var is used by a test +//nolint:deadcode,unused,varcheck var mapTestDoc = testMapDoc{ Title: "TOML Marshal Testing", BasicMap: map[string]string{ @@ -573,11 +579,20 @@ func (c customMarshaler) MarshalTOML() ([]byte, error) { } var customMarshalerData = customMarshaler{FirstName: "Sally", LastName: "Fields"} + +// TODO: Remove nolint once var is used by a test +//nolint:deadcode,unused,varcheck var customMarshalerToml = []byte(`Sally Fields`) + +// TODO: Remove nolint once var is used by a test +//nolint:deadcode,unused,varcheck var nestedCustomMarshalerData = customMarshalerParent{ Self: customMarshaler{FirstName: "Maiku", LastName: "Suteda"}, Friends: []customMarshaler{customMarshalerData}, } + +// TODO: Remove nolint once var is used by a test +//nolint:deadcode,unused,varcheck var nestedCustomMarshalerToml = []byte(`friends = ["Sally Fields"] me = "Maiku Suteda" `) @@ -626,38 +641,50 @@ func TestUnmarshalTextMarshaler(t *testing.T) { } } +// TODO: Remove nolint once type and methods are used by a test +//nolint:unused type precedentMarshaler struct { FirstName string LastName string } +//nolint:unused func (m precedentMarshaler) MarshalText() ([]byte, error) { return []byte("shadowed"), nil } +//nolint:unused func (m precedentMarshaler) MarshalTOML() ([]byte, error) { fullName := fmt.Sprintf("%s %s", m.FirstName, m.LastName) return []byte(fullName), nil } +// TODO: Remove nolint once type and method are used by a test +//nolint:unused type customPointerMarshaler struct { FirstName string LastName string } +//nolint:unused func (m *customPointerMarshaler) MarshalTOML() ([]byte, error) { return []byte(`"hidden"`), nil } +// TODO: Remove nolint once type and method are used by a test +//nolint:unused type textPointerMarshaler struct { FirstName string LastName string } +//nolint:unused func (m *textPointerMarshaler) MarshalText() ([]byte, error) { return []byte("hidden"), nil } +// TODO: Remove nolint once var is used by a test +//nolint:deadcode,unused,varcheck var commentTestToml = []byte(` # it's a comment on type [postgres] @@ -693,6 +720,8 @@ type mapsTestStruct struct { } } +// TODO: Remove nolint once var is used by a test +//nolint:deadcode,unused,varcheck var mapsTestData = mapsTestStruct{ Simple: map[string]string{ "one plus one": "two", @@ -713,6 +742,9 @@ var mapsTestData = mapsTestStruct{ }, }, } + +// TODO: Remove nolint once var is used by a test +//nolint:deadcode,unused,varcheck var mapsTestToml = []byte(` [Other] "testing" = 3.9999 @@ -733,6 +765,8 @@ var mapsTestToml = []byte(` "is.Nested" = true `) +// TODO: Remove nolint once type is used by a test +//nolint:deadcode,unused type structArrayNoTag struct { A struct { B []int64 @@ -740,6 +774,8 @@ type structArrayNoTag struct { } } +// TODO: Remove nolint once var is used by a test +//nolint:deadcode,unused,varcheck var customTagTestToml = []byte(` [postgres] password = "bvalue" @@ -752,6 +788,8 @@ var customTagTestToml = []byte(` My = "Baar" `) +// TODO: Remove nolint once var is used by a test +//nolint:deadcode,unused,varcheck var customCommentTagTestToml = []byte(` # db connection [postgres] @@ -763,6 +801,8 @@ var customCommentTagTestToml = []byte(` user = "avalue" `) +// TODO: Remove nolint once var is used by a test +//nolint:deadcode,unused,varcheck var customCommentedTagTestToml = []byte(` [postgres] # password = "bvalue" @@ -815,6 +855,8 @@ func TestUnmarshalTabInStringAndQuotedKey(t *testing.T) { } } +// TODO: Remove nolint once var is used by a test +//nolint:deadcode,unused,varcheck var customMultilineTagTestToml = []byte(`int_slice = [ 1, 2, @@ -822,6 +864,8 @@ var customMultilineTagTestToml = []byte(`int_slice = [ ] `) +// TODO: Remove nolint once var is used by a test +//nolint:deadcode,unused,varcheck var testDocBasicToml = []byte(` [document] bool_val = true @@ -832,9 +876,14 @@ var testDocBasicToml = []byte(` uint_val = 5001 `) +// TODO: Remove nolint once type is used by a test +//nolint:deadcode type testDocCustomTag struct { Doc testDocBasicsCustomTag `file:"document"` } + +// TODO: Remove nolint once type is used by a test +//nolint:deadcode type testDocBasicsCustomTag struct { Bool bool `file:"bool_val"` Date time.Time `file:"date_val"` @@ -845,6 +894,8 @@ type testDocBasicsCustomTag struct { unexported int `file:"shouldntBeHere"` } +// TODO: Remove nolint once var is used by a test +//nolint:deadcode,varcheck var testDocCustomTagData = testDocCustomTag{ Doc: testDocBasicsCustomTag{ Bool: true, @@ -922,6 +973,8 @@ func TestUnmarshalInvalidPointerKind(t *testing.T) { assert.Error(t, err) } +// TODO: Remove nolint once var is used by a test +//nolint:deadcode,unused type testDuration struct { Nanosec time.Duration `toml:"nanosec"` Microsec1 time.Duration `toml:"microsec1"` @@ -934,6 +987,8 @@ type testDuration struct { AString string `toml:"a_string"` } +// TODO: Remove nolint once var is used by a test +//nolint:deadcode,unused,varcheck var testDurationToml = []byte(` nanosec = "1ns" microsec1 = "1us" @@ -946,6 +1001,8 @@ mixed = "1h1m1s1ms1µs1ns" a_string = "15s" `) +// TODO: Remove nolint once var is used by a test +//nolint:deadcode,unused,varcheck var testDurationToml2 = []byte(`a_string = "15s" hour = "1h0m0s" microsec1 = "1µs" @@ -957,12 +1014,16 @@ nanosec = "1ns" sec = "1s" `) +// TODO: Remove nolint once type is used by a test +//nolint:deadcode,unused type testBadDuration struct { Val time.Duration `toml:"val"` } -var testCamelCaseKeyToml = []byte(`fooBar = 10`) +// TODO: add back camelCase test +var testCamelCaseKeyToml = []byte(`fooBar = 10`) //nolint:unused +//nolint:unused func TestUnmarshalCamelCaseKey(t *testing.T) { t.Skipf("don't know if it is a good idea to automatically convert like that yet") var x struct { @@ -981,7 +1042,7 @@ func TestUnmarshalCamelCaseKey(t *testing.T) { func TestUnmarshalNegativeUint(t *testing.T) { t.Skipf("not sure if we this should always error") - type check struct{ U uint } + type check struct{ U uint } // nolint:unused err := toml.Unmarshal([]byte("U = -1"), &check{}) assert.Error(t, err) } diff --git a/internal/tracker/tracker.go b/internal/tracker/tracker.go index a6f6252e..97f916b4 100644 --- a/internal/tracker/tracker.go +++ b/internal/tracker/tracker.go @@ -97,7 +97,7 @@ func (s *Seen) CheckExpression(node ast.Node) error { default: panic(fmt.Errorf("this should not be a top level node type: %s", node.Kind)) } - return nil + } func (s *Seen) checkTable(node ast.Node) error { s.current = s.root From 18af62d3eac5409f3fe1f23f39735d40efd9a709 Mon Sep 17 00:00:00 2001 From: Vincent Serpoul Date: Thu, 8 Apr 2021 01:39:01 +0800 Subject: [PATCH 157/228] Golangci-lint v2 part one (#492) --- .golangci.toml | 81 ++++++++++++++++++++++++++++++ decode.go | 131 +++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 187 insertions(+), 25 deletions(-) create mode 100644 .golangci.toml diff --git a/.golangci.toml b/.golangci.toml new file mode 100644 index 00000000..2124de66 --- /dev/null +++ b/.golangci.toml @@ -0,0 +1,81 @@ +[service] +golangci-lint-version = "1.39.0" + +[linters-settings.wsl] +allow-assign-and-anything = true + +[linters] +disable-all = true +enable = [ + "asciicheck", + "bodyclose", + "cyclop", + "deadcode", + "depguard", + "dogsled", + "dupl", + "durationcheck", + "errcheck", + "errorlint", + "exhaustive", + "exhaustivestruct", + "exportloopref", + "forbidigo", + "forcetypeassert", + "funlen", + "gci", + "gochecknoglobals", + "gochecknoinits", + "gocognit", + "goconst", + "gocritic", + "gocyclo", + "godot", + "godox", + "goerr113", + "gofmt", + "gofumpt", + "goheader", + "goimports", + "golint", + "gomnd", + # "gomoddirectives", + "gomodguard", + "goprintffuncname", + "gosec", + "gosimple", + "govet", + "ifshort", + "importas", + "ineffassign", + "lll", + "makezero", + "misspell", + "nakedret", + "nestif", + "nilerr", + "nlreturn", + "noctx", + "nolintlint", + "paralleltest", + "prealloc", + "predeclared", + "revive", + "rowserrcheck", + "sqlclosecheck", + "staticcheck", + "structcheck", + "stylecheck", + # "testpackage", + "thelper", + "tparallel", + "typecheck", + "unconvert", + "unparam", + "unused", + "varcheck", + "wastedassign", + "whitespace", + "wrapcheck", + "wsl" +] diff --git a/decode.go b/decode.go index d4b85594..938587be 100644 --- a/decode.go +++ b/decode.go @@ -22,6 +22,7 @@ func parseInteger(b []byte) (int64, error) { return 0, newDecodeError(b[1:2], "invalid base: '%c'", b[1]) } } + return parseIntDec(b) } @@ -30,8 +31,7 @@ func parseLocalDate(b []byte) (LocalDate, error) { // date-fullyear = 4DIGIT // date-month = 2DIGIT ; 01-12 // date-mday = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on month/year - - date := LocalDate{} + var date LocalDate if len(b) != 10 || b[4] != '-' || b[7] != '-' { return date, newDecodeError(b, "dates are expected to have the format YYYY-MM-DD") @@ -48,31 +48,41 @@ func parseLocalDate(b []byte) (LocalDate, error) { if err != nil { return date, err } + date.Month = time.Month(v) date.Day, err = parseDecimalDigits(b[8:10]) + if err != nil { + return date, err + } return date, nil } +var errNotDigit = errors.New("not a digit") + func parseDecimalDigits(b []byte) (int, error) { v := 0 + for _, c := range b { if !isDigit(c) { - return 0, fmt.Errorf("expected digit") + return 0, fmt.Errorf("%s: %w", b, errNotDigit) } + v *= 10 v += int(c - '0') } + return v, nil } +var errParseDateTimeMissingInfo = errors.New("date-time missing timezone information") + func parseDateTime(b []byte) (time.Time, error) { // offset-date-time = full-date time-delim full-time // full-time = partial-time time-offset // time-offset = "Z" / time-numoffset // time-numoffset = ( "+" / "-" ) time-hour ":" time-minute - dt, b, err := parseLocalDateTime(b) if err != nil { return time.Time{}, err @@ -81,14 +91,15 @@ func parseDateTime(b []byte) (time.Time, error) { var zone *time.Location if len(b) == 0 { - return time.Time{}, fmt.Errorf("date-time missing timezone information") + return time.Time{}, errParseDateTimeMissingInfo } if b[0] == 'Z' { b = b[1:] zone = time.UTC } else { - if len(b) != 6 { + const dateTimeByteLen = 6 + if len(b) != dateTimeByteLen { return time.Time{}, newDecodeError(b, "invalid date-time timezone") } direction := 1 @@ -123,11 +134,19 @@ func parseDateTime(b []byte) (time.Time, error) { return t, nil } +var ( + errParseLocalDateTimeWrongLength = errors.New( + "local datetimes are expected to have the format YYYY-MM-DDTHH:MM:SS[.NNNNNN]", + ) + errParseLocalDateTimeWrongSeparator = errors.New("datetime separator is expected to be T or a space") +) + func parseLocalDateTime(b []byte) (LocalDateTime, []byte, error) { - dt := LocalDateTime{} + var dt LocalDateTime - if len(b) < 11 { - return dt, nil, fmt.Errorf("local datetimes are expected to have the format YYYY-MM-DDTHH:MM:SS[.NNNNNN]") + const localDateTimeByteLen = 11 + if len(b) < localDateTimeByteLen { + return dt, nil, errParseLocalDateTimeWrongLength } date, err := parseLocalDate(b[:10]) @@ -138,7 +157,7 @@ func parseLocalDateTime(b []byte) (LocalDateTime, []byte, error) { sep := b[10] if sep != 'T' && sep != ' ' { - return dt, nil, fmt.Errorf("datetime separator is expected to be T or a space") + return dt, nil, errParseLocalDateTimeWrongSeparator } t, rest, err := parseLocalTime(b[11:]) @@ -150,31 +169,39 @@ func parseLocalDateTime(b []byte) (LocalDateTime, []byte, error) { return dt, rest, nil } +var errParseLocalTimeWrongLength = errors.New("times are expected to have the format HH:MM:SS[.NNNNNN]") + // parseLocalTime is a bit different because it also returns the remaining // []byte that is didn't need. This is to allow parseDateTime to parse those // remaining bytes as a timezone. func parseLocalTime(b []byte) (LocalTime, []byte, error) { - t := LocalTime{} + var t LocalTime - if len(b) < 8 { - return t, nil, fmt.Errorf("times are expected to have the format HH:MM:SS[.NNNNNN]") + const localTimeByteLen = 8 + if len(b) < localTimeByteLen { + return t, nil, errParseLocalTimeWrongLength } var err error + t.Hour, err = parseDecimalDigits(b[0:2]) if err != nil { return t, nil, err } + if b[2] != ':' { return t, nil, newDecodeError(b[2:3], "expecting colon between hours and minutes") } + t.Minute, err = parseDecimalDigits(b[3:5]) if err != nil { return t, nil, err } + if b[5] != ':' { return t, nil, newDecodeError(b[5:6], "expecting colon between minutes and seconds") } + t.Second, err = parseDecimalDigits(b[6:8]) if err != nil { return t, nil, err @@ -182,79 +209,127 @@ func parseLocalTime(b []byte) (LocalTime, []byte, error) { if len(b) >= 15 && b[8] == '.' { t.Nanosecond, err = parseDecimalDigits(b[9:15]) + if err != nil { + return t, nil, err + } + return t, b[15:], nil } return t, b[8:], nil } +var ( + errParseFloatStartDot = errors.New("float cannot start with a dot") + errParseFloatEndDot = errors.New("float cannot end with a dot") +) + +//nolint:cyclop func parseFloat(b []byte) (float64, error) { + //nolint:godox // TODO: inefficient if len(b) == 4 && (b[0] == '+' || b[0] == '-') && b[1] == 'n' && b[2] == 'a' && b[3] == 'n' { return math.NaN(), nil } tok := string(b) + err := numberContainsInvalidUnderscore(tok) if err != nil { return 0, err } + cleanedVal := cleanupNumberToken(tok) if cleanedVal[0] == '.' { - return 0, fmt.Errorf("float cannot start with a dot") + return 0, errParseFloatStartDot } + if cleanedVal[len(cleanedVal)-1] == '.' { - return 0, fmt.Errorf("float cannot end with a dot") + return 0, errParseFloatEndDot + } + + f, err := strconv.ParseFloat(cleanedVal, 64) + if err != nil { + return 0, fmt.Errorf("coudn't ParseFloat %w", err) } - return strconv.ParseFloat(cleanedVal, 64) + + return f, nil } func parseIntHex(b []byte) (int64, error) { tok := string(b) cleanedVal := cleanupNumberToken(tok) + err := hexNumberContainsInvalidUnderscore(cleanedVal) if err != nil { - return 0, nil + return 0, err + } + + i, err := strconv.ParseInt(cleanedVal[2:], 16, 64) + if err != nil { + return 0, fmt.Errorf("coudn't ParseIntHex %w", err) } - return strconv.ParseInt(cleanedVal[2:], 16, 64) + + return i, nil } func parseIntOct(b []byte) (int64, error) { tok := string(b) cleanedVal := cleanupNumberToken(tok) + err := numberContainsInvalidUnderscore(cleanedVal) if err != nil { return 0, err } - return strconv.ParseInt(cleanedVal[2:], 8, 64) + + i, err := strconv.ParseInt(cleanedVal[2:], 8, 64) + if err != nil { + return 0, fmt.Errorf("coudn't ParseIntOct %w", err) + } + + return i, nil } func parseIntBin(b []byte) (int64, error) { tok := string(b) cleanedVal := cleanupNumberToken(tok) + err := numberContainsInvalidUnderscore(cleanedVal) if err != nil { return 0, err } - return strconv.ParseInt(cleanedVal[2:], 2, 64) + + i, err := strconv.ParseInt(cleanedVal[2:], 2, 64) + if err != nil { + return 0, fmt.Errorf("coudn't ParseIntBin %w", err) + } + + return i, nil } func parseIntDec(b []byte) (int64, error) { tok := string(b) cleanedVal := cleanupNumberToken(tok) + err := numberContainsInvalidUnderscore(cleanedVal) if err != nil { return 0, err } - return strconv.ParseInt(cleanedVal, 10, 64) + + i, err := strconv.ParseInt(cleanedVal, 10, 64) + if err != nil { + return 0, fmt.Errorf("coudn't parseIntDec %w", err) + } + + return i, nil } func numberContainsInvalidUnderscore(value string) error { // For large numbers, you may use underscores between digits to enhance // readability. Each underscore must be surrounded by at least one digit on // each side. - hasBefore := false + for idx, r := range value { if r == '_' { if !hasBefore || idx+1 >= len(value) { @@ -264,11 +339,13 @@ func numberContainsInvalidUnderscore(value string) error { } hasBefore = isDigitRune(r) } + return nil } func hexNumberContainsInvalidUnderscore(value string) error { hasBefore := false + for idx, r := range value { if r == '_' { if !hasBefore || idx+1 >= len(value) { @@ -278,11 +355,13 @@ func hexNumberContainsInvalidUnderscore(value string) error { } hasBefore = isHexDigit(r) } + return nil } func cleanupNumberToken(value string) string { - cleanedVal := strings.Replace(value, "_", "", -1) + cleanedVal := strings.ReplaceAll(value, "_", "") + return cleanedVal } @@ -296,5 +375,7 @@ func isDigitRune(r rune) bool { return r >= '0' && r <= '9' } -var errInvalidUnderscore = errors.New("invalid use of _ in number") -var errInvalidUnderscoreHex = errors.New("invalid use of _ in hex number") +var ( + errInvalidUnderscore = errors.New("invalid use of _ in number") + errInvalidUnderscoreHex = errors.New("invalid use of _ in hex number") +) From 275e366c17446746c9cddae71147eaada7c08da5 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Thu, 8 Apr 2021 08:00:31 -0400 Subject: [PATCH 158/228] decoder: handle casting local date into time.Time Refs #494 --- README.md | 1 + unmarshaler.go | 17 +++++++++++++++++ unmarshaler_test.go | 15 +++++++++++++++ 3 files changed, 33 insertions(+) diff --git a/README.md b/README.md index 934ae814..a3cccfcf 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ Development branch. Use at your own risk. - [x] Original go-toml testgen tests pass. - [x] Track file position (line, column) for errors. - [ ] Strict mode. +- [ ] Document Unmarshal / Decode ### Marshal diff --git a/unmarshaler.go b/unmarshaler.go index 0f897032..1184e63e 100644 --- a/unmarshaler.go +++ b/unmarshaler.go @@ -30,6 +30,9 @@ func NewDecoder(r io.Reader) *Decoder { } // Decode the whole content of r into v. +// +// When a TOML local date is decoded into a time.Time, its value is represented +// in time.Local timezone. func (d *Decoder) Decode(v interface{}) error { b, err := ioutil.ReadAll(d.r) if err != nil { @@ -224,6 +227,13 @@ func tryTextUnmarshaler(x target, node ast.Node) (bool, error) { if v.Kind() != reflect.Struct { return false, nil } + + // Special case for time, becase we allow to unmarshal to it from + // different kind of AST nodes. + if v.Type() == timeType { + return false, nil + } + if v.Type().Implements(textUnmarshalerType) { return true, v.Interface().(encoding.TextUnmarshaler).UnmarshalText(node.Data) } @@ -313,7 +323,14 @@ func setDateTime(x target, v time.Time) error { return x.set(reflect.ValueOf(v)) } +var timeType = reflect.TypeOf(time.Time{}) + func setDate(x target, v LocalDate) error { + if x.get().Type() == timeType { + cast := v.In(time.Local) + return setDateTime(x, cast) + } + return x.set(reflect.ValueOf(v)) } diff --git a/unmarshaler_test.go b/unmarshaler_test.go index ac874eb0..2aa5daf9 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -4,6 +4,7 @@ import ( "math" "strconv" "testing" + "time" "github.com/pelletier/go-toml/v2" "github.com/stretchr/testify/assert" @@ -780,6 +781,20 @@ val1 = "test1" require.Equal(t, "test2", cfg.Val2) } +func TestIssue494(t *testing.T) { + data := ` +foo = 2021-04-08 +bar = 2021-04-08 +` + type s struct { + Foo time.Time `toml:"foo"` + Bar time.Time `toml:"bar"` + } + ss := new(s) + err := toml.Unmarshal([]byte(data), ss) + require.NoError(t, err) +} + func TestUnmarshalDecodeErrors(t *testing.T) { examples := []struct { desc string From 37714006b6168ee81ac9834e2b630f41e2705692 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Thu, 8 Apr 2021 10:07:29 -0400 Subject: [PATCH 159/228] V2 Marshaler MVP (#495) --- README.md | 7 +- .../imported_tests/marshal_imported_test.go | 166 +++++ marshaler.go | 639 ++++++++++++++++++ marshaler_test.go | 215 ++++++ parser.go | 261 ------- targets.go | 9 +- toml_testgen_support_test.go | 19 +- unmarshaler.go | 15 + 8 files changed, 1058 insertions(+), 273 deletions(-) create mode 100644 internal/imported_tests/marshal_imported_test.go create mode 100644 marshaler.go create mode 100644 marshaler_test.go diff --git a/README.md b/README.md index a3cccfcf..4083c815 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,12 @@ Development branch. Use at your own risk. ### Marshal -- [ ] Minimal implementation +- [x] Minimal implementation +- [ ] Multiline strings +- [ ] Multiline arrays +- [ ] `inline` tag for tables +- [ ] Optional indentation +- [ ] Option to pick default quotes ### Document diff --git a/internal/imported_tests/marshal_imported_test.go b/internal/imported_tests/marshal_imported_test.go new file mode 100644 index 00000000..98ad71fe --- /dev/null +++ b/internal/imported_tests/marshal_imported_test.go @@ -0,0 +1,166 @@ +package imported_tests + +// Those tests have been imported from v1, but adjust to match the new +// defaults of v2. + +import ( + "testing" + "time" + + "github.com/pelletier/go-toml/v2" + "github.com/stretchr/testify/require" +) + +func TestDocMarshal(t *testing.T) { + type testDoc struct { + Title string `toml:"title"` + BasicLists testDocBasicLists `toml:"basic_lists"` + SubDocPtrs []*testSubDoc `toml:"subdocptrs"` + BasicMap map[string]string `toml:"basic_map"` + Subdocs testDocSubs `toml:"subdoc"` + Basics testDocBasics `toml:"basic"` + SubDocList []testSubDoc `toml:"subdoclist"` + err int `toml:"shouldntBeHere"` + unexported int `toml:"shouldntBeHere"` + Unexported2 int `toml:"-"` + } + + var docData = testDoc{ + Title: "TOML Marshal Testing", + unexported: 0, + Unexported2: 0, + Basics: testDocBasics{ + Bool: true, + Date: time.Date(1979, 5, 27, 7, 32, 0, 0, time.UTC), + Float32: 123.4, + Float64: 123.456782132399, + Int: 5000, + Uint: 5001, + String: &biteMe, + unexported: 0, + }, + BasicLists: testDocBasicLists{ + Floats: []*float32{&float1, &float2, &float3}, + Bools: []bool{true, false, true}, + Dates: []time.Time{ + time.Date(1979, 5, 27, 7, 32, 0, 0, time.UTC), + time.Date(1980, 5, 27, 7, 32, 0, 0, time.UTC), + }, + Ints: []int{8001, 8001, 8002}, + Strings: []string{"One", "Two", "Three"}, + UInts: []uint{5002, 5003}, + }, + BasicMap: map[string]string{ + "one": "one", + "two": "two", + }, + Subdocs: testDocSubs{ + First: testSubDoc{"First", 0}, + Second: &subdoc, + }, + SubDocList: []testSubDoc{ + {"List.First", 0}, + {"List.Second", 0}, + }, + SubDocPtrs: []*testSubDoc{&subdoc}, + } + + marshalTestToml := `title = 'TOML Marshal Testing' +[basic_lists] +floats = [12.3, 45.6, 78.9] +bools = [true, false, true] +dates = [1979-05-27T07:32:00Z, 1980-05-27T07:32:00Z] +ints = [8001, 8001, 8002] +uints = [5002, 5003] +strings = ['One', 'Two', 'Three'] + +[[subdocptrs]] +name = 'Second' + +[basic_map] +one = 'one' +two = 'two' + +[subdoc] +[subdoc.second] +name = 'Second' + +[subdoc.first] +name = 'First' + + +[basic] +uint = 5001 +bool = true +float = 123.4 +float64 = 123.456782132399 +int = 5000 +string = 'Bite me' +date = 1979-05-27T07:32:00Z + +[[subdoclist]] +name = 'List.First' +[[subdoclist]] +name = 'List.Second' + +` + + result, err := toml.Marshal(docData) + require.NoError(t, err) + require.Equal(t, marshalTestToml, string(result)) +} + +func TestBasicMarshalQuotedKey(t *testing.T) { + result, err := toml.Marshal(quotedKeyMarshalTestData) + require.NoError(t, err) + + expected := `'Z.string-àéù' = 'Hello' +'Yfloat-𝟘' = 3.5 +['Xsubdoc-àéù'] +String2 = 'One' + +[['W.sublist-𝟘']] +String2 = 'Two' +[['W.sublist-𝟘']] +String2 = 'Three' + +` + + require.Equal(t, string(expected), string(result)) + +} + +func TestEmptyMarshal(t *testing.T) { + type emptyMarshalTestStruct struct { + Title string `toml:"title"` + Bool bool `toml:"bool"` + Int int `toml:"int"` + String string `toml:"string"` + StringList []string `toml:"stringlist"` + Ptr *basicMarshalTestStruct `toml:"ptr"` + Map map[string]string `toml:"map"` + } + + doc := emptyMarshalTestStruct{ + Title: "Placeholder", + Bool: false, + Int: 0, + String: "", + StringList: []string{}, + Ptr: nil, + Map: map[string]string{}, + } + result, err := toml.Marshal(doc) + require.NoError(t, err) + + expected := `title = 'Placeholder' +bool = false +int = 0 +string = '' +stringlist = [] +[map] + +` + + require.Equal(t, string(expected), string(result)) +} diff --git a/marshaler.go b/marshaler.go new file mode 100644 index 00000000..fa8f3415 --- /dev/null +++ b/marshaler.go @@ -0,0 +1,639 @@ +package toml + +import ( + "bytes" + "errors" + "fmt" + "io" + "reflect" + "sort" + "strconv" + "strings" + "time" + "unicode/utf8" +) + +// Marshal serializes a Go value as a TOML document. +// +// It is a shortcut for Encoder.Encode() with the default options. +func Marshal(v interface{}) ([]byte, error) { + var buf bytes.Buffer + enc := NewEncoder(&buf) + err := enc.Encode(v) + if err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +// Encoder writes a TOML document to an output stream. +type Encoder struct { + w io.Writer +} + +type encoderCtx struct { + // Current top-level key. + parentKey []string + + // Key that should be used for a KV. + key string + // Extra flag to account for the empty string + hasKey bool + + // Set to true to indicate that the encoder is inside a KV, so that all + // tables need to be inlined. + insideKv bool + + // Set to true to skip the first table header in an array table. + skipTableHeader bool +} + +func (ctx *encoderCtx) shiftKey() { + if ctx.hasKey { + ctx.parentKey = append(ctx.parentKey, ctx.key) + ctx.clearKey() + } +} + +func (ctx *encoderCtx) setKey(k string) { + ctx.key = k + ctx.hasKey = true +} + +func (ctx *encoderCtx) clearKey() { + ctx.key = "" + ctx.hasKey = false +} + +// NewEncoder returns a new Encoder that writes to w. +func NewEncoder(w io.Writer) *Encoder { + return &Encoder{ + w: w, + } +} + +// Encode writes a TOML representation of v to the stream. +// +// If v cannot be represented to TOML it returns an error. +// +// Encoding rules: +// +// 1. A top level slice containing only maps or structs is encoded as [[table +// array]]. +// +// 2. All slices not matching rule 1 are encoded as [array]. As a result, any +// map or struct they contain is encoded as an {inline table}. +// +// 3. Nil interfaces and nil pointers are not supported. +// +// 4. Keys in key-values always have one part. +// +// 5. Intermediate tables are always printed. +func (enc *Encoder) Encode(v interface{}) error { + var b []byte + var ctx encoderCtx + b, err := enc.encode(b, ctx, reflect.ValueOf(v)) + if err != nil { + return err + } + _, err = enc.w.Write(b) + return err +} + +func (enc *Encoder) encode(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) { + switch i := v.Interface().(type) { + case time.Time: // TODO: add TextMarshaler + b = i.AppendFormat(b, time.RFC3339) + return b, nil + } + + // containers + switch v.Kind() { + case reflect.Map: + return enc.encodeMap(b, ctx, v) + case reflect.Struct: + return enc.encodeStruct(b, ctx, v) + case reflect.Slice: + return enc.encodeSlice(b, ctx, v) + case reflect.Interface: + if v.IsNil() { + return nil, errNilInterface + } + return enc.encode(b, ctx, v.Elem()) + case reflect.Ptr: + if v.IsNil() { + return enc.encode(b, ctx, reflect.Zero(v.Type().Elem())) + } + return enc.encode(b, ctx, v.Elem()) + } + + // values + var err error + switch v.Kind() { + case reflect.String: + b, err = enc.encodeString(b, v.String()) + case reflect.Float32: + b = strconv.AppendFloat(b, v.Float(), 'f', -1, 32) + case reflect.Float64: + b = strconv.AppendFloat(b, v.Float(), 'f', -1, 64) + case reflect.Bool: + if v.Bool() { + b = append(b, "true"...) + } else { + b = append(b, "false"...) + } + case reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8, reflect.Uint: + b = strconv.AppendUint(b, v.Uint(), 10) + case reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8, reflect.Int: + b = strconv.AppendInt(b, v.Int(), 10) + default: + err = fmt.Errorf("unsupported encode value kind: %s", v.Kind()) + } + if err != nil { + return nil, err + } + + return b, nil +} + +func isNil(v reflect.Value) bool { + switch v.Kind() { + case reflect.Ptr, reflect.Interface, reflect.Map: + return v.IsNil() + default: + return false + } +} + +func (enc *Encoder) encodeKv(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) { + var err error + + if !ctx.hasKey { + panic("caller of encodeKv should have set the key in the context") + } + + if isNil(v) { + return b, nil + } + + b, err = enc.encodeKey(b, ctx.key) + if err != nil { + return nil, err + } + + b = append(b, " = "...) + + // create a copy of the context because the value of a KV shouldn't + // modify the global context. + subctx := ctx + subctx.insideKv = true + subctx.shiftKey() + + b, err = enc.encode(b, subctx, v) + if err != nil { + return nil, err + } + + return b, nil +} + +const literalQuote = '\'' + +func (enc *Encoder) encodeString(b []byte, v string) ([]byte, error) { + if needsQuoting(v) { + b = enc.encodeQuotedString(b, v) + } else { + b = enc.encodeLiteralString(b, v) + } + return b, nil +} + +func needsQuoting(v string) bool { + return strings.ContainsAny(v, "'\b\f\n\r\t") +} + +// caller should have checked that the string does not contain new lines or ' +func (enc *Encoder) encodeLiteralString(b []byte, v string) []byte { + b = append(b, literalQuote) + b = append(b, v...) + b = append(b, literalQuote) + return b +} + +func (enc *Encoder) encodeQuotedString(b []byte, v string) []byte { + const stringQuote = '"' + + b = append(b, stringQuote) + + for _, r := range v { + switch r { + case '\\': + b = append(b, `\\`...) + continue + case '"': + b = append(b, `\"`...) + continue + case '\b': + b = append(b, `\b`...) + continue + case '\f': + b = append(b, `\f`...) + continue + case '\n': + b = append(b, `\n`...) + continue + case '\r': + b = append(b, `\r`...) + continue + case '\t': + b = append(b, `\t`...) + continue + } + if r == 0x20 || r == 0x09 || r == 0x21 || (r >= 0x23 && r <= 0x5B) || (r >= 0x5D && r <= 0x7E) { + b = append(b, byte(r)) + } else if (r >= 0x80 && r <= 0xD7FF) || (r >= 0xE000 && r <= 0x10FFFF) { + l := utf8.RuneLen(r) + buf := make([]byte, l) + utf8.EncodeRune(buf, r) + b = append(b, buf...) + } else { + var h []byte + if r > 0xFFFF { + h = []byte(fmt.Sprintf("%08x", r)) + + } else { + h = []byte(fmt.Sprintf("%04x", r)) + } + b = append(b, `\u`...) + b = append(b, h...) + } + } + + b = append(b, stringQuote) + return b +} + +// called should have checked that the string is in A-Z / a-z / 0-9 / - / _ +func (enc *Encoder) encodeUnquotedKey(b []byte, v string) []byte { + return append(b, v...) +} + +func (enc *Encoder) encodeTableHeader(b []byte, key []string) ([]byte, error) { + if len(key) == 0 { + return b, nil + } + + b = append(b, '[') + + var err error + b, err = enc.encodeKey(b, key[0]) + if err != nil { + return nil, err + } + + for _, k := range key[1:] { + b = append(b, '.') + b, err = enc.encodeKey(b, k) + if err != nil { + return nil, err + } + } + + b = append(b, "]\n"...) + + return b, nil +} + +func (enc *Encoder) encodeKey(b []byte, k string) ([]byte, error) { + needsQuotation := false + cannotUseLiteral := false + + for _, c := range k { + if (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '_' { + continue + } + if c == '\n' { + return nil, fmt.Errorf("TOML does not support multiline keys") + } + if c == literalQuote { + cannotUseLiteral = true + } + needsQuotation = true + } + + if cannotUseLiteral { + b = enc.encodeQuotedString(b, k) + } else if needsQuotation { + b = enc.encodeLiteralString(b, k) + } else { + b = enc.encodeUnquotedKey(b, k) + } + + return b, nil +} + +func (enc *Encoder) encodeMap(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) { + if v.Type().Key().Kind() != reflect.String { + return nil, fmt.Errorf("type '%s' not supported as map key", v.Type().Key().Kind()) + } + + t := table{} + + iter := v.MapRange() + for iter.Next() { + k := iter.Key().String() + v := iter.Value() + + if isNil(v) { + continue + } + + table, err := willConvertToTableOrArrayTable(v) + if err != nil { + return nil, err + } + + if table { + t.pushTable(k, v) + } else { + t.pushKV(k, v) + } + } + + sortEntriesByKey(t.kvs) + sortEntriesByKey(t.tables) + + return enc.encodeTable(b, ctx, t) +} + +func sortEntriesByKey(e []entry) { + sort.Slice(e, func(i, j int) bool { + return e[i].Key < e[j].Key + }) +} + +type entry struct { + Key string + Value reflect.Value +} + +type table struct { + kvs []entry + tables []entry +} + +func (t *table) pushKV(k string, v reflect.Value) { + t.kvs = append(t.kvs, entry{Key: k, Value: v}) +} + +func (t *table) pushTable(k string, v reflect.Value) { + t.tables = append(t.tables, entry{Key: k, Value: v}) +} + +func (t *table) hasKVs() bool { + return len(t.kvs) > 0 +} + +func (enc *Encoder) encodeStruct(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) { + t := table{} + + // TODO: cache this? + typ := v.Type() + for i := 0; i < typ.NumField(); i++ { + fieldType := typ.Field(i) + + // only consider exported fields + if fieldType.PkgPath != "" { + continue + } + + k, ok := fieldType.Tag.Lookup("toml") + if !ok { + k = fieldType.Name + } + + // special field name to skip field + if k == "-" { + continue + } + + f := v.Field(i) + + if isNil(f) { + continue + } + + willConvert, err := willConvertToTableOrArrayTable(f) + if err != nil { + return nil, err + } + + if willConvert { + t.pushTable(k, f) + } else { + t.pushKV(k, f) + } + } + + return enc.encodeTable(b, ctx, t) +} + +func (enc *Encoder) encodeTable(b []byte, ctx encoderCtx, t table) ([]byte, error) { + var err error + + ctx.shiftKey() + + if ctx.insideKv { + b = append(b, '{') + + first := true + for _, kv := range t.kvs { + if first { + first = false + } else { + b = append(b, `, `...) + } + ctx.setKey(kv.Key) + b, err = enc.encodeKv(b, ctx, kv.Value) + if err != nil { + return nil, err + } + } + + for _, table := range t.tables { + if first { + first = false + } else { + b = append(b, `, `...) + } + ctx.setKey(table.Key) + b, err = enc.encode(b, ctx, table.Value) + if err != nil { + return nil, err + } + b = append(b, '\n') + } + + b = append(b, "}\n"...) + return b, nil + } + + if !ctx.skipTableHeader { + b, err = enc.encodeTableHeader(b, ctx.parentKey) + if err != nil { + return nil, err + } + } + ctx.skipTableHeader = false + + for _, kv := range t.kvs { + ctx.setKey(kv.Key) + b, err = enc.encodeKv(b, ctx, kv.Value) + if err != nil { + return nil, err + } + b = append(b, '\n') + } + + for _, table := range t.tables { + ctx.setKey(table.Key) + b, err = enc.encode(b, ctx, table.Value) + if err != nil { + return nil, err + } + b = append(b, '\n') + } + + return b, nil +} + +var errNilInterface = errors.New("nil interface not supported") +var errNilPointer = errors.New("nil pointer not supported") + +func willConvertToTable(v reflect.Value) (bool, error) { + switch v.Interface().(type) { + case time.Time: // TODO: add TextMarshaler + return false, nil + } + + t := v.Type() + switch t.Kind() { + case reflect.Map, reflect.Struct: + return true, nil + case reflect.Interface: + if v.IsNil() { + return false, errNilInterface + } + return willConvertToTable(v.Elem()) + case reflect.Ptr: + if v.IsNil() { + return false, nil + } + return willConvertToTable(v.Elem()) + default: + return false, nil + } +} + +func willConvertToTableOrArrayTable(v reflect.Value) (bool, error) { + t := v.Type() + + if t.Kind() == reflect.Interface { + if v.IsNil() { + return false, errNilInterface + } + return willConvertToTableOrArrayTable(v.Elem()) + } + + if t.Kind() == reflect.Slice { + if v.Len() == 0 { + // An empty slice should be a kv = []. + return false, nil + } + for i := 0; i < v.Len(); i++ { + t, err := willConvertToTable(v.Index(i)) + if err != nil { + return false, err + } + if !t { + return false, nil + } + } + return true, nil + } + + return willConvertToTable(v) +} + +func (enc *Encoder) encodeSlice(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) { + if v.Len() == 0 { + b = append(b, "[]"...) + return b, nil + } + + allTables, err := willConvertToTableOrArrayTable(v) + if err != nil { + return nil, err + } + + if allTables { + return enc.encodeSliceAsArrayTable(b, ctx, v) + } + + return enc.encodeSliceAsArray(b, ctx, v) +} + +// caller should have checked that v is a slice that only contains values that +// encode into tables. +func (enc *Encoder) encodeSliceAsArrayTable(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) { + if v.Len() == 0 { + return b, nil + } + + ctx.shiftKey() + + var err error + scratch := make([]byte, 0, 64) + scratch = append(scratch, "[["...) + for i, k := range ctx.parentKey { + if i > 0 { + scratch = append(scratch, '.') + } + scratch, err = enc.encodeKey(scratch, k) + if err != nil { + return nil, err + } + } + scratch = append(scratch, "]]\n"...) + ctx.skipTableHeader = true + + for i := 0; i < v.Len(); i++ { + b = append(b, scratch...) + b, err = enc.encode(b, ctx, v.Index(i)) + if err != nil { + return nil, err + } + } + return b, nil +} + +func (enc *Encoder) encodeSliceAsArray(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) { + b = append(b, '[') + + var err error + first := true + for i := 0; i < v.Len(); i++ { + if !first { + b = append(b, ", "...) + } + first = false + + b, err = enc.encode(b, ctx, v.Index(i)) + if err != nil { + return nil, err + } + } + + b = append(b, ']') + return b, nil +} diff --git a/marshaler_test.go b/marshaler_test.go new file mode 100644 index 00000000..0ec50667 --- /dev/null +++ b/marshaler_test.go @@ -0,0 +1,215 @@ +package toml_test + +import ( + "bytes" + "encoding/json" + "strings" + "testing" + + "github.com/pelletier/go-toml/v2" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestMarshal(t *testing.T) { + examples := []struct { + desc string + v interface{} + expected string + err bool + }{ + { + desc: "simple map and string", + v: map[string]string{ + "hello": "world", + }, + expected: "hello = 'world'", + }, + { + desc: "map with new line in key", + v: map[string]string{ + "hel\nlo": "world", + }, + err: true, + }, + { + desc: `map with " in key`, + v: map[string]string{ + `hel"lo`: "world", + }, + expected: `'hel"lo' = 'world'`, + }, + { + desc: "map in map and string", + v: map[string]map[string]string{ + "table": { + "hello": "world", + }, + }, + expected: ` +[table] +hello = 'world'`, + }, + { + desc: "map in map in map and string", + v: map[string]map[string]map[string]string{ + "this": { + "is": { + "a": "test", + }, + }, + }, + expected: ` +[this] +[this.is] +a = 'test'`, + }, + { + // TODO: this test is flaky because output changes depending on + // the map iteration order. + desc: "map in map in map and string with values", + v: map[string]interface{}{ + "this": map[string]interface{}{ + "is": map[string]string{ + "a": "test", + }, + "also": "that", + }, + }, + expected: ` +[this] +also = 'that' +[this.is] +a = 'test'`, + }, + { + desc: "simple string array", + v: map[string][]string{ + "array": {"one", "two", "three"}, + }, + expected: `array = ['one', 'two', 'three']`, + }, + { + desc: "nested string arrays", + v: map[string][][]string{ + "array": {{"one", "two"}, {"three"}}, + }, + expected: `array = [['one', 'two'], ['three']]`, + }, + { + desc: "mixed strings and nested string arrays", + v: map[string][]interface{}{ + "array": {"a string", []string{"one", "two"}, "last"}, + }, + expected: `array = ['a string', ['one', 'two'], 'last']`, + }, + { + desc: "slice of maps", + v: map[string][]map[string]string{ + "top": { + {"map1.1": "v1.1"}, + {"map2.1": "v2.1"}, + }, + }, + expected: ` +[[top]] +'map1.1' = 'v1.1' +[[top]] +'map2.1' = 'v2.1' +`, + }, + { + desc: "map with two keys", + v: map[string]string{ + "key1": "value1", + "key2": "value2", + }, + expected: ` +key1 = 'value1' +key2 = 'value2'`, + }, + { + desc: "simple struct", + v: struct { + A string + }{ + A: "foo", + }, + expected: `A = 'foo'`, + }, + { + desc: "one level of structs within structs", + v: struct { + A interface{} + }{ + A: struct { + K1 string + K2 string + }{ + K1: "v1", + K2: "v2", + }, + }, + expected: ` +[A] +K1 = 'v1' +K2 = 'v2' +`, + }, + { + desc: "structs in slice with interfaces", + v: map[string]interface{}{ + "root": map[string]interface{}{ + "nested": []interface{}{ + map[string]interface{}{"name": "Bob"}, + map[string]interface{}{"name": "Alice"}, + }, + }, + }, + expected: ` +[root] +[[root.nested]] +name = 'Bob' +[[root.nested]] +name = 'Alice' +`, + }, + } + + for _, e := range examples { + t.Run(e.desc, func(t *testing.T) { + b, err := toml.Marshal(e.v) + if e.err { + require.Error(t, err) + } else { + require.NoError(t, err) + equalStringsIgnoreNewlines(t, e.expected, string(b)) + } + }) + } +} + +func equalStringsIgnoreNewlines(t *testing.T, expected string, actual string) { + t.Helper() + cutset := "\n" + assert.Equal(t, strings.Trim(expected, cutset), strings.Trim(actual, cutset)) +} + +func TestIssue436(t *testing.T) { + data := []byte(`{"a": [ { "b": { "c": "d" } } ]}`) + + var v interface{} + err := json.Unmarshal(data, &v) + require.NoError(t, err) + + var buf bytes.Buffer + err = toml.NewEncoder(&buf).Encode(v) + require.NoError(t, err) + + expected := ` +[[a]] +[a.b] +c = 'd' +` + equalStringsIgnoreNewlines(t, expected, buf.String()) +} diff --git a/parser.go b/parser.go index bb9a37e5..89fadf6a 100644 --- a/parser.go +++ b/parser.go @@ -4,7 +4,6 @@ import ( "bytes" "fmt" "strconv" - "time" "github.com/pelletier/go-toml/v2/internal/ast" ) @@ -793,266 +792,6 @@ func (p *parser) scanDateTime(b []byte) (ast.Reference, []byte, error) { }), b[i:], nil } -func (p *parser) parseDateTime(b []byte) ([]byte, error) { - // we know the first 2 are digits. - if b[2] == ':' { - return p.parseTime(b) - } - // This state accepts an offset date-time, a local date-time, or a local date. - // - // 1979-05-27T07:32:00Z - // 1979-05-27T00:32:00-07:00 - // 1979-05-27T00:32:00.999999-07:00 - // 1979-05-27 07:32:00Z - // 1979-05-27 00:32:00-07:00 - // 1979-05-27 00:32:00.999999-07:00 - // 1979-05-27T07:32:00 - // 1979-05-27T00:32:00.999999 - // 1979-05-27 07:32:00 - // 1979-05-27 00:32:00.999999 - // 1979-05-27 - - // date - - idx := 4 - - localDate := LocalDate{ - Year: digitsToInt(b[:idx]), - } - - for i := 0; i < 2; i++ { - // month - idx++ - if !isDigit(b[idx]) { - return nil, fmt.Errorf("invalid month digit in date: %c", b[idx]) - } - localDate.Month *= 10 - localDate.Month += time.Month(b[idx] - '0') - } - - idx++ - if b[idx] != '-' { - return nil, fmt.Errorf("expected - to separate month of a date, not %c", b[idx]) - } - - for i := 0; i < 2; i++ { - // day - idx++ - if !isDigit(b[idx]) { - return nil, fmt.Errorf("invalid day digit in date: %c", b[idx]) - } - localDate.Day *= 10 - localDate.Day += int(b[idx] - '0') - } - - idx++ - - if idx >= len(b) { - //p.builder.LocalDateValue(localDate) - // TODO - return nil, nil - } else if b[idx] != ' ' && b[idx] != 'T' { - //p.builder.LocalDateValue(localDate) - // TODO - return b[idx:], nil - } - - // check if there is a chance there is anything useful after - if b[idx] == ' ' && (((idx + 2) >= len(b)) || !isDigit(b[idx+1]) || !isDigit(b[idx+2])) { - //p.builder.LocalDateValue(localDate) - // TODO - return b[idx:], nil - } - - //idx++ // skip the T or ' ' - - // time - localTime := LocalTime{} - - for i := 0; i < 2; i++ { - idx++ - if !isDigit(b[idx]) { - return nil, fmt.Errorf("invalid hour digit in time: %c", b[idx]) - } - localTime.Hour *= 10 - localTime.Hour += int(b[idx] - '0') - } - - idx++ - if b[idx] != ':' { - return nil, fmt.Errorf("time hour/minute separator should be :, not %c", b[idx]) - } - - for i := 0; i < 2; i++ { - idx++ - if !isDigit(b[idx]) { - return nil, fmt.Errorf("invalid minute digit in time: %c", b[idx]) - } - localTime.Minute *= 10 - localTime.Minute += int(b[idx] - '0') - } - - idx++ - if b[idx] != ':' { - return nil, fmt.Errorf("time minute/second separator should be :, not %c", b[idx]) - } - - for i := 0; i < 2; i++ { - idx++ - if !isDigit(b[idx]) { - return nil, fmt.Errorf("invalid second digit in time: %c", b[idx]) - } - localTime.Second *= 10 - localTime.Second += int(b[idx] - '0') - } - - idx++ - if idx < len(b) && b[idx] == '.' { - idx++ - idx++ - if !isDigit(b[idx]) { - return nil, fmt.Errorf("expected at least one digit in time's fraction, not %c", b[idx]) - } - - for { - localTime.Nanosecond *= 10 - localTime.Nanosecond += int(b[idx] - '0') - idx++ - - if idx < len(b) { - break - } - - if !isDigit(b[idx]) { - break - } - } - } - - if idx >= len(b) || (b[idx] != 'Z' && b[idx] != '+' && b[idx] != '-') { - dt := LocalDateTime{ - Date: localDate, - Time: localTime, - } - //p.builder.LocalDateTimeValue(dt) - // TODO - dt = dt - return b[idx:], nil - } - - loc := time.UTC - - if b[idx] == 'Z' { - idx++ - } else { - start := idx - sign := 1 - if b[idx] == '-' { - sign = -1 - } - - hours := 0 - for i := 0; i < 2; i++ { - idx++ - if !isDigit(b[idx]) { - return nil, fmt.Errorf("invalid hour digit in time offset: %c", b[idx]) - } - hours *= 10 - hours += int(b[idx] - '0') - } - offset := hours * 60 * 60 - - idx++ - if b[idx] != ':' { - return nil, fmt.Errorf("time offset hour/minute separator should be :, not %c", b[idx]) - } - - minutes := 0 - for i := 0; i < 2; i++ { - idx++ - if !isDigit(b[idx]) { - return nil, fmt.Errorf("invalid minute digit in time offset: %c", b[idx]) - } - minutes *= 10 - minutes += int(b[idx] - '0') - } - offset += minutes * 60 - offset *= sign - idx++ - loc = time.FixedZone(string(b[start:idx]), offset) - } - dt := time.Date(localDate.Year, localDate.Month, localDate.Day, localTime.Hour, localTime.Minute, localTime.Second, localTime.Nanosecond, loc) - //p.builder.DateTimeValue(dt) - // TODO - dt = dt - return b[idx:], nil -} - -func (p *parser) parseTime(b []byte) ([]byte, error) { - localTime := LocalTime{} - - idx := 0 - - for i := 0; i < 2; i++ { - idx++ - if !isDigit(b[idx]) { - return nil, fmt.Errorf("invalid hour digit in time: %c", b[idx]) - } - localTime.Hour *= 10 - localTime.Hour += int(b[idx] - '0') - } - - idx++ - if b[idx] != ':' { - return nil, fmt.Errorf("time hour/minute separator should be :, not %c", b[idx]) - } - - for i := 0; i < 2; i++ { - idx++ - if !isDigit(b[idx]) { - return nil, fmt.Errorf("invalid minute digit in time: %c", b[idx]) - } - localTime.Minute *= 10 - localTime.Minute += int(b[idx] - '0') - } - - idx++ - if b[idx] != ':' { - return nil, fmt.Errorf("time minute/second separator should be :, not %c", b[idx]) - } - - for i := 0; i < 2; i++ { - idx++ - if !isDigit(b[idx]) { - return nil, fmt.Errorf("invalid second digit in time: %c", b[idx]) - } - localTime.Second *= 10 - localTime.Second += int(b[idx] - '0') - } - - idx++ - if idx < len(b) && b[idx] == '.' { - idx++ - idx++ - if !isDigit(b[idx]) { - return nil, fmt.Errorf("expected at least one digit in time's fraction, not %c", b[idx]) - } - - for { - localTime.Nanosecond *= 10 - localTime.Nanosecond += int(b[idx] - '0') - idx++ - if !isDigit(b[idx]) { - break - } - } - } - - //p.builder.LocalTimeValue(localTime) - // TODO - return b[idx:], nil -} - func (p *parser) scanIntOrFloat(b []byte) (ast.Reference, []byte, error) { i := 0 diff --git a/targets.go b/targets.go index d229020f..a1e922f1 100644 --- a/targets.go +++ b/targets.go @@ -356,6 +356,11 @@ func (d *decoder) scopeTableTarget(append bool, t target, name string) (target, case reflect.Struct: return scopeStruct(x, name) case reflect.Map: + if x.IsNil() { + t.set(reflect.MakeMap(x.Type())) + x = t.get() + } + return scopeMap(x, name) default: panic(fmt.Errorf("can't scope on a %s", x.Kind())) @@ -442,10 +447,6 @@ func (d *decoder) scopeArray(append bool, t target) (target, error) { } func scopeMap(v reflect.Value, name string) (target, bool, error) { - if v.IsNil() { - v.Set(reflect.MakeMap(v.Type())) - } - k := reflect.ValueOf(name) keyType := v.Type().Key() diff --git a/toml_testgen_support_test.go b/toml_testgen_support_test.go index 5d4b0a03..5edd25b5 100644 --- a/toml_testgen_support_test.go +++ b/toml_testgen_support_test.go @@ -38,6 +38,15 @@ func testgenValid(t *testing.T, input string, jsonRef string) { refDoc := testgenBuildRefDoc(jsonRef) require.Equal(t, refDoc, doc) + + out, err := toml.Marshal(doc) + require.NoError(t, err) + + doc2 := map[string]interface{}{} + err = toml.Unmarshal(out, &doc2) + require.NoError(t, err) + + require.Equal(t, refDoc, doc2) } type testGenDescNode struct { @@ -121,13 +130,9 @@ func testGenTranslateDesc(input interface{}) interface{} { } } - var dest interface{} - if len(d) > 0 { - x := map[string]interface{}{} - for k, v := range d { - x[k] = testGenTranslateDesc(v) - } - dest = x + dest := map[string]interface{}{} + for k, v := range d { + dest[k] = testGenTranslateDesc(v) } return dest } diff --git a/unmarshaler.go b/unmarshaler.go index 1184e63e..074d8ef2 100644 --- a/unmarshaler.go +++ b/unmarshaler.go @@ -33,6 +33,9 @@ func NewDecoder(r io.Reader) *Decoder { // // When a TOML local date is decoded into a time.Time, its value is represented // in time.Local timezone. +// +// Empty tables decoded in an interface{} create an empty initialized +// map[string]interface{}. func (d *Decoder) Decode(v interface{}) error { b, err := ioutil.ReadAll(d.r) if err != nil { @@ -111,6 +114,18 @@ func (d *decoder) fromParser(p *parser, v interface{}) error { found = true case ast.Table: current, found, err = d.scopeWithKey(root, node.Key()) + if err == nil { + // In case this table points to an interface, + // make sure it at least holds something that + // looks like a table. Otherwise the information + // of a table is lost, and marshal cannot do the + // round trip. + v := current.get() + if v.Kind() == reflect.Interface && v.IsNil() { + newElement := reflect.MakeMap(mapStringInterfaceType) + current.set(newElement) + } + } case ast.ArrayTable: current, found, err = d.scopeWithArrayTable(root, node.Key()) default: From f2378983d915155440efd2ce5168df779ff48c2b Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Thu, 8 Apr 2021 10:24:38 -0400 Subject: [PATCH 160/228] encoder: added test for #287 --- unmarshaler_test.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/unmarshaler_test.go b/unmarshaler_test.go index 2aa5daf9..ed472bb5 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -885,3 +885,19 @@ world'`, }) } } + +func TestIssue287(t *testing.T) { + b := `y=[[{}]]` + v := map[string]interface{}{} + err := toml.Unmarshal([]byte(b), &v) + require.NoError(t, err) + + expected := map[string]interface{}{ + "y": []interface{}{ + []interface{}{ + nil, + }, + }, + } + require.Equal(t, expected, v) +} From ca41df4a5979f6463b0b283ee1fde90dbebd9d2a Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Thu, 8 Apr 2021 19:40:34 -0400 Subject: [PATCH 161/228] encoder: only create empty map when target exists --- unmarshaler.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unmarshaler.go b/unmarshaler.go index 074d8ef2..b7738316 100644 --- a/unmarshaler.go +++ b/unmarshaler.go @@ -114,7 +114,7 @@ func (d *decoder) fromParser(p *parser, v interface{}) error { found = true case ast.Table: current, found, err = d.scopeWithKey(root, node.Key()) - if err == nil { + if err == nil && found { // In case this table points to an interface, // make sure it at least holds something that // looks like a table. Otherwise the information From 84f9e9bceb3777b4dd0c12ee8c6efb520c24628a Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Thu, 8 Apr 2021 19:43:14 -0400 Subject: [PATCH 162/228] ci: run benchmark tests --- .github/workflows/workflow.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 85504dac..d66e6a3d 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -21,4 +21,8 @@ jobs: uses: actions/setup-go@master with: go-version: ${{ matrix.go }} - - run: go test -race ./... + - name: Run unit tests + run: go test -race ./... + - name: Run benchmark tests + run: go test -race ./... + working-directory: benchmark From e1f035461bc55d0971a0c3ba9ddf10b28e38d707 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Thu, 8 Apr 2021 22:02:41 -0400 Subject: [PATCH 163/228] encoder: simplify quoted strings escaping --- marshaler.go | 37 +++++++++++-------------------------- marshaler_test.go | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 26 deletions(-) diff --git a/marshaler.go b/marshaler.go index fa8f3415..3dbe2c1f 100644 --- a/marshaler.go +++ b/marshaler.go @@ -10,7 +10,6 @@ import ( "strconv" "strings" "time" - "unicode/utf8" ) // Marshal serializes a Go value as a TOML document. @@ -222,51 +221,37 @@ func (enc *Encoder) encodeLiteralString(b []byte, v string) []byte { func (enc *Encoder) encodeQuotedString(b []byte, v string) []byte { const stringQuote = '"' + const hextable = "0123456789ABCDEF" b = append(b, stringQuote) - for _, r := range v { + for _, r := range []byte(v) { switch r { case '\\': b = append(b, `\\`...) - continue case '"': b = append(b, `\"`...) - continue case '\b': b = append(b, `\b`...) - continue case '\f': b = append(b, `\f`...) - continue case '\n': b = append(b, `\n`...) - continue case '\r': b = append(b, `\r`...) - continue case '\t': b = append(b, `\t`...) - continue - } - if r == 0x20 || r == 0x09 || r == 0x21 || (r >= 0x23 && r <= 0x5B) || (r >= 0x5D && r <= 0x7E) { - b = append(b, byte(r)) - } else if (r >= 0x80 && r <= 0xD7FF) || (r >= 0xE000 && r <= 0x10FFFF) { - l := utf8.RuneLen(r) - buf := make([]byte, l) - utf8.EncodeRune(buf, r) - b = append(b, buf...) - } else { - var h []byte - if r > 0xFFFF { - h = []byte(fmt.Sprintf("%08x", r)) - - } else { - h = []byte(fmt.Sprintf("%04x", r)) + default: + switch { + case r >= 0x0 && r <= 0x8, r >= 0xA && r <= 0x1F, r == 0x7F: + b = append(b, `\u00`...) + b = append(b, hextable[r>>4]) + b = append(b, hextable[r&0x0f]) + default: + b = append(b, r) } - b = append(b, `\u`...) - b = append(b, h...) } + // U+0000 to U+0008, U+000A to U+001F, U+007F } b = append(b, stringQuote) diff --git a/marshaler_test.go b/marshaler_test.go index 0ec50667..ce71954b 100644 --- a/marshaler_test.go +++ b/marshaler_test.go @@ -174,6 +174,48 @@ name = 'Bob' name = 'Alice' `, }, + { + desc: "string escapes", + v: map[string]interface{}{ + "a": `'"\`, + }, + expected: `a = "'\"\\"`, + }, + { + desc: "string utf8 low", + v: map[string]interface{}{ + "a": "'Ę", + }, + expected: `a = "'Ę"`, + }, + { + desc: "string utf8 low 2", + v: map[string]interface{}{ + "a": "'\u10A85", + }, + expected: "a = \"'\u10A85\"", + }, + { + desc: "string utf8 low 2", + v: map[string]interface{}{ + "a": "'\u10A85", + }, + expected: "a = \"'\u10A85\"", + }, + { + desc: "emoji", + v: map[string]interface{}{ + "a": "'😀", + }, + expected: "a = \"'😀\"", + }, + { + desc: "control char", + v: map[string]interface{}{ + "a": "'\u001A", + }, + expected: `a = "'\u001A"`, + }, } for _, e := range examples { From 466bfe8664108313c52c9f9e50a671ad18f8b8e2 Mon Sep 17 00:00:00 2001 From: Cameron Moore Date: Fri, 9 Apr 2021 08:02:00 -0500 Subject: [PATCH 164/228] encoder: inline tables create map in nil interface (#496) Co-authored-by: Thomas Pelletier --- targets.go | 8 ++++++++ unmarshaler.go | 8 +++----- unmarshaler_test.go | 25 ++++++++++++++++++++++++- 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/targets.go b/targets.go index a1e922f1..0024264b 100644 --- a/targets.go +++ b/targets.go @@ -159,6 +159,14 @@ func ensureValueIndexable(t target) error { var sliceInterfaceType = reflect.TypeOf([]interface{}{}) var mapStringInterfaceType = reflect.TypeOf(map[string]interface{}{}) +func ensureMapIfInterface(x target) { + v := x.get() + if v.Kind() == reflect.Interface && v.IsNil() { + newElement := reflect.MakeMap(mapStringInterfaceType) + x.set(newElement) + } +} + func setString(t target, v string) error { f := t.get() diff --git a/unmarshaler.go b/unmarshaler.go index b7738316..885717b4 100644 --- a/unmarshaler.go +++ b/unmarshaler.go @@ -120,11 +120,7 @@ func (d *decoder) fromParser(p *parser, v interface{}) error { // looks like a table. Otherwise the information // of a table is lost, and marshal cannot do the // round trip. - v := current.get() - if v.Kind() == reflect.Interface && v.IsNil() { - newElement := reflect.MakeMap(mapStringInterfaceType) - current.set(newElement) - } + ensureMapIfInterface(current) } case ast.ArrayTable: current, found, err = d.scopeWithArrayTable(root, node.Key()) @@ -381,6 +377,8 @@ func unmarshalFloat(x target, node ast.Node) error { func (d *decoder) unmarshalInlineTable(x target, node ast.Node) error { assertNode(ast.InlineTable, node) + ensureMapIfInterface(x) + it := node.Children() for it.Next() { n := it.Node() diff --git a/unmarshaler_test.go b/unmarshaler_test.go index ed472bb5..89e57e29 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -296,6 +296,17 @@ B = "data"`, } }, }, + { + desc: "standard empty table", + input: `[A]`, + gen: func() test { + var v map[string]interface{} + return test{ + target: &v, + expected: &map[string]interface{}{`A`: map[string]interface{}{}}, + } + }, + }, { desc: "inline table", input: `Name = {First = "hello", Last = "world"}`, @@ -316,6 +327,17 @@ B = "data"`, } }, }, + { + desc: "inline empty table", + input: `A = {}`, + gen: func() test { + var v map[string]interface{} + return test{ + target: &v, + expected: &map[string]interface{}{`A`: map[string]interface{}{}}, + } + }, + }, { desc: "inline table inside array", input: `Names = [{First = "hello", Last = "world"}, {First = "ab", Last = "cd"}]`, @@ -713,6 +735,7 @@ type Integer484 struct { func (i Integer484) MarshalText() ([]byte, error) { return []byte(strconv.Itoa(i.Value)), nil } + func (i *Integer484) UnmarshalText(data []byte) error { conv, err := strconv.Atoi(string(data)) if err != nil { @@ -895,7 +918,7 @@ func TestIssue287(t *testing.T) { expected := map[string]interface{}{ "y": []interface{}{ []interface{}{ - nil, + map[string]interface{}{}, }, }, } From ed1f9ed9de3fcf49fdd027730e6c5f345f8008b9 Mon Sep 17 00:00:00 2001 From: Cameron Moore Date: Fri, 9 Apr 2021 10:27:22 -0500 Subject: [PATCH 165/228] Add sanity check tests to benchmark dataset (#497) Marshal results into JSON and ensure all runners match --- benchmark/bench_datasets_test.go | 52 ++++++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/benchmark/bench_datasets_test.go b/benchmark/bench_datasets_test.go index 34c892f7..7c0165aa 100644 --- a/benchmark/bench_datasets_test.go +++ b/benchmark/bench_datasets_test.go @@ -2,35 +2,61 @@ package benchmark_test import ( "compress/gzip" + "encoding/json" "io/ioutil" "os" "path/filepath" "testing" + + "github.com/stretchr/testify/require" ) -var bench_inputs = []string{ +var bench_inputs = []struct { + name string + jsonLen int +}{ // from https://gist.githubusercontent.com/feeeper/2197d6d734729625a037af1df14cf2aa/raw/2f22b120e476d897179be3c1e2483d18067aa7df/config.toml - "config", + {"config", 806507}, // converted from https://github.com/miloyip/nativejson-benchmark - "canada", - "citm_catalog", - "twitter", - "code", + {"canada", 2090234}, + {"citm_catalog", 479897}, + {"twitter", 428778}, + {"code", 1940472}, // converted from https://raw.githubusercontent.com/mailru/easyjson/master/benchmark/example.json - "example", + {"example", 7779}, +} + +func TestUnmarshalDatasetCode(t *testing.T) { + for _, tc := range bench_inputs { + buf := fixture(t, tc.name) + t.Run(tc.name, func(t *testing.T) { + for _, r := range runners { + if r.name == "bs" && tc.name == "canada" { + t.Skip("skipping: burntsushi can't handle mixed arrays") + } + + t.Run(r.name, func(t *testing.T) { + var v interface{} + check(t, r.unmarshal(buf, &v)) + + b, err := json.Marshal(v) + check(t, err) + require.Equal(t, len(b), tc.jsonLen) + }) + } + }) + } } func BenchmarkUnmarshalDataset(b *testing.B) { for _, tc := range bench_inputs { - buf := fixture(b, tc) - b.Run(tc, func(b *testing.B) { + buf := fixture(b, tc.name) + b.Run(tc.name, func(b *testing.B) { bench(b, func(r runner, b *testing.B) { - if r.name == "bs" && tc == "canada" { - // bs can't handle the canada dataset due to mixed integer & - // floats values in an array. - b.Skip() + if r.name == "bs" && tc.name == "canada" { + b.Skip("skipping: burntsushi can't handle mixed arrays") } b.SetBytes(int64(len(buf))) From 9e122af5fcbe987bdb6957d9e29e7e30c85699f3 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Sat, 10 Apr 2021 17:58:37 -0400 Subject: [PATCH 166/228] encoder: support multiline strings + local options --- README.md | 2 +- marshaler.go | 81 ++++++++++++++++++++++++++++++++++------------- marshaler_test.go | 18 +++++++++++ 3 files changed, 78 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 4083c815..1a2e6bba 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ Development branch. Use at your own risk. ### Marshal - [x] Minimal implementation -- [ ] Multiline strings +- [x] Multiline strings - [ ] Multiline arrays - [ ] `inline` tag for tables - [ ] Optional indentation diff --git a/marshaler.go b/marshaler.go index 3dbe2c1f..9019ec0b 100644 --- a/marshaler.go +++ b/marshaler.go @@ -45,6 +45,12 @@ type encoderCtx struct { // Set to true to skip the first table header in an array table. skipTableHeader bool + + options valueOptions +} + +type valueOptions struct { + multiline bool } func (ctx *encoderCtx) shiftKey() { @@ -88,6 +94,18 @@ func NewEncoder(w io.Writer) *Encoder { // 4. Keys in key-values always have one part. // // 5. Intermediate tables are always printed. +// +// By default, strings are encoded as literal string, unless they contain either +// a newline character or a single quote. In that case they are emited as quoted +// strings. +// +// When encoding structs, fields are encoded in order of definition, with their +// exact name. The following struct tags are available: +// +// `toml:"foo"`: changes the name of the key to use for the field to foo. +// +// `multiline:"true"`: when the field contains a string, it will be emitted as +// a quoted multi-line TOML string. func (enc *Encoder) Encode(v interface{}) error { var b []byte var ctx encoderCtx @@ -130,7 +148,7 @@ func (enc *Encoder) encode(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, e var err error switch v.Kind() { case reflect.String: - b, err = enc.encodeString(b, v.String()) + b, err = enc.encodeString(b, v.String(), ctx.options) case reflect.Float32: b = strconv.AppendFloat(b, v.Float(), 'f', -1, 32) case reflect.Float64: @@ -164,7 +182,7 @@ func isNil(v reflect.Value) bool { } } -func (enc *Encoder) encodeKv(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) { +func (enc *Encoder) encodeKv(b []byte, ctx encoderCtx, options valueOptions, v reflect.Value) ([]byte, error) { var err error if !ctx.hasKey { @@ -187,6 +205,7 @@ func (enc *Encoder) encodeKv(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, subctx := ctx subctx.insideKv = true subctx.shiftKey() + subctx.options = options b, err = enc.encode(b, subctx, v) if err != nil { @@ -198,9 +217,9 @@ func (enc *Encoder) encodeKv(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, const literalQuote = '\'' -func (enc *Encoder) encodeString(b []byte, v string) ([]byte, error) { +func (enc *Encoder) encodeString(b []byte, v string, options valueOptions) ([]byte, error) { if needsQuoting(v) { - b = enc.encodeQuotedString(b, v) + b = enc.encodeQuotedString(options.multiline, b, v) } else { b = enc.encodeLiteralString(b, v) } @@ -219,11 +238,17 @@ func (enc *Encoder) encodeLiteralString(b []byte, v string) []byte { return b } -func (enc *Encoder) encodeQuotedString(b []byte, v string) []byte { - const stringQuote = '"' +func (enc *Encoder) encodeQuotedString(multiline bool, b []byte, v string) []byte { const hextable = "0123456789ABCDEF" + stringQuote := `"` + if multiline { + stringQuote = `"""` + } - b = append(b, stringQuote) + b = append(b, stringQuote...) + if multiline { + b = append(b, '\n') + } for _, r := range []byte(v) { switch r { @@ -236,7 +261,11 @@ func (enc *Encoder) encodeQuotedString(b []byte, v string) []byte { case '\f': b = append(b, `\f`...) case '\n': - b = append(b, `\n`...) + if multiline { + b = append(b, r) + } else { + b = append(b, `\n`...) + } case '\r': b = append(b, `\r`...) case '\t': @@ -254,7 +283,7 @@ func (enc *Encoder) encodeQuotedString(b []byte, v string) []byte { // U+0000 to U+0008, U+000A to U+001F, U+007F } - b = append(b, stringQuote) + b = append(b, stringQuote...) return b } @@ -307,7 +336,7 @@ func (enc *Encoder) encodeKey(b []byte, k string) ([]byte, error) { } if cannotUseLiteral { - b = enc.encodeQuotedString(b, k) + b = enc.encodeQuotedString(false, b, k) } else if needsQuotation { b = enc.encodeLiteralString(b, k) } else { @@ -339,9 +368,9 @@ func (enc *Encoder) encodeMap(b []byte, ctx encoderCtx, v reflect.Value) ([]byte } if table { - t.pushTable(k, v) + t.pushTable(k, v, valueOptions{}) } else { - t.pushKV(k, v) + t.pushKV(k, v, valueOptions{}) } } @@ -358,8 +387,9 @@ func sortEntriesByKey(e []entry) { } type entry struct { - Key string - Value reflect.Value + Key string + Value reflect.Value + Options valueOptions } type table struct { @@ -367,12 +397,12 @@ type table struct { tables []entry } -func (t *table) pushKV(k string, v reflect.Value) { - t.kvs = append(t.kvs, entry{Key: k, Value: v}) +func (t *table) pushKV(k string, v reflect.Value, options valueOptions) { + t.kvs = append(t.kvs, entry{Key: k, Value: v, Options: options}) } -func (t *table) pushTable(k string, v reflect.Value) { - t.tables = append(t.tables, entry{Key: k, Value: v}) +func (t *table) pushTable(k string, v reflect.Value, options valueOptions) { + t.tables = append(t.tables, entry{Key: k, Value: v, Options: options}) } func (t *table) hasKVs() bool { @@ -413,10 +443,17 @@ func (enc *Encoder) encodeStruct(b []byte, ctx encoderCtx, v reflect.Value) ([]b return nil, err } + options := valueOptions{} + + ml, ok := fieldType.Tag.Lookup("multiline") + if ok { + options.multiline = ml == "true" + } + if willConvert { - t.pushTable(k, f) + t.pushTable(k, f, options) } else { - t.pushKV(k, f) + t.pushKV(k, f, options) } } @@ -439,7 +476,7 @@ func (enc *Encoder) encodeTable(b []byte, ctx encoderCtx, t table) ([]byte, erro b = append(b, `, `...) } ctx.setKey(kv.Key) - b, err = enc.encodeKv(b, ctx, kv.Value) + b, err = enc.encodeKv(b, ctx, kv.Options, kv.Value) if err != nil { return nil, err } @@ -473,7 +510,7 @@ func (enc *Encoder) encodeTable(b []byte, ctx encoderCtx, t table) ([]byte, erro for _, kv := range t.kvs { ctx.setKey(kv.Key) - b, err = enc.encodeKv(b, ctx, kv.Value) + b, err = enc.encodeKv(b, ctx, kv.Options, kv.Value) if err != nil { return nil, err } diff --git a/marshaler_test.go b/marshaler_test.go index ce71954b..29f857dc 100644 --- a/marshaler_test.go +++ b/marshaler_test.go @@ -216,6 +216,24 @@ name = 'Alice' }, expected: `a = "'\u001A"`, }, + { + desc: "multi-line string", + v: map[string]interface{}{ + "a": "hello\nworld", + }, + expected: `a = "hello\nworld"`, + }, + { + desc: "multi-line forced", + v: struct { + A string `multiline:"true"` + }{ + A: "hello\nworld", + }, + expected: `A = """ +hello +world"""`, + }, } for _, e := range examples { From 59cddbc57398aa61ae6a53a086628468b4b7c10e Mon Sep 17 00:00:00 2001 From: Vincent Serpoul Date: Thu, 15 Apr 2021 22:29:46 +0800 Subject: [PATCH 167/228] Golangci-lint v2 part two (#498) --- errors.go | 94 +++++++++++++++++++++++++++++++++++--------------- errors_test.go | 32 +++++++++++------ 2 files changed, 88 insertions(+), 38 deletions(-) diff --git a/errors.go b/errors.go index 79b59bb0..35ecc05b 100644 --- a/errors.go +++ b/errors.go @@ -50,7 +50,7 @@ func (e *DecodeError) String() string { return e.human } -/// Position returns the (line, column) pair indicating where the error +// Position returns the (line, column) pair indicating where the error // occurred in the document. Positions are 1-indexed. func (e *DecodeError) Position() (row int, column int) { return e.line, e.column @@ -63,110 +63,147 @@ func (e *DecodeError) Position() (row int, column int) { // // The function copies all bytes used in DecodeError, so that document and // highlight can be freely deallocated. +//nolint:funlen func wrapDecodeError(document []byte, de *decodeError) error { if de == nil { return nil } - err := &DecodeError{ - message: de.message, - } offset := unsafe.SubsliceOffset(document, de.highlight) - err.line, err.column = positionAtEnd(document[:offset]) + errMessage := de.message + errLine, errColumn := positionAtEnd(document[:offset]) before, after := linesOfContext(document, de.highlight, offset, 3) var buf strings.Builder - maxLine := err.line + len(after) - 1 + maxLine := errLine + len(after) - 1 lineColumnWidth := len(strconv.Itoa(maxLine)) for i := len(before) - 1; i > 0; i-- { - line := err.line - i + line := errLine - i buf.WriteString(formatLineNumber(line, lineColumnWidth)) - buf.WriteString("| ") - buf.Write(before[i]) + buf.WriteString("|") + + if len(before[i]) > 0 { + buf.WriteString(" ") + buf.Write(before[i]) + } + buf.WriteRune('\n') } - buf.WriteString(formatLineNumber(err.line, lineColumnWidth)) + buf.WriteString(formatLineNumber(errLine, lineColumnWidth)) buf.WriteString("| ") if len(before) > 0 { buf.Write(before[0]) } + buf.Write(de.highlight) + if len(after) > 0 { buf.Write(after[0]) } + buf.WriteRune('\n') buf.WriteString(strings.Repeat(" ", lineColumnWidth)) buf.WriteString("| ") + if len(before) > 0 { buf.WriteString(strings.Repeat(" ", len(before[0]))) } + buf.WriteString(strings.Repeat("~", len(de.highlight))) - buf.WriteString(" ") - buf.WriteString(err.message) + + if len(errMessage) > 0 { + buf.WriteString(" ") + buf.WriteString(errMessage) + } for i := 1; i < len(after); i++ { buf.WriteRune('\n') - line := err.line + i + line := errLine + i buf.WriteString(formatLineNumber(line, lineColumnWidth)) - buf.WriteString("| ") - buf.Write(after[i]) + buf.WriteString("|") + + if len(after[i]) > 0 { + buf.WriteString(" ") + buf.Write(after[i]) + } } - err.human = buf.String() - return err + return &DecodeError{ + message: errMessage, + line: errLine, + column: errColumn, + human: buf.String(), + } } func formatLineNumber(line int, width int) string { format := "%" + strconv.Itoa(width) + "d" + return fmt.Sprintf(format, line) } func linesOfContext(document []byte, highlight []byte, offset int, linesAround int) ([][]byte, [][]byte) { + return beforeLines(document, offset, linesAround), afterLines(document, highlight, offset, linesAround) +} + +func beforeLines(document []byte, offset int, linesAround int) [][]byte { var beforeLines [][]byte - // Walk the document in reverse from the highlight to find previous lines + // Walk the document backward from the highlight to find previous lines // of context. rest := document[:offset] +backward: for o := len(rest) - 1; o >= 0 && len(beforeLines) <= linesAround && len(rest) > 0; { - if rest[o] == '\n' { + switch { + case rest[o] == '\n': // handle individual lines beforeLines = append(beforeLines, rest[o+1:]) rest = rest[:o] o = len(rest) - 1 - } else if o == 0 { + case o == 0: // add the first line only if it's non-empty beforeLines = append(beforeLines, rest) - break - } else { + + break backward + default: o-- } } + return beforeLines +} + +func afterLines(document []byte, highlight []byte, offset int, linesAround int) [][]byte { var afterLines [][]byte // Walk the document forward from the highlight to find the following // lines of context. - rest = document[offset+len(highlight):] + rest := document[offset+len(highlight):] +forward: for o := 0; o < len(rest) && len(afterLines) <= linesAround; { - if rest[o] == '\n' { + switch { + case rest[o] == '\n': // handle individual lines afterLines = append(afterLines, rest[:o]) rest = rest[o+1:] o = 0 - } else if o == len(rest)-1 && o > 0 { + + case o == len(rest)-1 && o > 0: // add last line only if it's non-empty afterLines = append(afterLines, rest) - break - } else { + + break forward + default: o++ } } - return beforeLines, afterLines + + return afterLines } func positionAtEnd(b []byte) (row int, column int) { @@ -181,5 +218,6 @@ func positionAtEnd(b []byte) (row int, column int) { column++ } } + return } diff --git a/errors_test.go b/errors_test.go index 6f49d568..efbb59a3 100644 --- a/errors_test.go +++ b/errors_test.go @@ -2,13 +2,17 @@ package toml import ( "bytes" + "errors" "strings" "testing" "github.com/stretchr/testify/assert" ) +//nolint:funlen func TestDecodeError(t *testing.T) { + t.Parallel() + examples := []struct { desc string doc [3]string @@ -103,7 +107,7 @@ should not be seen4`}, }, { desc: "last line of more than 10", - doc: [3]string{`should not be seen + doc: [3]string{`should not be seen should not be seen should not be seen should not be seen @@ -125,7 +129,8 @@ before `, "highlighted", ``}, }, { desc: "handle empty lines in the before/after blocks", - doc: [3]string{`line1 + doc: [3]string{ + `line1 line 2 before `, "highlighted", ` after @@ -134,21 +139,21 @@ line 3 line 4 line 5`, }, - expected: ` -1| line1 -2| + expected: `1| line1 +2| 3| line 2 4| before highlighted after - | ~~~~~~~~~~~ + | ~~~~~~~~~~~ 5| line 3 -6| -7| line 4 -`, +6| +7| line 4`, }, } for _, e := range examples { + e := e t.Run(e.desc, func(t *testing.T) { + t.Parallel() b := bytes.Buffer{} b.Write([]byte(e.doc[0])) start := b.Len() @@ -162,7 +167,14 @@ line 5`, highlight: hl, message: e.msg, }) - derr := err.(*DecodeError) + + var derr *DecodeError + if !errors.As(err, &derr) { + t.Errorf("error not in expected format") + + return + } + assert.Equal(t, strings.Trim(e.expected, "\n"), derr.String()) }) } From 2eff2d082a610a8f91da44b199a7538ff086755b Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Thu, 15 Apr 2021 11:26:14 -0400 Subject: [PATCH 168/228] Rename branch v2-wip -> v2 --- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/workflow.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 5e8a42c6..4f5c0fdd 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -13,7 +13,7 @@ name: "CodeQL" on: push: - branches: [ master, v2-wip ] + branches: [ master, v2 ] pull_request: # The branches below must be a subset of the branches above branches: [ master ] diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index d66e6a3d..7d358e7b 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -2,10 +2,10 @@ name: test on: push: branches: - - v2-wip + - v2 pull_request: branches: - - v2-wip + - v2 jobs: build: From 0537b928df6308cb546eb61d60f68602a3e0e32c Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Thu, 15 Apr 2021 11:34:59 -0400 Subject: [PATCH 169/228] decoder: add test for #507 --- unmarshaler_test.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/unmarshaler_test.go b/unmarshaler_test.go index 89e57e29..aad04e83 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -818,6 +818,13 @@ bar = 2021-04-08 require.NoError(t, err) } +func TestIssue507(t *testing.T) { + data := []byte{'0', '=', '\n', '0', 'a', 'm', 'e'} + m := map[string]interface{}{} + err := toml.Unmarshal(data, &m) + require.Error(t, err) +} + func TestUnmarshalDecodeErrors(t *testing.T) { examples := []struct { desc string From 080baa8574d627ae93ab199e12a31b79e99787e2 Mon Sep 17 00:00:00 2001 From: Vincent Serpoul Date: Fri, 16 Apr 2021 00:44:31 +0800 Subject: [PATCH 170/228] golangci-lint: localtime (#509) --- localtime.go | 62 ++++++++++++++++++++++++++++++----------------- localtime_test.go | 55 ++++++++++++++++++++++++++++++++++++----- 2 files changed, 89 insertions(+), 28 deletions(-) diff --git a/localtime.go b/localtime.go index a2149e96..6555a100 100644 --- a/localtime.go +++ b/localtime.go @@ -44,6 +44,7 @@ type LocalDate struct { func LocalDateOf(t time.Time) LocalDate { var d LocalDate d.Year, d.Month, d.Day = t.Date() + return d } @@ -51,8 +52,9 @@ func LocalDateOf(t time.Time) LocalDate { func ParseLocalDate(s string) (LocalDate, error) { t, err := time.Parse("2006-01-02", s) if err != nil { - return LocalDate{}, err + return LocalDate{}, fmt.Errorf("ParseLocalDate: %w", err) } + return LocalDateOf(t), nil } @@ -92,23 +94,28 @@ func (d LocalDate) DaysSince(s LocalDate) (days int) { // We convert to Unix time so we do not have to worry about leap seconds: // Unix time increases by exactly 86400 seconds per day. deltaUnix := d.In(time.UTC).Unix() - s.In(time.UTC).Unix() - return int(deltaUnix / 86400) + + const secondsInADay = 86400 + + return int(deltaUnix / secondsInADay) } -// Before reports whether d1 occurs before d2. -func (d1 LocalDate) Before(d2 LocalDate) bool { - if d1.Year != d2.Year { - return d1.Year < d2.Year +// Before reports whether d1 occurs before future date. +func (d LocalDate) Before(future LocalDate) bool { + if d.Year != future.Year { + return d.Year < future.Year } - if d1.Month != d2.Month { - return d1.Month < d2.Month + + if d.Month != future.Month { + return d.Month < future.Month } - return d1.Day < d2.Day + + return d.Day < future.Day } -// After reports whether d1 occurs after d2. -func (d1 LocalDate) After(d2 LocalDate) bool { - return d2.Before(d1) +// After reports whether d1 occurs after past date. +func (d LocalDate) After(past LocalDate) bool { + return past.Before(d) } // MarshalText implements the encoding.TextMarshaler interface. @@ -122,6 +129,7 @@ func (d LocalDate) MarshalText() ([]byte, error) { func (d *LocalDate) UnmarshalText(data []byte) error { var err error *d, err = ParseLocalDate(string(data)) + return err } @@ -145,6 +153,7 @@ func LocalTimeOf(t time.Time) LocalTime { var tm LocalTime tm.Hour, tm.Minute, tm.Second = t.Clock() tm.Nanosecond = t.Nanosecond() + return tm } @@ -156,8 +165,9 @@ func LocalTimeOf(t time.Time) LocalTime { func ParseLocalTime(s string) (LocalTime, error) { t, err := time.Parse("15:04:05.999999999", s) if err != nil { - return LocalTime{}, err + return LocalTime{}, fmt.Errorf("ParseLocalTime: %w", err) } + return LocalTimeOf(t), nil } @@ -169,6 +179,7 @@ func (t LocalTime) String() string { if t.Nanosecond == 0 { return s } + return s + fmt.Sprintf(".%09d", t.Nanosecond) } @@ -176,6 +187,7 @@ func (t LocalTime) String() string { func (t LocalTime) IsValid() bool { // Construct a non-zero time. tm := time.Date(2, 2, 2, t.Hour, t.Minute, t.Second, t.Nanosecond, time.UTC) + return LocalTimeOf(tm) == t } @@ -190,6 +202,7 @@ func (t LocalTime) MarshalText() ([]byte, error) { func (t *LocalTime) UnmarshalText(data []byte) error { var err error *t, err = ParseLocalTime(string(data)) + return err } @@ -223,9 +236,10 @@ func ParseLocalDateTime(s string) (LocalDateTime, error) { if err != nil { t, err = time.Parse("2006-01-02t15:04:05.999999999", s) if err != nil { - return LocalDateTime{}, err + return LocalDateTime{}, fmt.Errorf("ParseLocalDateTime: %w", err) } } + return LocalDateTimeOf(t), nil } @@ -253,17 +267,20 @@ func (dt LocalDateTime) IsValid() bool { // // In panics if loc is nil. func (dt LocalDateTime) In(loc *time.Location) time.Time { - return time.Date(dt.Date.Year, dt.Date.Month, dt.Date.Day, dt.Time.Hour, dt.Time.Minute, dt.Time.Second, dt.Time.Nanosecond, loc) + return time.Date( + dt.Date.Year, dt.Date.Month, dt.Date.Day, + dt.Time.Hour, dt.Time.Minute, dt.Time.Second, dt.Time.Nanosecond, loc, + ) } -// Before reports whether dt1 occurs before dt2. -func (dt1 LocalDateTime) Before(dt2 LocalDateTime) bool { - return dt1.In(time.UTC).Before(dt2.In(time.UTC)) +// Before reports whether dt occurs before future. +func (dt LocalDateTime) Before(future LocalDateTime) bool { + return dt.In(time.UTC).Before(future.In(time.UTC)) } -// After reports whether dt1 occurs after dt2. -func (dt1 LocalDateTime) After(dt2 LocalDateTime) bool { - return dt2.Before(dt1) +// After reports whether dt occurs after past. +func (dt LocalDateTime) After(past LocalDateTime) bool { + return past.Before(dt) } // MarshalText implements the encoding.TextMarshaler interface. @@ -273,9 +290,10 @@ func (dt LocalDateTime) MarshalText() ([]byte, error) { } // UnmarshalText implements the encoding.TextUnmarshaler interface. -// The datetime is expected to be a string in a format accepted by ParseLocalDateTime +// The datetime is expected to be a string in a format accepted by ParseLocalDateTime. func (dt *LocalDateTime) UnmarshalText(data []byte) error { var err error *dt, err = ParseLocalDateTime(string(data)) + return err } diff --git a/localtime_test.go b/localtime_test.go index 4bbb5b0e..a3275777 100644 --- a/localtime_test.go +++ b/localtime_test.go @@ -26,6 +26,8 @@ func cmpEqual(x, y interface{}) bool { } func TestDates(t *testing.T) { + t.Parallel() + for _, test := range []struct { date LocalDate loc *time.Location @@ -61,6 +63,8 @@ func TestDates(t *testing.T) { } func TestDateIsValid(t *testing.T) { + t.Parallel() + for _, test := range []struct { date LocalDate want bool @@ -86,6 +90,10 @@ func TestDateIsValid(t *testing.T) { } func TestParseDate(t *testing.T) { + t.Parallel() + + var emptyDate LocalDate + for _, test := range []struct { str string want LocalDate // if empty, expect an error @@ -93,21 +101,23 @@ func TestParseDate(t *testing.T) { {"2016-01-02", LocalDate{2016, 1, 2}}, {"2016-12-31", LocalDate{2016, 12, 31}}, {"0003-02-04", LocalDate{3, 2, 4}}, - {"999-01-26", LocalDate{}}, - {"", LocalDate{}}, - {"2016-01-02x", LocalDate{}}, + {"999-01-26", emptyDate}, + {"", emptyDate}, + {"2016-01-02x", emptyDate}, } { got, err := ParseLocalDate(test.str) if got != test.want { t.Errorf("ParseLocalDate(%q) = %+v, want %+v", test.str, got, test.want) } - if err != nil && test.want != (LocalDate{}) { + if err != nil && test.want != (emptyDate) { t.Errorf("Unexpected error %v from ParseLocalDate(%q)", err, test.str) } } } func TestDateArithmetic(t *testing.T) { + t.Parallel() + for _, test := range []struct { desc string start LocalDate @@ -167,6 +177,8 @@ func TestDateArithmetic(t *testing.T) { } func TestDateBefore(t *testing.T) { + t.Parallel() + for _, test := range []struct { d1, d2 LocalDate want bool @@ -183,6 +195,8 @@ func TestDateBefore(t *testing.T) { } func TestDateAfter(t *testing.T) { + t.Parallel() + for _, test := range []struct { d1, d2 LocalDate want bool @@ -198,6 +212,8 @@ func TestDateAfter(t *testing.T) { } func TestTimeToString(t *testing.T) { + t.Parallel() + for _, test := range []struct { str string time LocalTime @@ -212,6 +228,7 @@ func TestTimeToString(t *testing.T) { gotTime, err := ParseLocalTime(test.str) if err != nil { t.Errorf("ParseLocalTime(%q): got error: %v", test.str, err) + continue } if gotTime != test.time { @@ -227,6 +244,8 @@ func TestTimeToString(t *testing.T) { } func TestTimeOf(t *testing.T) { + t.Parallel() + for _, test := range []struct { time time.Time want LocalTime @@ -241,6 +260,8 @@ func TestTimeOf(t *testing.T) { } func TestTimeIsValid(t *testing.T) { + t.Parallel() + for _, test := range []struct { time LocalTime want bool @@ -265,6 +286,8 @@ func TestTimeIsValid(t *testing.T) { } func TestDateTimeToString(t *testing.T) { + t.Parallel() + for _, test := range []struct { str string dateTime LocalDateTime @@ -277,6 +300,7 @@ func TestDateTimeToString(t *testing.T) { gotDateTime, err := ParseLocalDateTime(test.str) if err != nil { t.Errorf("ParseLocalDateTime(%q): got error: %v", test.str, err) + continue } if gotDateTime != test.dateTime { @@ -292,6 +316,8 @@ func TestDateTimeToString(t *testing.T) { } func TestParseDateTimeErrors(t *testing.T) { + t.Parallel() + for _, str := range []string{ "", "2016-03-22", // just a date @@ -306,6 +332,8 @@ func TestParseDateTimeErrors(t *testing.T) { } func TestDateTimeOf(t *testing.T) { + t.Parallel() + for _, test := range []struct { time time.Time want LocalDateTime @@ -322,6 +350,8 @@ func TestDateTimeOf(t *testing.T) { } func TestDateTimeIsValid(t *testing.T) { + t.Parallel() + // No need to be exhaustive here; it's just LocalDate.IsValid && LocalTime.IsValid. for _, test := range []struct { dt LocalDateTime @@ -339,19 +369,24 @@ func TestDateTimeIsValid(t *testing.T) { } func TestDateTimeIn(t *testing.T) { + t.Parallel() + dt := LocalDateTime{LocalDate{2016, 1, 2}, LocalTime{3, 4, 5, 6}} - got := dt.In(time.UTC) + want := time.Date(2016, 1, 2, 3, 4, 5, 6, time.UTC) - if !got.Equal(want) { + if got := dt.In(time.UTC); !got.Equal(want) { t.Errorf("got %v, want %v", got, want) } } func TestDateTimeBefore(t *testing.T) { + t.Parallel() + d1 := LocalDate{2016, 12, 31} d2 := LocalDate{2017, 1, 1} t1 := LocalTime{5, 6, 7, 8} t2 := LocalTime{5, 6, 7, 9} + for _, test := range []struct { dt1, dt2 LocalDateTime want bool @@ -368,10 +403,13 @@ func TestDateTimeBefore(t *testing.T) { } func TestDateTimeAfter(t *testing.T) { + t.Parallel() + d1 := LocalDate{2016, 12, 31} d2 := LocalDate{2017, 1, 1} t1 := LocalTime{5, 6, 7, 8} t2 := LocalTime{5, 6, 7, 9} + for _, test := range []struct { dt1, dt2 LocalDateTime want bool @@ -388,6 +426,8 @@ func TestDateTimeAfter(t *testing.T) { } func TestMarshalJSON(t *testing.T) { + t.Parallel() + for _, test := range []struct { value interface{} want string @@ -407,9 +447,12 @@ func TestMarshalJSON(t *testing.T) { } func TestUnmarshalJSON(t *testing.T) { + t.Parallel() + var d LocalDate var tm LocalTime var dt LocalDateTime + for _, test := range []struct { data string ptr interface{} From b86b890b8d29feed03dbabf1355e2a3f4715b1ac Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Thu, 15 Apr 2021 12:49:24 -0400 Subject: [PATCH 171/228] decoder: handle private anonymous structs Ref #508 --- targets.go | 7 +++---- unmarshaler_test.go | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/targets.go b/targets.go index 0024264b..baad5c4a 100644 --- a/targets.go +++ b/targets.go @@ -516,11 +516,10 @@ func scopeStruct(v reflect.Value, name string) (target, bool, error) { l := len(path) path = append(path, i) f := t.Field(i) - if f.PkgPath != "" { - // only consider exported fields - } else if f.Anonymous { + if f.Anonymous { walk(v.Field(i)) - } else { + } else if f.PkgPath == "" { + // only consider exported fields fieldName, ok := f.Tag.Lookup("toml") if !ok { fieldName = f.Name diff --git a/unmarshaler_test.go b/unmarshaler_test.go index aad04e83..8d742f06 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -931,3 +931,19 @@ func TestIssue287(t *testing.T) { } require.Equal(t, expected, v) } + +func TestIssue508(t *testing.T) { + type head struct { + Title string `toml:"title"` + } + type text struct { + head + } + + var b = []byte(`title = "This is a title"`) + + t1 := text{} + err := toml.Unmarshal(b, &t1) + require.NoError(t, err) + require.Equal(t, "This is a title", t1.head.Title) +} From 9bc4641a49b9be94f4f2aa1e340009ea415ef588 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Thu, 15 Apr 2021 13:37:24 -0400 Subject: [PATCH 172/228] ci-lint: disable ifshort --- .golangci.toml | 2 +- targets.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.golangci.toml b/.golangci.toml index 2124de66..60bb5c10 100644 --- a/.golangci.toml +++ b/.golangci.toml @@ -45,7 +45,7 @@ enable = [ "gosec", "gosimple", "govet", - "ifshort", + # "ifshort", "importas", "ineffassign", "lll", diff --git a/targets.go b/targets.go index baad5c4a..b25b2039 100644 --- a/targets.go +++ b/targets.go @@ -24,7 +24,7 @@ type target interface { // Store a float64 at the target setFloat64(v float64) error - // Stores any value at the target + // Stores any value at the target set(v reflect.Value) error } From 24b62ebe616e68d412715ae1a6b3a9dfe337b613 Mon Sep 17 00:00:00 2001 From: Cameron Moore Date: Thu, 15 Apr 2021 15:48:19 -0500 Subject: [PATCH 173/228] Simplify scanFollows usage (#510) Use static functions to avoid declaring global vars and creating more package init costs. This change has no negative effects on benchmarks in my testing. --- scanner.go | 62 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/scanner.go b/scanner.go index bad47bcd..8ba6f99a 100644 --- a/scanner.go +++ b/scanner.go @@ -2,29 +2,37 @@ package toml import "fmt" -func scanFollows(pattern []byte) func(b []byte) bool { - return func(b []byte) bool { - if len(b) < len(pattern) { - return false - } - for i, c := range pattern { - if b[i] != c { - return false - } - } - return true - } +func scanFollows(b []byte, pattern string) bool { + n := len(pattern) + return len(b) >= n && string(b[:n]) == pattern +} + +func scanFollowsMultilineBasicStringDelimiter(b []byte) bool { + return scanFollows(b, `"""`) +} + +func scanFollowsMultilineLiteralStringDelimiter(b []byte) bool { + return scanFollows(b, `'''`) } -var scanFollowsMultilineBasicStringDelimiter = scanFollows([]byte{'"', '"', '"'}) -var scanFollowsMultilineLiteralStringDelimiter = scanFollows([]byte{'\'', '\'', '\''}) -var scanFollowsTrue = scanFollows([]byte{'t', 'r', 'u', 'e'}) -var scanFollowsFalse = scanFollows([]byte{'f', 'a', 'l', 's', 'e'}) -var scanFollowsInf = scanFollows([]byte{'i', 'n', 'f'}) -var scanFollowsNan = scanFollows([]byte{'n', 'a', 'n'}) +func scanFollowsTrue(b []byte) bool { + return scanFollows(b, `true`) +} + +func scanFollowsFalse(b []byte) bool { + return scanFollows(b, `false`) +} + +func scanFollowsInf(b []byte) bool { + return scanFollows(b, `inf`) +} + +func scanFollowsNan(b []byte) bool { + return scanFollows(b, `nan`) +} func scanUnquotedKey(b []byte) ([]byte, []byte, error) { - //unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _ + // unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _ for i := 0; i < len(b); i++ { if !isUnquotedKeyChar(b[i]) { return b[:i], b[i:], nil @@ -38,9 +46,9 @@ func isUnquotedKeyChar(r byte) bool { } func scanLiteralString(b []byte) ([]byte, []byte, error) { - //literal-string = apostrophe *literal-char apostrophe - //apostrophe = %x27 ; ' apostrophe - //literal-char = %x09 / %x20-26 / %x28-7E / non-ascii + // literal-string = apostrophe *literal-char apostrophe + // apostrophe = %x27 ; ' apostrophe + // literal-char = %x09 / %x20-26 / %x28-7E / non-ascii for i := 1; i < len(b); i++ { switch b[i] { case '\'': @@ -115,11 +123,11 @@ func scanComment(b []byte) ([]byte, []byte, error) { // TODO perform validation on the string? func scanBasicString(b []byte) ([]byte, []byte, error) { - //basic-string = quotation-mark *basic-char quotation-mark - //quotation-mark = %x22 ; " - //basic-char = basic-unescaped / escaped - //basic-unescaped = wschar / %x21 / %x23-5B / %x5D-7E / non-ascii - //escaped = escape escape-seq-char + // basic-string = quotation-mark *basic-char quotation-mark + // quotation-mark = %x22 ; " + // basic-char = basic-unescaped / escaped + // basic-unescaped = wschar / %x21 / %x23-5B / %x5D-7E / non-ascii + // escaped = escape escape-seq-char for i := 1; i < len(b); i++ { switch b[i] { case '"': From a7b50eb8f127779f3e948a74354c5e81a2824a2c Mon Sep 17 00:00:00 2001 From: Cameron Moore Date: Thu, 15 Apr 2021 15:49:19 -0500 Subject: [PATCH 174/228] Tidy (#511) * Disconnect package godoc comment from imported file * Add missing newline in toml.abnf * Tag testing helper funcs --- localtime.go | 1 + parser_test.go | 16 ++++++++++------ toml.abnf | 1 + unmarshaler_test.go | 16 +++++++++++----- 4 files changed, 23 insertions(+), 11 deletions(-) diff --git a/localtime.go b/localtime.go index 6555a100..f271befb 100644 --- a/localtime.go +++ b/localtime.go @@ -23,6 +23,7 @@ // // Because they lack location information, these types do not represent unique // moments or intervals of time. Use time.Time for that purpose. + package toml import ( diff --git a/parser_test.go b/parser_test.go index bdae78ab..e4b82a33 100644 --- a/parser_test.go +++ b/parser_test.go @@ -155,12 +155,14 @@ func TestParser_AST_Numbers(t *testing.T) { } } -type astRoot []astNode -type astNode struct { - Kind ast.Kind - Data []byte - Children []astNode -} +type ( + astRoot []astNode + astNode struct { + Kind ast.Kind + Data []byte + Children []astNode + } +) func compareAST(t *testing.T, expected astRoot, actual *ast.Root) { it := actual.Iterator() @@ -168,6 +170,7 @@ func compareAST(t *testing.T, expected astRoot, actual *ast.Root) { } func compareNode(t *testing.T, e astNode, n ast.Node) { + t.Helper() require.Equal(t, e.Kind, n.Kind) require.Equal(t, e.Data, n.Data) @@ -175,6 +178,7 @@ func compareNode(t *testing.T, e astNode, n ast.Node) { } func compareIterator(t *testing.T, expected []astNode, actual ast.Iterator) { + t.Helper() idx := 0 for actual.Next() { diff --git a/toml.abnf b/toml.abnf index 5655ed55..473f3749 100644 --- a/toml.abnf +++ b/toml.abnf @@ -59,6 +59,7 @@ val = string / boolean / array / inline-table / date-time / float / integer ;; String string = ml-basic-string / basic-string / ml-literal-string / literal-string + ;; Basic String basic-string = quotation-mark *basic-char quotation-mark diff --git a/unmarshaler_test.go b/unmarshaler_test.go index 8d742f06..4cda0de6 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -132,6 +132,7 @@ func TestUnmarshal_Floats(t *testing.T) { desc: "nan", input: `nan`, testFn: func(t *testing.T, v float64) { + t.Helper() assert.True(t, math.IsNaN(v)) }, }, @@ -139,6 +140,7 @@ func TestUnmarshal_Floats(t *testing.T) { desc: "nan negative", input: `-nan`, testFn: func(t *testing.T, v float64) { + t.Helper() assert.True(t, math.IsNaN(v)) }, }, @@ -146,6 +148,7 @@ func TestUnmarshal_Floats(t *testing.T) { desc: "nan positive", input: `+nan`, testFn: func(t *testing.T, v float64) { + t.Helper() assert.True(t, math.IsNaN(v)) }, }, @@ -759,8 +762,10 @@ func TestIssue484(t *testing.T) { }, cfg) } -type Map458 map[string]interface{} -type Slice458 []interface{} +type ( + Map458 map[string]interface{} + Slice458 []interface{} +) func (m Map458) A(s string) Slice458 { return m[s].([]interface{}) @@ -779,7 +784,8 @@ version = "0.1.0"`) map[string]interface{}{ "dependencies": []interface{}{"regex"}, "name": "decode", - "version": "0.1.0"}, + "version": "0.1.0", + }, } assert.Equal(t, expected, a) } @@ -790,7 +796,7 @@ func TestIssue252(t *testing.T) { Val2 string `toml:"val2"` } - var configFile = []byte( + configFile := []byte( ` val1 = "test1" `) @@ -940,7 +946,7 @@ func TestIssue508(t *testing.T) { head } - var b = []byte(`title = "This is a title"`) + b := []byte(`title = "This is a title"`) t1 := text{} err := toml.Unmarshal(b, &t1) From a713a96e69e4e514eeb5421af7cecb8f34d5b991 Mon Sep 17 00:00:00 2001 From: Cameron Moore Date: Fri, 16 Apr 2021 18:07:29 -0500 Subject: [PATCH 175/228] Add more newline tests for scanner (#515) --- unmarshaler_test.go | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/unmarshaler_test.go b/unmarshaler_test.go index 4cda0de6..d2b7512d 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -709,6 +709,42 @@ B = "data"`, } }, }, + { + desc: "windows line endings", + input: "A = 1\r\n\r\nB = 2", + gen: func() test { + doc := map[string]interface{}{} + return test{ + target: &doc, + expected: &map[string]interface{}{ + "A": int64(1), + "B": int64(2), + }, + } + }, + }, + { + desc: "dangling CR", + input: "A = 1\r", + gen: func() test { + doc := map[string]interface{}{} + return test{ + target: &doc, + err: true, + } + }, + }, + { + desc: "missing NL after CR", + input: "A = 1\rB = 2", + gen: func() test { + doc := map[string]interface{}{} + return test{ + target: &doc, + err: true, + } + }, + }, } for _, e := range examples { From dca210391016e95d77a81494586b10ab8cb7c898 Mon Sep 17 00:00:00 2001 From: Vincent Serpoul Date: Wed, 21 Apr 2021 08:24:44 +0800 Subject: [PATCH 176/228] golangci-lint: marshaler (#516) --- .golangci.toml | 3 + marshaler.go | 212 ++++++++++++++++++++++++++++++---------------- marshaler_test.go | 23 ++++- 3 files changed, 161 insertions(+), 77 deletions(-) diff --git a/.golangci.toml b/.golangci.toml index 60bb5c10..b27872a7 100644 --- a/.golangci.toml +++ b/.golangci.toml @@ -4,6 +4,9 @@ golangci-lint-version = "1.39.0" [linters-settings.wsl] allow-assign-and-anything = true +[linters-settings.exhaustive] +default-signifies-exhaustive = true + [linters] disable-all = true enable = [ diff --git a/marshaler.go b/marshaler.go index 9019ec0b..bbc3130c 100644 --- a/marshaler.go +++ b/marshaler.go @@ -18,10 +18,12 @@ import ( func Marshal(v interface{}) ([]byte, error) { var buf bytes.Buffer enc := NewEncoder(&buf) + err := enc.Encode(v) if err != nil { return nil, err } + return buf.Bytes(), nil } @@ -96,7 +98,7 @@ func NewEncoder(w io.Writer) *Encoder { // 5. Intermediate tables are always printed. // // By default, strings are encoded as literal string, unless they contain either -// a newline character or a single quote. In that case they are emited as quoted +// a newline character or a single quote. In that case they are emitted as quoted // strings. // // When encoding structs, fields are encoded in order of definition, with their @@ -107,25 +109,38 @@ func NewEncoder(w io.Writer) *Encoder { // `multiline:"true"`: when the field contains a string, it will be emitted as // a quoted multi-line TOML string. func (enc *Encoder) Encode(v interface{}) error { - var b []byte - var ctx encoderCtx + var ( + b []byte + ctx encoderCtx + ) + b, err := enc.encode(b, ctx, reflect.ValueOf(v)) if err != nil { - return err + return fmt.Errorf("Encode: %w", err) } + _, err = enc.w.Write(b) - return err + if err != nil { + return fmt.Errorf("Encode: %w", err) + } + + return nil } +var errUnsupportedValue = errors.New("unsupported encode value kind") + +//nolint:cyclop func (enc *Encoder) encode(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) { + //nolint:gocritic,godox switch i := v.Interface().(type) { case time.Time: // TODO: add TextMarshaler b = i.AppendFormat(b, time.RFC3339) + return b, nil } - // containers switch v.Kind() { + // containers case reflect.Map: return enc.encodeMap(b, ctx, v) case reflect.Struct: @@ -136,19 +151,18 @@ func (enc *Encoder) encode(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, e if v.IsNil() { return nil, errNilInterface } + return enc.encode(b, ctx, v.Elem()) case reflect.Ptr: if v.IsNil() { return enc.encode(b, ctx, reflect.Zero(v.Type().Elem())) } + return enc.encode(b, ctx, v.Elem()) - } // values - var err error - switch v.Kind() { case reflect.String: - b, err = enc.encodeString(b, v.String(), ctx.options) + b = enc.encodeString(b, v.String(), ctx.options) case reflect.Float32: b = strconv.AppendFloat(b, v.Float(), 'f', -1, 32) case reflect.Float64: @@ -164,10 +178,7 @@ func (enc *Encoder) encode(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, e case reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8, reflect.Int: b = strconv.AppendInt(b, v.Int(), 10) default: - err = fmt.Errorf("unsupported encode value kind: %s", v.Kind()) - } - if err != nil { - return nil, err + return nil, fmt.Errorf("encode(type %s): %w", v.Kind(), errUnsupportedValue) } return b, nil @@ -217,30 +228,31 @@ func (enc *Encoder) encodeKv(b []byte, ctx encoderCtx, options valueOptions, v r const literalQuote = '\'' -func (enc *Encoder) encodeString(b []byte, v string, options valueOptions) ([]byte, error) { +func (enc *Encoder) encodeString(b []byte, v string, options valueOptions) []byte { if needsQuoting(v) { - b = enc.encodeQuotedString(options.multiline, b, v) - } else { - b = enc.encodeLiteralString(b, v) + return enc.encodeQuotedString(options.multiline, b, v) } - return b, nil + + return enc.encodeLiteralString(b, v) } func needsQuoting(v string) bool { return strings.ContainsAny(v, "'\b\f\n\r\t") } -// caller should have checked that the string does not contain new lines or ' +// caller should have checked that the string does not contain new lines or ' . func (enc *Encoder) encodeLiteralString(b []byte, v string) []byte { b = append(b, literalQuote) b = append(b, v...) b = append(b, literalQuote) + return b } +//nolint:cyclop func (enc *Encoder) encodeQuotedString(multiline bool, b []byte, v string) []byte { - const hextable = "0123456789ABCDEF" stringQuote := `"` + if multiline { stringQuote = `"""` } @@ -250,6 +262,16 @@ func (enc *Encoder) encodeQuotedString(multiline bool, b []byte, v string) []byt b = append(b, '\n') } + const ( + hextable = "0123456789ABCDEF" + // U+0000 to U+0008, U+000A to U+001F, U+007F + nul = 0x0 + bs = 0x8 + lf = 0xa + us = 0x1f + del = 0x7f + ) + for _, r := range []byte(v) { switch r { case '\\': @@ -272,7 +294,7 @@ func (enc *Encoder) encodeQuotedString(multiline bool, b []byte, v string) []byt b = append(b, `\t`...) default: switch { - case r >= 0x0 && r <= 0x8, r >= 0xA && r <= 0x1F, r == 0x7F: + case r >= nul && r <= bs, r >= lf && r <= us, r == del: b = append(b, `\u00`...) b = append(b, hextable[r>>4]) b = append(b, hextable[r&0x0f]) @@ -280,14 +302,14 @@ func (enc *Encoder) encodeQuotedString(multiline bool, b []byte, v string) []byt b = append(b, r) } } - // U+0000 to U+0008, U+000A to U+001F, U+007F } b = append(b, stringQuote...) + return b } -// called should have checked that the string is in A-Z / a-z / 0-9 / - / _ +// called should have checked that the string is in A-Z / a-z / 0-9 / - / _ . func (enc *Encoder) encodeUnquotedKey(b []byte, v string) []byte { return append(b, v...) } @@ -300,6 +322,7 @@ func (enc *Encoder) encodeTableHeader(b []byte, key []string) ([]byte, error) { b = append(b, '[') var err error + b, err = enc.encodeKey(b, key[0]) if err != nil { return nil, err @@ -307,6 +330,7 @@ func (enc *Encoder) encodeTableHeader(b []byte, key []string) ([]byte, error) { for _, k := range key[1:] { b = append(b, '.') + b, err = enc.encodeKey(b, k) if err != nil { return nil, err @@ -318,6 +342,9 @@ func (enc *Encoder) encodeTableHeader(b []byte, key []string) ([]byte, error) { return b, nil } +var errTomlNoMultiline = errors.New("TOML does not support multiline keys") + +//nolint:cyclop func (enc *Encoder) encodeKey(b []byte, k string) ([]byte, error) { needsQuotation := false cannotUseLiteral := false @@ -326,32 +353,39 @@ func (enc *Encoder) encodeKey(b []byte, k string) ([]byte, error) { if (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '_' { continue } + if c == '\n' { - return nil, fmt.Errorf("TOML does not support multiline keys") + return nil, errTomlNoMultiline } + if c == literalQuote { cannotUseLiteral = true } + needsQuotation = true } - if cannotUseLiteral { - b = enc.encodeQuotedString(false, b, k) - } else if needsQuotation { - b = enc.encodeLiteralString(b, k) - } else { - b = enc.encodeUnquotedKey(b, k) + switch { + case cannotUseLiteral: + return enc.encodeQuotedString(false, b, k), nil + case needsQuotation: + return enc.encodeLiteralString(b, k), nil + default: + return enc.encodeUnquotedKey(b, k), nil } - - return b, nil } +var errNotSupportedAsMapKey = errors.New("type not supported as map key") + func (enc *Encoder) encodeMap(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) { if v.Type().Key().Kind() != reflect.String { - return nil, fmt.Errorf("type '%s' not supported as map key", v.Type().Key().Kind()) + return nil, fmt.Errorf("encodeMap '%s': %w", v.Type().Key().Kind(), errNotSupportedAsMapKey) } - t := table{} + var ( + t table + emptyValueOptions valueOptions + ) iter := v.MapRange() for iter.Next() { @@ -368,9 +402,9 @@ func (enc *Encoder) encodeMap(b []byte, ctx encoderCtx, v reflect.Value) ([]byte } if table { - t.pushTable(k, v, valueOptions{}) + t.pushTable(k, v, emptyValueOptions) } else { - t.pushKV(k, v, valueOptions{}) + t.pushKV(k, v, emptyValueOptions) } } @@ -405,13 +439,10 @@ func (t *table) pushTable(k string, v reflect.Value, options valueOptions) { t.tables = append(t.tables, entry{Key: k, Value: v, Options: options}) } -func (t *table) hasKVs() bool { - return len(t.kvs) > 0 -} - func (enc *Encoder) encodeStruct(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) { - t := table{} + var t table + //nolint:godox // TODO: cache this? typ := v.Type() for i := 0; i < typ.NumField(); i++ { @@ -443,7 +474,7 @@ func (enc *Encoder) encodeStruct(b []byte, ctx encoderCtx, v reflect.Value) ([]b return nil, err } - options := valueOptions{} + var options valueOptions ml, ok := fieldType.Tag.Lookup("multiline") if ok { @@ -466,73 +497,89 @@ func (enc *Encoder) encodeTable(b []byte, ctx encoderCtx, t table) ([]byte, erro ctx.shiftKey() if ctx.insideKv { - b = append(b, '{') + return enc.encodeTableInsideKV(b, ctx, t) + } - first := true - for _, kv := range t.kvs { - if first { - first = false - } else { - b = append(b, `, `...) - } - ctx.setKey(kv.Key) - b, err = enc.encodeKv(b, ctx, kv.Options, kv.Value) - if err != nil { - return nil, err - } + if !ctx.skipTableHeader { + b, err = enc.encodeTableHeader(b, ctx.parentKey) + if err != nil { + return nil, err } + } + ctx.skipTableHeader = false - for _, table := range t.tables { - if first { - first = false - } else { - b = append(b, `, `...) - } - ctx.setKey(table.Key) - b, err = enc.encode(b, ctx, table.Value) - if err != nil { - return nil, err - } - b = append(b, '\n') + for _, kv := range t.kvs { + ctx.setKey(kv.Key) + + b, err = enc.encodeKv(b, ctx, kv.Options, kv.Value) + if err != nil { + return nil, err } - b = append(b, "}\n"...) - return b, nil + b = append(b, '\n') } - if !ctx.skipTableHeader { - b, err = enc.encodeTableHeader(b, ctx.parentKey) + for _, table := range t.tables { + ctx.setKey(table.Key) + + b, err = enc.encode(b, ctx, table.Value) if err != nil { return nil, err } + + b = append(b, '\n') } - ctx.skipTableHeader = false + return b, nil +} + +func (enc *Encoder) encodeTableInsideKV(b []byte, ctx encoderCtx, t table) ([]byte, error) { + var err error + + b = append(b, '{') + + first := true for _, kv := range t.kvs { + if first { + first = false + } else { + b = append(b, `, `...) + } + ctx.setKey(kv.Key) + b, err = enc.encodeKv(b, ctx, kv.Options, kv.Value) if err != nil { return nil, err } - b = append(b, '\n') } for _, table := range t.tables { + if first { + first = false + } else { + b = append(b, `, `...) + } + ctx.setKey(table.Key) + b, err = enc.encode(b, ctx, table.Value) if err != nil { return nil, err } + b = append(b, '\n') } + b = append(b, "}\n"...) + return b, nil } var errNilInterface = errors.New("nil interface not supported") -var errNilPointer = errors.New("nil pointer not supported") func willConvertToTable(v reflect.Value) (bool, error) { + //nolint:gocritic,godox switch v.Interface().(type) { case time.Time: // TODO: add TextMarshaler return false, nil @@ -546,11 +593,13 @@ func willConvertToTable(v reflect.Value) (bool, error) { if v.IsNil() { return false, errNilInterface } + return willConvertToTable(v.Elem()) case reflect.Ptr: if v.IsNil() { return false, nil } + return willConvertToTable(v.Elem()) default: return false, nil @@ -564,6 +613,7 @@ func willConvertToTableOrArrayTable(v reflect.Value) (bool, error) { if v.IsNil() { return false, errNilInterface } + return willConvertToTableOrArrayTable(v.Elem()) } @@ -572,15 +622,18 @@ func willConvertToTableOrArrayTable(v reflect.Value) (bool, error) { // An empty slice should be a kv = []. return false, nil } + for i := 0; i < v.Len(); i++ { t, err := willConvertToTable(v.Index(i)) if err != nil { return false, err } + if !t { return false, nil } } + return true, nil } @@ -590,6 +643,7 @@ func willConvertToTableOrArrayTable(v reflect.Value) (bool, error) { func (enc *Encoder) encodeSlice(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) { if v.Len() == 0 { b = append(b, "[]"...) + return b, nil } @@ -617,25 +671,30 @@ func (enc *Encoder) encodeSliceAsArrayTable(b []byte, ctx encoderCtx, v reflect. var err error scratch := make([]byte, 0, 64) scratch = append(scratch, "[["...) + for i, k := range ctx.parentKey { if i > 0 { scratch = append(scratch, '.') } + scratch, err = enc.encodeKey(scratch, k) if err != nil { return nil, err } } + scratch = append(scratch, "]]\n"...) ctx.skipTableHeader = true for i := 0; i < v.Len(); i++ { b = append(b, scratch...) + b, err = enc.encode(b, ctx, v.Index(i)) if err != nil { return nil, err } } + return b, nil } @@ -644,10 +703,12 @@ func (enc *Encoder) encodeSliceAsArray(b []byte, ctx encoderCtx, v reflect.Value var err error first := true + for i := 0; i < v.Len(); i++ { if !first { b = append(b, ", "...) } + first = false b, err = enc.encode(b, ctx, v.Index(i)) @@ -657,5 +718,6 @@ func (enc *Encoder) encodeSliceAsArray(b []byte, ctx encoderCtx, v reflect.Value } b = append(b, ']') + return b, nil } diff --git a/marshaler_test.go b/marshaler_test.go index 29f857dc..b1447665 100644 --- a/marshaler_test.go +++ b/marshaler_test.go @@ -11,7 +11,10 @@ import ( "github.com/stretchr/testify/require" ) +//nolint:funlen func TestMarshal(t *testing.T) { + t.Parallel() + examples := []struct { desc string v interface{} @@ -65,6 +68,7 @@ hello = 'world'`, a = 'test'`, }, { + //nolint:godox // TODO: this test is flaky because output changes depending on // the map iteration order. desc: "map in map in map and string with values", @@ -89,6 +93,16 @@ a = 'test'`, }, expected: `array = ['one', 'two', 'three']`, }, + { + desc: "empty string array", + v: map[string][]string{}, + expected: ``, + }, + { + desc: "map", + v: map[string][]string{}, + expected: ``, + }, { desc: "nested string arrays", v: map[string][][]string{ @@ -104,7 +118,7 @@ a = 'test'`, expected: `array = ['a string', ['one', 'two'], 'last']`, }, { - desc: "slice of maps", + desc: "array of maps", v: map[string][]map[string]string{ "top": { {"map1.1": "v1.1"}, @@ -157,7 +171,7 @@ K2 = 'v2' `, }, { - desc: "structs in slice with interfaces", + desc: "structs in array with interfaces", v: map[string]interface{}{ "root": map[string]interface{}{ "nested": []interface{}{ @@ -237,7 +251,10 @@ world"""`, } for _, e := range examples { + e := e t.Run(e.desc, func(t *testing.T) { + t.Parallel() + b, err := toml.Marshal(e.v) if e.err { require.Error(t, err) @@ -256,6 +273,8 @@ func equalStringsIgnoreNewlines(t *testing.T, expected string, actual string) { } func TestIssue436(t *testing.T) { + t.Parallel() + data := []byte(`{"a": [ { "b": { "c": "d" } } ]}`) var v interface{} From 9b67e40640616143e24bc94458908a694e679821 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Tue, 20 Apr 2021 21:26:22 -0400 Subject: [PATCH 177/228] decoder: strict mode (#512) --- README.md | 2 +- errors.go | 39 +++- .../imported_tests/unmarshal_imported_test.go | 127 ++++++----- internal/tracker/key.go | 50 +++++ internal/tracker/seen.go | 200 ++++++++++++++++++ internal/tracker/tracker.go | 199 ----------------- internal/unsafe/unsafe.go | 24 +++ internal/unsafe/unsafe_test.go | 89 ++++++++ strict.go | 79 +++++++ unmarshaler.go | 51 ++++- unmarshaler_test.go | 114 ++++++++++ 11 files changed, 715 insertions(+), 259 deletions(-) create mode 100644 internal/tracker/key.go create mode 100644 internal/tracker/seen.go create mode 100644 strict.go diff --git a/README.md b/README.md index 1a2e6bba..ee6a3eae 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Development branch. Use at your own risk. - [x] Abstract AST. - [x] Original go-toml testgen tests pass. - [x] Track file position (line, column) for errors. -- [ ] Strict mode. +- [x] Strict mode. - [ ] Document Unmarshal / Decode ### Marshal diff --git a/errors.go b/errors.go index 35ecc05b..39446dcb 100644 --- a/errors.go +++ b/errors.go @@ -18,15 +18,46 @@ type DecodeError struct { message string line int column int + key Key human string } +// StrictMissingError occurs in a TOML document that does not have a +// corresponding field in the target value. It contains all the missing fields +// in Errors. +// +// Emitted by Decoder when SetStrict(true) was called. +type StrictMissingError struct { + // One error per field that could not be found. + Errors []DecodeError +} + +// Error returns the cannonical string for this error. +func (s *StrictMissingError) Error() string { + return "strict mode: fields in the document are missing in the target struct" +} + +// String returns a human readable description of all errors. +func (s *StrictMissingError) String() string { + var buf strings.Builder + for i, e := range s.Errors { + if i > 0 { + buf.WriteString("\n---\n") + } + buf.WriteString(e.String()) + } + return buf.String() +} + +type Key []string + // internal version of DecodeError that is used as the base to create a // DecodeError with full context. type decodeError struct { highlight []byte message string + key Key // optional } func (de *decodeError) Error() string { @@ -56,6 +87,11 @@ func (e *DecodeError) Position() (row int, column int) { return e.line, e.column } +// Key that was being processed when the error occured. +func (e *DecodeError) Key() Key { + return e.key +} + // decodeErrorFromHighlight creates a DecodeError referencing to a highlighted // range of bytes from document. // @@ -64,7 +100,7 @@ func (e *DecodeError) Position() (row int, column int) { // The function copies all bytes used in DecodeError, so that document and // highlight can be freely deallocated. //nolint:funlen -func wrapDecodeError(document []byte, de *decodeError) error { +func wrapDecodeError(document []byte, de *decodeError) *DecodeError { if de == nil { return nil } @@ -137,6 +173,7 @@ func wrapDecodeError(document []byte, de *decodeError) error { message: errMessage, line: errLine, column: errColumn, + key: de.key, human: buf.String(), } } diff --git a/internal/imported_tests/unmarshal_imported_test.go b/internal/imported_tests/unmarshal_imported_test.go index 59585435..c641a2a0 100644 --- a/internal/imported_tests/unmarshal_imported_test.go +++ b/internal/imported_tests/unmarshal_imported_test.go @@ -7,6 +7,7 @@ package imported_tests // marked as skipped until we figure out if that's something we want in v2. import ( + "bytes" "errors" "fmt" "reflect" @@ -1955,66 +1956,80 @@ String2="2"` assert.Error(t, err) } +func decoder(doc string) *toml.Decoder { + return toml.NewDecoder(bytes.NewReader([]byte(doc))) +} + +func strictDecoder(doc string) *toml.Decoder { + d := decoder(doc) + d.SetStrict(true) + return d +} + func TestDecoderStrict(t *testing.T) { - t.Skip() - // input := ` - //[decoded] - // key = "" - // - //[undecoded] - // key = "" - // - // [undecoded.inner] - // key = "" - // - // [[undecoded.array]] - // key = "" - // - // [[undecoded.array]] - // key = "" - // - //` - // var doc struct { - // Decoded struct { - // Key string - // } - // } - // - // expected := `undecoded keys: ["undecoded.array.0.key" "undecoded.array.1.key" "undecoded.inner.key" "undecoded.key"]` - // - // err := NewDecoder(bytes.NewReader([]byte(input))).Strict(true).Decode(&doc) - // if err == nil { - // t.Error("expected error, got none") - // } else if err.Error() != expected { - // t.Errorf("expect err: %s, got: %s", expected, err.Error()) - // } - // - // if err := NewDecoder(bytes.NewReader([]byte(input))).Decode(&doc); err != nil { - // t.Errorf("unexpected err: %s", err) - // } - // - // var m map[string]interface{} - // if err := NewDecoder(bytes.NewReader([]byte(input))).Decode(&m); err != nil { - // t.Errorf("unexpected err: %s", err) - // } + input := ` + [decoded] + key = "" + + [undecoded] + key = "" + + [undecoded.inner] + key = "" + + [[undecoded.array]] + key = "" + + [[undecoded.array]] + key = "" + + ` + var doc struct { + Decoded struct { + Key string + } + } + + err := strictDecoder(input).Decode(&doc) + require.Error(t, err) + require.IsType(t, &toml.StrictMissingError{}, err) + se := err.(*toml.StrictMissingError) + + keys := []toml.Key{} + + for _, e := range se.Errors { + keys = append(keys, e.Key()) + } + + expectedKeys := []toml.Key{ + {"undecoded"}, + {"undecoded", "inner"}, + {"undecoded", "array"}, + {"undecoded", "array"}, + } + + require.Equal(t, expectedKeys, keys) + + err = decoder(input).Decode(&doc) + require.NoError(t, err) + + var m map[string]interface{} + err = decoder(input).Decode(&m) } func TestDecoderStrictValid(t *testing.T) { - t.Skip() - // input := ` - //[decoded] - // key = "" - //` - // var doc struct { - // Decoded struct { - // Key string - // } - // } - // - // err := NewDecoder(bytes.NewReader([]byte(input))).Strict(true).Decode(&doc) - // if err != nil { - // t.Fatal("unexpected error:", err) - // } + input := ` + [decoded] + key = "" + ` + var doc struct { + Decoded struct { + Key string + } + } + + err := strictDecoder(input).Decode(&doc) + require.NoError(t, err) } type docUnmarshalTOML struct { diff --git a/internal/tracker/key.go b/internal/tracker/key.go new file mode 100644 index 00000000..be99f720 --- /dev/null +++ b/internal/tracker/key.go @@ -0,0 +1,50 @@ +package tracker + +import ( + "github.com/pelletier/go-toml/v2/internal/ast" +) + +// KeyTracker is a tracker that keeps track of the current Key as the AST is +// walked. +type KeyTracker struct { + k []string +} + +// UpdateTable sets the state of the tracker with the AST table node. +func (t *KeyTracker) UpdateTable(node ast.Node) { + t.reset() + t.Push(node) +} + +// UpdateArrayTable sets the state of the tracker with the AST array table node. +func (t *KeyTracker) UpdateArrayTable(node ast.Node) { + t.reset() + t.Push(node) +} + +// Push the given key on the stack. +func (t *KeyTracker) Push(node ast.Node) { + it := node.Key() + for it.Next() { + t.k = append(t.k, string(it.Node().Data)) + } +} + +// Pop key from stack. +func (t *KeyTracker) Pop(node ast.Node) { + it := node.Key() + for it.Next() { + t.k = t.k[:len(t.k)-1] + } +} + +// Key returns the current key +func (t *KeyTracker) Key() []string { + k := make([]string, len(t.k)) + copy(k, t.k) + return k +} + +func (t *KeyTracker) reset() { + t.k = t.k[:0] +} diff --git a/internal/tracker/seen.go b/internal/tracker/seen.go new file mode 100644 index 00000000..f80052db --- /dev/null +++ b/internal/tracker/seen.go @@ -0,0 +1,200 @@ +package tracker + +import ( + "fmt" + + "github.com/pelletier/go-toml/v2/internal/ast" +) + +type keyKind uint8 + +const ( + invalidKind keyKind = iota + valueKind + tableKind + arrayTableKind +) + +func (k keyKind) String() string { + switch k { + case invalidKind: + return "invalid" + case valueKind: + return "value" + case tableKind: + return "table" + case arrayTableKind: + return "array table" + } + panic("missing keyKind string mapping") +} + +// SeenTracker tracks which keys have been seen with which TOML type to flag duplicates +// and mismatches according to the spec. +type SeenTracker struct { + root *info + current *info +} + +type info struct { + parent *info + kind keyKind + children map[string]*info + explicit bool +} + +func (i *info) Clear() { + i.children = nil +} + +func (i *info) Has(k string) (*info, bool) { + c, ok := i.children[k] + return c, ok +} + +func (i *info) SetKind(kind keyKind) { + i.kind = kind +} + +func (i *info) CreateTable(k string, explicit bool) *info { + return i.createChild(k, tableKind, explicit) +} + +func (i *info) CreateArrayTable(k string, explicit bool) *info { + return i.createChild(k, arrayTableKind, explicit) +} + +func (i *info) createChild(k string, kind keyKind, explicit bool) *info { + if i.children == nil { + i.children = make(map[string]*info, 1) + } + + x := &info{ + parent: i, + kind: kind, + explicit: explicit, + } + i.children[k] = x + return x +} + +// CheckExpression takes a top-level node and checks that it does not contain keys +// that have been seen in previous calls, and validates that types are consistent. +func (s *SeenTracker) CheckExpression(node ast.Node) error { + if s.root == nil { + s.root = &info{ + kind: tableKind, + } + s.current = s.root + } + switch node.Kind { + case ast.KeyValue: + return s.checkKeyValue(s.current, node) + case ast.Table: + return s.checkTable(node) + case ast.ArrayTable: + return s.checkArrayTable(node) + default: + panic(fmt.Errorf("this should not be a top level node type: %s", node.Kind)) + } + +} +func (s *SeenTracker) checkTable(node ast.Node) error { + s.current = s.root + + it := node.Key() + // handle the first parts of the key, excluding the last one + for it.Next() { + if !it.Node().Next().Valid() { + break + } + + k := string(it.Node().Data) + child, found := s.current.Has(k) + if !found { + child = s.current.CreateTable(k, false) + } + s.current = child + } + + // handle the last part of the key + k := string(it.Node().Data) + + i, found := s.current.Has(k) + if found { + if i.kind != tableKind { + return fmt.Errorf("key %s should be a table", k) + } + if i.explicit { + return fmt.Errorf("table %s already exists", k) + } + i.explicit = true + s.current = i + } else { + s.current = s.current.CreateTable(k, true) + } + + return nil +} + +func (s *SeenTracker) checkArrayTable(node ast.Node) error { + s.current = s.root + + it := node.Key() + + // handle the first parts of the key, excluding the last one + for it.Next() { + if !it.Node().Next().Valid() { + break + } + + k := string(it.Node().Data) + child, found := s.current.Has(k) + if !found { + child = s.current.CreateTable(k, false) + } + s.current = child + } + + // handle the last part of the key + k := string(it.Node().Data) + + info, found := s.current.Has(k) + if found { + if info.kind != arrayTableKind { + return fmt.Errorf("key %s already exists but is not an array table", k) + } + info.Clear() + } else { + info = s.current.CreateArrayTable(k, true) + } + + s.current = info + return nil +} + +func (s *SeenTracker) checkKeyValue(context *info, node ast.Node) error { + it := node.Key() + + // handle the first parts of the key, excluding the last one + for it.Next() { + k := string(it.Node().Data) + child, found := context.Has(k) + if found { + if child.kind != tableKind { + return fmt.Errorf("expected %s to be a table, not a %s", k, child.kind) + } + } else { + child = context.CreateTable(k, false) + } + context = child + } + + if node.Value().Kind == ast.InlineTable { + context.SetKind(tableKind) + } else { + context.SetKind(valueKind) + } + + return nil +} diff --git a/internal/tracker/tracker.go b/internal/tracker/tracker.go index 97f916b4..bf031739 100644 --- a/internal/tracker/tracker.go +++ b/internal/tracker/tracker.go @@ -1,200 +1 @@ package tracker - -import ( - "fmt" - - "github.com/pelletier/go-toml/v2/internal/ast" -) - -type keyKind uint8 - -const ( - invalidKind keyKind = iota - valueKind - tableKind - arrayTableKind -) - -func (k keyKind) String() string { - switch k { - case invalidKind: - return "invalid" - case valueKind: - return "value" - case tableKind: - return "table" - case arrayTableKind: - return "array table" - } - panic("missing keyKind string mapping") -} - -// Tracks which keys have been seen with which TOML type to flag duplicates -// and mismatches according to the spec. -type Seen struct { - root *info - current *info -} - -type info struct { - parent *info - kind keyKind - children map[string]*info - explicit bool -} - -func (i *info) Clear() { - i.children = nil -} - -func (i *info) Has(k string) (*info, bool) { - c, ok := i.children[k] - return c, ok -} - -func (i *info) SetKind(kind keyKind) { - i.kind = kind -} - -func (i *info) CreateTable(k string, explicit bool) *info { - return i.createChild(k, tableKind, explicit) -} - -func (i *info) CreateArrayTable(k string, explicit bool) *info { - return i.createChild(k, arrayTableKind, explicit) -} - -func (i *info) createChild(k string, kind keyKind, explicit bool) *info { - if i.children == nil { - i.children = make(map[string]*info, 1) - } - - x := &info{ - parent: i, - kind: kind, - explicit: explicit, - } - i.children[k] = x - return x -} - -// CheckExpression takes a top-level node and checks that it does not contain keys -// that have been seen in previous calls, and validates that types are consistent. -func (s *Seen) CheckExpression(node ast.Node) error { - if s.root == nil { - s.root = &info{ - kind: tableKind, - } - s.current = s.root - } - switch node.Kind { - case ast.KeyValue: - return s.checkKeyValue(s.current, node) - case ast.Table: - return s.checkTable(node) - case ast.ArrayTable: - return s.checkArrayTable(node) - default: - panic(fmt.Errorf("this should not be a top level node type: %s", node.Kind)) - } - -} -func (s *Seen) checkTable(node ast.Node) error { - s.current = s.root - - it := node.Key() - // handle the first parts of the key, excluding the last one - for it.Next() { - if !it.Node().Next().Valid() { - break - } - - k := string(it.Node().Data) - child, found := s.current.Has(k) - if !found { - child = s.current.CreateTable(k, false) - } - s.current = child - } - - // handle the last part of the key - k := string(it.Node().Data) - - i, found := s.current.Has(k) - if found { - if i.kind != tableKind { - return fmt.Errorf("key %s should be a table", k) - } - if i.explicit { - return fmt.Errorf("table %s already exists", k) - } - i.explicit = true - s.current = i - } else { - s.current = s.current.CreateTable(k, true) - } - - return nil -} - -func (s *Seen) checkArrayTable(node ast.Node) error { - s.current = s.root - - it := node.Key() - - // handle the first parts of the key, excluding the last one - for it.Next() { - if !it.Node().Next().Valid() { - break - } - - k := string(it.Node().Data) - child, found := s.current.Has(k) - if !found { - child = s.current.CreateTable(k, false) - } - s.current = child - } - - // handle the last part of the key - k := string(it.Node().Data) - - info, found := s.current.Has(k) - if found { - if info.kind != arrayTableKind { - return fmt.Errorf("key %s already exists but is not an array table", k) - } - info.Clear() - } else { - info = s.current.CreateArrayTable(k, true) - } - - s.current = info - return nil -} - -func (s *Seen) checkKeyValue(context *info, node ast.Node) error { - it := node.Key() - - // handle the first parts of the key, excluding the last one - for it.Next() { - k := string(it.Node().Data) - child, found := context.Has(k) - if found { - if child.kind != tableKind { - return fmt.Errorf("expected %s to be a table, not a %s", k, child.kind) - } - } else { - child = context.CreateTable(k, false) - } - context = child - } - - if node.Value().Kind == ast.InlineTable { - context.SetKind(tableKind) - } else { - context.SetKind(valueKind) - } - - return nil -} diff --git a/internal/unsafe/unsafe.go b/internal/unsafe/unsafe.go index ce6b955e..742c6ab2 100644 --- a/internal/unsafe/unsafe.go +++ b/internal/unsafe/unsafe.go @@ -33,3 +33,27 @@ func SubsliceOffset(data []byte, subslice []byte) int { return intoffset } + +func BytesRange(start []byte, end []byte) []byte { + if start == nil || end == nil { + panic("cannot call BytesRange with nil") + } + startp := (*reflect.SliceHeader)(unsafe.Pointer(&start)) + endp := (*reflect.SliceHeader)(unsafe.Pointer(&end)) + + if startp.Data > endp.Data { + panic(fmt.Errorf("start pointer address (%d) is after end pointer address (%d)", startp.Data, endp.Data)) + } + + l := startp.Len + endLen := int(endp.Data-startp.Data) + endp.Len + if endLen > l { + l = endLen + } + + if l > startp.Cap { + panic(fmt.Errorf("range length is larger than capacity")) + } + + return start[:l] +} diff --git a/internal/unsafe/unsafe_test.go b/internal/unsafe/unsafe_test.go index a184be9b..5462d086 100644 --- a/internal/unsafe/unsafe_test.go +++ b/internal/unsafe/unsafe_test.go @@ -77,3 +77,92 @@ func TestUnsafeSubsliceOffsetInvalid(t *testing.T) { }) } } + +func TestUnsafeBytesRange(t *testing.T) { + type fn = func() ([]byte, []byte) + examples := []struct { + desc string + test fn + expected []byte + }{ + { + desc: "simple", + test: func() ([]byte, []byte) { + full := []byte("hello world") + return full[1:3], full[6:8] + }, + expected: []byte("ello wo"), + }, + { + desc: "full", + test: func() ([]byte, []byte) { + full := []byte("hello world") + return full[0:1], full[len(full)-1:] + }, + expected: []byte("hello world"), + }, + { + desc: "end before start", + test: func() ([]byte, []byte) { + full := []byte("hello world") + return full[len(full)-1:], full[0:1] + }, + }, + { + desc: "nils", + test: func() ([]byte, []byte) { + return nil, nil + }, + }, + { + desc: "nils start", + test: func() ([]byte, []byte) { + return nil, []byte("foo") + }, + }, + { + desc: "nils end", + test: func() ([]byte, []byte) { + return []byte("foo"), nil + }, + }, + { + desc: "start is end", + test: func() ([]byte, []byte) { + full := []byte("hello world") + return full[1:3], full[1:3] + }, + expected: []byte("el"), + }, + { + desc: "end contained in start", + test: func() ([]byte, []byte) { + full := []byte("hello world") + return full[1:7], full[2:4] + }, + expected: []byte("ello w"), + }, + { + desc: "different backing arrays", + test: func() ([]byte, []byte) { + one := []byte("hello world") + two := []byte("hello world") + return one, two + }, + }, + } + + for _, e := range examples { + t.Run(e.desc, func(t *testing.T) { + start, end := e.test() + if e.expected == nil { + require.Panics(t, func() { + unsafe.BytesRange(start, end) + }) + } else { + res := unsafe.BytesRange(start, end) + require.Equal(t, e.expected, res) + } + }) + } +} diff --git a/strict.go b/strict.go new file mode 100644 index 00000000..236ad643 --- /dev/null +++ b/strict.go @@ -0,0 +1,79 @@ +package toml + +import ( + "github.com/pelletier/go-toml/v2/internal/ast" + "github.com/pelletier/go-toml/v2/internal/tracker" +) + +type strict struct { + Enabled bool + + // Tracks the current key being processed. + key tracker.KeyTracker + + missing []decodeError +} + +func (s *strict) EnterTable(node ast.Node) { + if !s.Enabled { + return + } + s.key.UpdateTable(node) +} + +func (s *strict) EnterArrayTable(node ast.Node) { + if !s.Enabled { + return + } + s.key.UpdateArrayTable(node) +} + +func (s *strict) EnterKeyValue(node ast.Node) { + if !s.Enabled { + return + } + s.key.Push(node) +} + +func (s *strict) ExitKeyValue(node ast.Node) { + if !s.Enabled { + return + } + s.key.Pop(node) +} + +func (s *strict) MissingTable(node ast.Node) { + if !s.Enabled { + return + } + s.missing = append(s.missing, decodeError{ + highlight: keyLocation(node), + message: "missing table", + key: s.key.Key(), + }) +} + +func (s *strict) MissingField(node ast.Node) { + if !s.Enabled { + return + } + s.missing = append(s.missing, decodeError{ + highlight: keyLocation(node), + message: "missing field", + key: s.key.Key(), + }) +} + +func (s *strict) Error(doc []byte) error { + if !s.Enabled || len(s.missing) == 0 { + return nil + } + + err := &StrictMissingError{ + Errors: make([]DecodeError, 0, len(s.missing)), + } + for _, derr := range s.missing { + err.Errors = append(err.Errors, *wrapDecodeError(doc, &derr)) + } + return err +} diff --git a/unmarshaler.go b/unmarshaler.go index 885717b4..f2956dd8 100644 --- a/unmarshaler.go +++ b/unmarshaler.go @@ -10,6 +10,7 @@ import ( "github.com/pelletier/go-toml/v2/internal/ast" "github.com/pelletier/go-toml/v2/internal/tracker" + "github.com/pelletier/go-toml/v2/internal/unsafe" ) func Unmarshal(data []byte, v interface{}) error { @@ -21,7 +22,11 @@ func Unmarshal(data []byte, v interface{}) error { // Decoder reads and decode a TOML document from an input stream. type Decoder struct { + // input r io.Reader + + // global settings + strict bool } // NewDecoder creates a new Decoder that will read from r. @@ -29,6 +34,16 @@ func NewDecoder(r io.Reader) *Decoder { return &Decoder{r: r} } +// SetStrict toggles decoding in stict mode. +// +// When the decoder is in strict mode, it will record fields from the document +// that could not be set on the target value. In that case, the decoder returns +// a StrictMissingError that can be used to retrieve the individual errors as +// well as generate a human readable description of the missing fields. +func (d *Decoder) SetStrict(strict bool) { + d.strict = strict +} + // Decode the whole content of r into v. // // When a TOML local date is decoded into a time.Time, its value is represented @@ -43,7 +58,11 @@ func (d *Decoder) Decode(v interface{}) error { } p := parser{} p.Reset(b) - dec := decoder{} + dec := decoder{ + strict: strict{ + Enabled: d.strict, + }, + } return dec.FromParser(&p, v) } @@ -52,7 +71,10 @@ type decoder struct { arrayIndexes map[reflect.Value]int // Tracks keys that have been seen, with which type. - seen tracker.Seen + seen tracker.SeenTracker + + // Strict mode + strict strict } func (d *decoder) arrayIndex(append bool, v reflect.Value) int { @@ -79,9 +101,27 @@ func (d *decoder) FromParser(p *parser, v interface{}) error { err = wrapDecodeError(p.data, de) } } + if err == nil { + err = d.strict.Error(p.data) + } + return err } +func keyLocation(node ast.Node) []byte { + k := node.Key() + hasOne := k.Next() + if !hasOne { + panic("should not be called with empty key") + } + start := k.Node().Data + end := k.Node().Data + for k.Next() { + end = k.Node().Data + } + return unsafe.BytesRange(start, end) +} + func (d *decoder) fromParser(p *parser, v interface{}) error { r := reflect.ValueOf(v) if r.Kind() != reflect.Ptr { @@ -113,6 +153,7 @@ func (d *decoder) fromParser(p *parser, v interface{}) error { err = d.unmarshalKeyValue(current, node) found = true case ast.Table: + d.strict.EnterTable(node) current, found, err = d.scopeWithKey(root, node.Key()) if err == nil && found { // In case this table points to an interface, @@ -123,6 +164,7 @@ func (d *decoder) fromParser(p *parser, v interface{}) error { ensureMapIfInterface(current) } case ast.ArrayTable: + d.strict.EnterArrayTable(node) current, found, err = d.scopeWithArrayTable(root, node.Key()) default: panic(fmt.Errorf("this should not be a top level node type: %s", node.Kind)) @@ -134,6 +176,7 @@ func (d *decoder) fromParser(p *parser, v interface{}) error { if !found { skipUntilTable = true + d.strict.MissingTable(node) } } @@ -217,6 +260,9 @@ func (d *decoder) scopeWithArrayTable(x target, key ast.Iterator) (target, bool, func (d *decoder) unmarshalKeyValue(x target, node ast.Node) error { assertNode(ast.KeyValue, node) + d.strict.EnterKeyValue(node) + defer d.strict.ExitKeyValue(node) + x, found, err := d.scopeWithKey(x, node.Key()) if err != nil { return err @@ -224,6 +270,7 @@ func (d *decoder) unmarshalKeyValue(x target, node ast.Node) error { // A struct in the path was not found. Skip this value. if !found { + d.strict.MissingField(node) return nil } diff --git a/unmarshaler_test.go b/unmarshaler_test.go index d2b7512d..dde75599 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -1,8 +1,10 @@ package toml_test import ( + "fmt" "math" "strconv" + "strings" "testing" "time" @@ -989,3 +991,115 @@ func TestIssue508(t *testing.T) { require.NoError(t, err) require.Equal(t, "This is a title", t1.head.Title) } + +func TestDecoderStrict(t *testing.T) { + examples := []struct { + desc string + input string + expected string + target interface{} + }{ + { + desc: "multiple missing root keys", + input: ` +key1 = "value1" +key2 = "missing2" +key3 = "missing3" +key4 = "value4" +`, + expected: ` +2| key1 = "value1" +3| key2 = "missing2" + | ~~~~ missing field +4| key3 = "missing3" +5| key4 = "value4" +--- +2| key1 = "value1" +3| key2 = "missing2" +4| key3 = "missing3" + | ~~~~ missing field +5| key4 = "value4" +`, + target: &struct { + Key1 string + Key4 string + }{}, + }, + { + desc: "multi-part key", + input: `a.short.key="foo"`, + expected: ` +1| a.short.key="foo" + | ~~~~~~~~~~~ missing field +`, + }, + { + desc: "missing table", + input: ` +[foo] +bar = 42 +`, + expected: ` +2| [foo] + | ~~~ missing table +3| bar = 42 +`, + }, + + { + desc: "missing array table", + input: ` +[[foo]] +bar = 42 +`, + expected: ` +2| [[foo]] + | ~~~ missing table +3| bar = 42 +`, + }, + } + + for _, e := range examples { + t.Run(e.desc, func(t *testing.T) { + r := strings.NewReader(e.input) + d := toml.NewDecoder(r) + d.SetStrict(true) + x := e.target + if x == nil { + x = &struct{}{} + } + err := d.Decode(x) + details := err.(*toml.StrictMissingError) + equalStringsIgnoreNewlines(t, e.expected, details.String()) + }) + } +} + +func ExampleDecoder_SetStrict() { + type S struct { + Key1 string + Key3 string + } + doc := ` +key1 = "value1" +key2 = "value2" +key3 = "value3" +` + r := strings.NewReader(doc) + d := toml.NewDecoder(r) + d.SetStrict(true) + s := S{} + err := d.Decode(&s) + + fmt.Println(err.Error()) + // Output: strict mode: fields in the document are missing in the target struct + + details := err.(*toml.StrictMissingError) + fmt.Println(details.String()) + // Ouput: + // 2| key1 = "value1" + // 3| key2 = "value2" + // | ~~~~ missing field + // 4| key3 = "value3" +} From ee102a3528797051def014df9a7ead9dd1fd5cc9 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Tue, 20 Apr 2021 23:16:08 -0400 Subject: [PATCH 178/228] decoder: fix time fractional parsing --- decode.go | 39 +- .../imported_tests/unmarshal_imported_test.go | 409 +++++++++--------- unmarshaler.go | 6 +- unmarshaler_test.go | 67 +++ 4 files changed, 307 insertions(+), 214 deletions(-) diff --git a/decode.go b/decode.go index 938587be..f4d27830 100644 --- a/decode.go +++ b/decode.go @@ -136,7 +136,7 @@ func parseDateTime(b []byte) (time.Time, error) { var ( errParseLocalDateTimeWrongLength = errors.New( - "local datetimes are expected to have the format YYYY-MM-DDTHH:MM:SS[.NNNNNN]", + "local datetimes are expected to have the format YYYY-MM-DDTHH:MM:SS[.NNNNNNNNN]", ) errParseLocalDateTimeWrongSeparator = errors.New("datetime separator is expected to be T or a space") ) @@ -144,8 +144,8 @@ var ( func parseLocalDateTime(b []byte) (LocalDateTime, []byte, error) { var dt LocalDateTime - const localDateTimeByteLen = 11 - if len(b) < localDateTimeByteLen { + const localDateTimeByteMinLen = 11 + if len(b) < localDateTimeByteMinLen { return dt, nil, errParseLocalDateTimeWrongLength } @@ -207,18 +207,41 @@ func parseLocalTime(b []byte) (LocalTime, []byte, error) { return t, nil, err } - if len(b) >= 15 && b[8] == '.' { - t.Nanosecond, err = parseDecimalDigits(b[9:15]) - if err != nil { - return t, nil, err + if len(b) >= 9 && b[8] == '.' { + frac := 0 + digits := 0 + + for i, c := range b[9:] { + if !isDigit(c) { + if i == 0 { + return t, nil, newDecodeError(b[i:i+1], "need at least one digit after fraction point") + } + + break + } + if i >= 9 { + return t, nil, newDecodeError(b[i:i+1], "maximum precision for date time is nanosecond") + } + + frac *= 10 + frac += int(c - '0') + digits++ } - return t, b[15:], nil + t.Nanosecond = frac * nanosecPower(digits) + + return t, b[9+digits:], nil } return t, b[8:], nil } +var nspow = []int{0, 1e8, 1e7, 1e6, 1e5, 1e4, 1e3, 1e2, 1e1, 1e0} + +func nanosecPower(n int) int { + return nspow[n] +} + var ( errParseFloatStartDot = errors.New("float cannot start with a dot") errParseFloatEndDot = errors.New("float cannot end with a dot") diff --git a/internal/imported_tests/unmarshal_imported_test.go b/internal/imported_tests/unmarshal_imported_test.go index c641a2a0..8dbfec45 100644 --- a/internal/imported_tests/unmarshal_imported_test.go +++ b/internal/imported_tests/unmarshal_imported_test.go @@ -1430,211 +1430,210 @@ func TestUnmarshalPreservesUnexportedFields(t *testing.T) { }) } -// -//func TestUnmarshalLocalDate(t *testing.T) { -// t.Run("ToLocalDate", func(t *testing.T) { -// type dateStruct struct { -// Date toml.LocalDate -// } -// -// doc := `date = 1979-05-27` -// -// var obj dateStruct -// -// err := toml.Unmarshal([]byte(doc), &obj) -// -// if err != nil { -// t.Fatal(err) -// } -// -// if obj.Date.Year != 1979 { -// t.Errorf("expected year 1979, got %d", obj.Date.Year) -// } -// if obj.Date.Month != 5 { -// t.Errorf("expected month 5, got %d", obj.Date.Month) -// } -// if obj.Date.Day != 27 { -// t.Errorf("expected day 27, got %d", obj.Date.Day) -// } -// }) -// -// t.Run("ToLocalDate", func(t *testing.T) { -// type dateStruct struct { -// Date time.Time -// } -// -// doc := `date = 1979-05-27` -// -// var obj dateStruct -// -// err := toml.Unmarshal([]byte(doc), &obj) -// -// if err != nil { -// t.Fatal(err) -// } -// -// if obj.Date.Year() != 1979 { -// t.Errorf("expected year 1979, got %d", obj.Date.Year()) -// } -// if obj.Date.Month() != 5 { -// t.Errorf("expected month 5, got %d", obj.Date.Month()) -// } -// if obj.Date.Day() != 27 { -// t.Errorf("expected day 27, got %d", obj.Date.Day()) -// } -// }) -//} -// -//func TestUnmarshalLocalDateTime(t *testing.T) { -// examples := []struct { -// name string -// in string -// out toml.LocalDateTime -// }{ -// { -// name: "normal", -// in: "1979-05-27T07:32:00", -// out: toml.LocalDateTime{ -// Date: toml.LocalDate{ -// Year: 1979, -// Month: 5, -// Day: 27, -// }, -// Time: toml.LocalTime{ -// Hour: 7, -// Minute: 32, -// Second: 0, -// Nanosecond: 0, -// }, -// }}, -// { -// name: "with nanoseconds", -// in: "1979-05-27T00:32:00.999999", -// out: toml.LocalDateTime{ -// Date: toml.LocalDate{ -// Year: 1979, -// Month: 5, -// Day: 27, -// }, -// Time: toml.LocalTime{ -// Hour: 0, -// Minute: 32, -// Second: 0, -// Nanosecond: 999999000, -// }, -// }, -// }, -// } -// -// for i, example := range examples { -// doc := fmt.Sprintf(`date = %s`, example.in) -// -// t.Run(fmt.Sprintf("ToLocalDateTime_%d_%s", i, example.name), func(t *testing.T) { -// type dateStruct struct { -// Date toml.LocalDateTime -// } -// -// var obj dateStruct -// -// err := toml.Unmarshal([]byte(doc), &obj) -// -// if err != nil { -// t.Fatal(err) -// } -// -// if obj.Date != example.out { -// t.Errorf("expected '%s', got '%s'", example.out, obj.Date) -// } -// }) -// -// t.Run(fmt.Sprintf("ToTime_%d_%s", i, example.name), func(t *testing.T) { -// type dateStruct struct { -// Date time.Time -// } -// -// var obj dateStruct -// -// err := toml.Unmarshal([]byte(doc), &obj) -// -// if err != nil { -// t.Fatal(err) -// } -// -// if obj.Date.Year() != example.out.Date.Year { -// t.Errorf("expected year %d, got %d", example.out.Date.Year, obj.Date.Year()) -// } -// if obj.Date.Month() != example.out.Date.Month { -// t.Errorf("expected month %d, got %d", example.out.Date.Month, obj.Date.Month()) -// } -// if obj.Date.Day() != example.out.Date.Day { -// t.Errorf("expected day %d, got %d", example.out.Date.Day, obj.Date.Day()) -// } -// if obj.Date.Hour() != example.out.Time.Hour { -// t.Errorf("expected hour %d, got %d", example.out.Time.Hour, obj.Date.Hour()) -// } -// if obj.Date.Minute() != example.out.Time.Minute { -// t.Errorf("expected minute %d, got %d", example.out.Time.Minute, obj.Date.Minute()) -// } -// if obj.Date.Second() != example.out.Time.Second { -// t.Errorf("expected second %d, got %d", example.out.Time.Second, obj.Date.Second()) -// } -// if obj.Date.Nanosecond() != example.out.Time.Nanosecond { -// t.Errorf("expected nanoseconds %d, got %d", example.out.Time.Nanosecond, obj.Date.Nanosecond()) -// } -// }) -// } -//} -// -//func TestUnmarshalLocalTime(t *testing.T) { -// examples := []struct { -// name string -// in string -// out toml.LocalTime -// }{ -// { -// name: "normal", -// in: "07:32:00", -// out: toml.LocalTime{ -// Hour: 7, -// Minute: 32, -// Second: 0, -// Nanosecond: 0, -// }, -// }, -// { -// name: "with nanoseconds", -// in: "00:32:00.999999", -// out: toml.LocalTime{ -// Hour: 0, -// Minute: 32, -// Second: 0, -// Nanosecond: 999999000, -// }, -// }, -// } -// -// for i, example := range examples { -// doc := fmt.Sprintf(`Time = %s`, example.in) -// -// t.Run(fmt.Sprintf("ToLocalTime_%d_%s", i, example.name), func(t *testing.T) { -// type dateStruct struct { -// Time toml.LocalTime -// } -// -// var obj dateStruct -// -// err := toml.Unmarshal([]byte(doc), &obj) -// -// if err != nil { -// t.Fatal(err) -// } -// -// if obj.Time != example.out { -// t.Errorf("expected '%s', got '%s'", example.out, obj.Time) -// } -// }) -// } -//} +func TestUnmarshalLocalDate(t *testing.T) { + t.Run("ToLocalDate", func(t *testing.T) { + type dateStruct struct { + Date toml.LocalDate + } + + doc := `date = 1979-05-27` + + var obj dateStruct + + err := toml.Unmarshal([]byte(doc), &obj) + + if err != nil { + t.Fatal(err) + } + + if obj.Date.Year != 1979 { + t.Errorf("expected year 1979, got %d", obj.Date.Year) + } + if obj.Date.Month != 5 { + t.Errorf("expected month 5, got %d", obj.Date.Month) + } + if obj.Date.Day != 27 { + t.Errorf("expected day 27, got %d", obj.Date.Day) + } + }) + + t.Run("ToLocalDate", func(t *testing.T) { + type dateStruct struct { + Date time.Time + } + + doc := `date = 1979-05-27` + + var obj dateStruct + + err := toml.Unmarshal([]byte(doc), &obj) + + if err != nil { + t.Fatal(err) + } + + if obj.Date.Year() != 1979 { + t.Errorf("expected year 1979, got %d", obj.Date.Year()) + } + if obj.Date.Month() != 5 { + t.Errorf("expected month 5, got %d", obj.Date.Month()) + } + if obj.Date.Day() != 27 { + t.Errorf("expected day 27, got %d", obj.Date.Day()) + } + }) +} + +func TestUnmarshalLocalDateTime(t *testing.T) { + examples := []struct { + name string + in string + out toml.LocalDateTime + }{ + { + name: "normal", + in: "1979-05-27T07:32:00", + out: toml.LocalDateTime{ + Date: toml.LocalDate{ + Year: 1979, + Month: 5, + Day: 27, + }, + Time: toml.LocalTime{ + Hour: 7, + Minute: 32, + Second: 0, + Nanosecond: 0, + }, + }}, + { + name: "with nanoseconds", + in: "1979-05-27T00:32:00.999999", + out: toml.LocalDateTime{ + Date: toml.LocalDate{ + Year: 1979, + Month: 5, + Day: 27, + }, + Time: toml.LocalTime{ + Hour: 0, + Minute: 32, + Second: 0, + Nanosecond: 999999000, + }, + }, + }, + } + + for i, example := range examples { + doc := fmt.Sprintf(`date = %s`, example.in) + + t.Run(fmt.Sprintf("ToLocalDateTime_%d_%s", i, example.name), func(t *testing.T) { + type dateStruct struct { + Date toml.LocalDateTime + } + + var obj dateStruct + + err := toml.Unmarshal([]byte(doc), &obj) + + if err != nil { + t.Fatal(err) + } + + if obj.Date != example.out { + t.Errorf("expected '%s', got '%s'", example.out, obj.Date) + } + }) + + t.Run(fmt.Sprintf("ToTime_%d_%s", i, example.name), func(t *testing.T) { + type dateStruct struct { + Date time.Time + } + + var obj dateStruct + + err := toml.Unmarshal([]byte(doc), &obj) + + if err != nil { + t.Fatal(err) + } + + if obj.Date.Year() != example.out.Date.Year { + t.Errorf("expected year %d, got %d", example.out.Date.Year, obj.Date.Year()) + } + if obj.Date.Month() != example.out.Date.Month { + t.Errorf("expected month %d, got %d", example.out.Date.Month, obj.Date.Month()) + } + if obj.Date.Day() != example.out.Date.Day { + t.Errorf("expected day %d, got %d", example.out.Date.Day, obj.Date.Day()) + } + if obj.Date.Hour() != example.out.Time.Hour { + t.Errorf("expected hour %d, got %d", example.out.Time.Hour, obj.Date.Hour()) + } + if obj.Date.Minute() != example.out.Time.Minute { + t.Errorf("expected minute %d, got %d", example.out.Time.Minute, obj.Date.Minute()) + } + if obj.Date.Second() != example.out.Time.Second { + t.Errorf("expected second %d, got %d", example.out.Time.Second, obj.Date.Second()) + } + if obj.Date.Nanosecond() != example.out.Time.Nanosecond { + t.Errorf("expected nanoseconds %d, got %d", example.out.Time.Nanosecond, obj.Date.Nanosecond()) + } + }) + } +} + +func TestUnmarshalLocalTime(t *testing.T) { + examples := []struct { + name string + in string + out toml.LocalTime + }{ + { + name: "normal", + in: "07:32:00", + out: toml.LocalTime{ + Hour: 7, + Minute: 32, + Second: 0, + Nanosecond: 0, + }, + }, + { + name: "with nanoseconds", + in: "00:32:00.999999", + out: toml.LocalTime{ + Hour: 0, + Minute: 32, + Second: 0, + Nanosecond: 999999000, + }, + }, + } + + for i, example := range examples { + doc := fmt.Sprintf(`Time = %s`, example.in) + + t.Run(fmt.Sprintf("ToLocalTime_%d_%s", i, example.name), func(t *testing.T) { + type dateStruct struct { + Time toml.LocalTime + } + + var obj dateStruct + + err := toml.Unmarshal([]byte(doc), &obj) + + if err != nil { + t.Fatal(err) + } + + if obj.Time != example.out { + t.Errorf("expected '%s', got '%s'", example.out, obj.Time) + } + }) + } +} // test case for issue #339 func TestUnmarshalSameInnerField(t *testing.T) { diff --git a/unmarshaler.go b/unmarshaler.go index f2956dd8..b4d5954b 100644 --- a/unmarshaler.go +++ b/unmarshaler.go @@ -286,7 +286,7 @@ func tryTextUnmarshaler(x target, node ast.Node) (bool, error) { return false, nil } - // Special case for time, becase we allow to unmarshal to it from + // Special case for time, because we allow to unmarshal to it from // different kind of AST nodes. if v.Type() == timeType { return false, nil @@ -374,6 +374,10 @@ func unmarshalDateTime(x target, node ast.Node) error { } func setLocalDateTime(x target, v LocalDateTime) error { + if x.get().Type() == timeType { + cast := v.In(time.Local) + return setDateTime(x, cast) + } return x.set(reflect.ValueOf(v)) } diff --git a/unmarshaler_test.go b/unmarshaler_test.go index dde75599..7d1bd07d 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -960,6 +960,73 @@ world'`, } } +func TestLocalDateTime(t *testing.T) { + t.Parallel() + + examples := []struct { + desc string + input string + }{ + { + desc: "9 digits", + input: "2006-01-02T15:04:05.123456789", + }, + { + desc: "8 digits", + input: "2006-01-02T15:04:05.12345678", + }, + { + desc: "7 digits", + input: "2006-01-02T15:04:05.1234567", + }, + { + desc: "6 digits", + input: "2006-01-02T15:04:05.123456", + }, + { + desc: "5 digits", + input: "2006-01-02T15:04:05.12345", + }, + { + desc: "4 digits", + input: "2006-01-02T15:04:05.1234", + }, + { + desc: "3 digits", + input: "2006-01-02T15:04:05.123", + }, + { + desc: "2 digits", + input: "2006-01-02T15:04:05.12", + }, + { + desc: "1 digit", + input: "2006-01-02T15:04:05.1", + }, + { + desc: "0 digit", + input: "2006-01-02T15:04:05", + }, + } + + for _, e := range examples { + e := e + t.Run(e.desc, func(t *testing.T) { + t.Parallel() + t.Log("input:", e.input) + doc := `a = ` + e.input + m := map[string]toml.LocalDateTime{} + err := toml.Unmarshal([]byte(doc), &m) + require.NoError(t, err) + actual := m["a"] + golang, err := time.Parse("2006-01-02T15:04:05.999999999", e.input) + require.NoError(t, err) + expected := toml.LocalDateTimeOf(golang) + require.Equal(t, expected, actual) + }) + } +} + func TestIssue287(t *testing.T) { b := `y=[[{}]]` v := map[string]interface{}{} From 32c1a8d372fef15478658e7c056bd7fe1fbc5857 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Tue, 20 Apr 2021 23:19:40 -0400 Subject: [PATCH 179/228] encoder: move nspow into the parseLocalTime --- decode.go | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/decode.go b/decode.go index f4d27830..bd6b7216 100644 --- a/decode.go +++ b/decode.go @@ -175,6 +175,7 @@ var errParseLocalTimeWrongLength = errors.New("times are expected to have the fo // []byte that is didn't need. This is to allow parseDateTime to parse those // remaining bytes as a timezone. func parseLocalTime(b []byte) (LocalTime, []byte, error) { + var nspow = [10]int{0, 1e8, 1e7, 1e6, 1e5, 1e4, 1e3, 1e2, 1e1, 1e0} var t LocalTime const localTimeByteLen = 8 @@ -228,7 +229,7 @@ func parseLocalTime(b []byte) (LocalTime, []byte, error) { digits++ } - t.Nanosecond = frac * nanosecPower(digits) + t.Nanosecond = frac * nspow[digits] return t, b[9+digits:], nil } @@ -236,12 +237,6 @@ func parseLocalTime(b []byte) (LocalTime, []byte, error) { return t, b[8:], nil } -var nspow = []int{0, 1e8, 1e7, 1e6, 1e5, 1e4, 1e3, 1e2, 1e1, 1e0} - -func nanosecPower(n int) int { - return nspow[n] -} - var ( errParseFloatStartDot = errors.New("float cannot start with a dot") errParseFloatEndDot = errors.New("float cannot end with a dot") From 6fe332a869e0b99e007895dd86b218b8f8cf1c14 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Wed, 21 Apr 2021 19:11:15 -0400 Subject: [PATCH 180/228] Encoder inline tables (#519) --- marshaler.go | 146 ++++++++++++++++++++++++++-------------------- marshaler_test.go | 52 +++++++++++++++-- 2 files changed, 130 insertions(+), 68 deletions(-) diff --git a/marshaler.go b/marshaler.go index bbc3130c..31531089 100644 --- a/marshaler.go +++ b/marshaler.go @@ -29,47 +29,11 @@ func Marshal(v interface{}) ([]byte, error) { // Encoder writes a TOML document to an output stream. type Encoder struct { + // output w io.Writer -} - -type encoderCtx struct { - // Current top-level key. - parentKey []string - - // Key that should be used for a KV. - key string - // Extra flag to account for the empty string - hasKey bool - - // Set to true to indicate that the encoder is inside a KV, so that all - // tables need to be inlined. - insideKv bool - - // Set to true to skip the first table header in an array table. - skipTableHeader bool - - options valueOptions -} -type valueOptions struct { - multiline bool -} - -func (ctx *encoderCtx) shiftKey() { - if ctx.hasKey { - ctx.parentKey = append(ctx.parentKey, ctx.key) - ctx.clearKey() - } -} - -func (ctx *encoderCtx) setKey(k string) { - ctx.key = k - ctx.hasKey = true -} - -func (ctx *encoderCtx) clearKey() { - ctx.key = "" - ctx.hasKey = false + // global settings + tablesInline bool } // NewEncoder returns a new Encoder that writes to w. @@ -79,6 +43,11 @@ func NewEncoder(w io.Writer) *Encoder { } } +// SetTablesInline forces the encoder to emit all tables inline. +func (e *Encoder) SetTablesInline(inline bool) { + e.tablesInline = inline +} + // Encode writes a TOML representation of v to the stream. // // If v cannot be represented to TOML it returns an error. @@ -114,6 +83,8 @@ func (enc *Encoder) Encode(v interface{}) error { ctx encoderCtx ) + ctx.inline = enc.tablesInline + b, err := enc.encode(b, ctx, reflect.ValueOf(v)) if err != nil { return fmt.Errorf("Encode: %w", err) @@ -127,6 +98,53 @@ func (enc *Encoder) Encode(v interface{}) error { return nil } +type valueOptions struct { + multiline bool +} + +type encoderCtx struct { + // Current top-level key. + parentKey []string + + // Key that should be used for a KV. + key string + // Extra flag to account for the empty string + hasKey bool + + // Set to true to indicate that the encoder is inside a KV, so that all + // tables need to be inlined. + insideKv bool + + // Set to true to skip the first table header in an array table. + skipTableHeader bool + + // Should the next table be encoded as inline + inline bool + + options valueOptions +} + +func (ctx *encoderCtx) shiftKey() { + if ctx.hasKey { + ctx.parentKey = append(ctx.parentKey, ctx.key) + ctx.clearKey() + } +} + +func (ctx *encoderCtx) setKey(k string) { + ctx.key = k + ctx.hasKey = true +} + +func (ctx *encoderCtx) clearKey() { + ctx.key = "" + ctx.hasKey = false +} + +func (ctx *encoderCtx) isRoot() bool { + return len(ctx.parentKey) == 0 && !ctx.hasKey +} + var errUnsupportedValue = errors.New("unsupported encode value kind") //nolint:cyclop @@ -396,7 +414,7 @@ func (enc *Encoder) encodeMap(b []byte, ctx encoderCtx, v reflect.Value) ([]byte continue } - table, err := willConvertToTableOrArrayTable(v) + table, err := willConvertToTableOrArrayTable(ctx, v) if err != nil { return nil, err } @@ -469,35 +487,39 @@ func (enc *Encoder) encodeStruct(b []byte, ctx encoderCtx, v reflect.Value) ([]b continue } - willConvert, err := willConvertToTableOrArrayTable(f) + willConvert, err := willConvertToTableOrArrayTable(ctx, f) if err != nil { return nil, err } - var options valueOptions - - ml, ok := fieldType.Tag.Lookup("multiline") - if ok { - options.multiline = ml == "true" + options := valueOptions{ + multiline: fieldBoolTag(fieldType, "multiline"), } - if willConvert { - t.pushTable(k, f, options) - } else { + inline := fieldBoolTag(fieldType, "inline") + + if inline || !willConvert { t.pushKV(k, f, options) + } else { + t.pushTable(k, f, options) } } return enc.encodeTable(b, ctx, t) } +func fieldBoolTag(field reflect.StructField, tag string) bool { + x, ok := field.Tag.Lookup(tag) + return ok && x == "true" +} + func (enc *Encoder) encodeTable(b []byte, ctx encoderCtx, t table) ([]byte, error) { var err error ctx.shiftKey() - if ctx.insideKv { - return enc.encodeTableInsideKV(b, ctx, t) + if ctx.insideKv || (ctx.inline && !ctx.isRoot()) { + return enc.encodeTableInline(b, ctx, t) } if !ctx.skipTableHeader { @@ -533,7 +555,7 @@ func (enc *Encoder) encodeTable(b []byte, ctx encoderCtx, t table) ([]byte, erro return b, nil } -func (enc *Encoder) encodeTableInsideKV(b []byte, ctx encoderCtx, t table) ([]byte, error) { +func (enc *Encoder) encodeTableInline(b []byte, ctx encoderCtx, t table) ([]byte, error) { var err error b = append(b, '{') @@ -571,14 +593,14 @@ func (enc *Encoder) encodeTableInsideKV(b []byte, ctx encoderCtx, t table) ([]by b = append(b, '\n') } - b = append(b, "}\n"...) + b = append(b, "}"...) return b, nil } var errNilInterface = errors.New("nil interface not supported") -func willConvertToTable(v reflect.Value) (bool, error) { +func willConvertToTable(ctx encoderCtx, v reflect.Value) (bool, error) { //nolint:gocritic,godox switch v.Interface().(type) { case time.Time: // TODO: add TextMarshaler @@ -588,25 +610,25 @@ func willConvertToTable(v reflect.Value) (bool, error) { t := v.Type() switch t.Kind() { case reflect.Map, reflect.Struct: - return true, nil + return !ctx.inline, nil case reflect.Interface: if v.IsNil() { return false, errNilInterface } - return willConvertToTable(v.Elem()) + return willConvertToTable(ctx, v.Elem()) case reflect.Ptr: if v.IsNil() { return false, nil } - return willConvertToTable(v.Elem()) + return willConvertToTable(ctx, v.Elem()) default: return false, nil } } -func willConvertToTableOrArrayTable(v reflect.Value) (bool, error) { +func willConvertToTableOrArrayTable(ctx encoderCtx, v reflect.Value) (bool, error) { t := v.Type() if t.Kind() == reflect.Interface { @@ -614,7 +636,7 @@ func willConvertToTableOrArrayTable(v reflect.Value) (bool, error) { return false, errNilInterface } - return willConvertToTableOrArrayTable(v.Elem()) + return willConvertToTableOrArrayTable(ctx, v.Elem()) } if t.Kind() == reflect.Slice { @@ -624,7 +646,7 @@ func willConvertToTableOrArrayTable(v reflect.Value) (bool, error) { } for i := 0; i < v.Len(); i++ { - t, err := willConvertToTable(v.Index(i)) + t, err := willConvertToTable(ctx, v.Index(i)) if err != nil { return false, err } @@ -637,7 +659,7 @@ func willConvertToTableOrArrayTable(v reflect.Value) (bool, error) { return true, nil } - return willConvertToTable(v) + return willConvertToTable(ctx, v) } func (enc *Encoder) encodeSlice(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) { @@ -647,7 +669,7 @@ func (enc *Encoder) encodeSlice(b []byte, ctx encoderCtx, v reflect.Value) ([]by return b, nil } - allTables, err := willConvertToTableOrArrayTable(v) + allTables, err := willConvertToTableOrArrayTable(ctx, v) if err != nil { return nil, err } diff --git a/marshaler_test.go b/marshaler_test.go index b1447665..3304f174 100644 --- a/marshaler_test.go +++ b/marshaler_test.go @@ -68,9 +68,6 @@ hello = 'world'`, a = 'test'`, }, { - //nolint:godox - // TODO: this test is flaky because output changes depending on - // the map iteration order. desc: "map in map in map and string with values", v: map[string]interface{}{ "this": map[string]interface{}{ @@ -248,6 +245,25 @@ name = 'Alice' hello world"""`, }, + { + desc: "inline field", + v: struct { + A map[string]string `inline:"true"` + B map[string]string + }{ + A: map[string]string{ + "isinline": "yes", + }, + B: map[string]string{ + "isinline": "no", + }, + }, + expected: ` +A = {isinline = 'yes'} +[B] +isinline = 'no' +`, + }, } for _, e := range examples { @@ -258,10 +274,34 @@ world"""`, b, err := toml.Marshal(e.v) if e.err { require.Error(t, err) - } else { - require.NoError(t, err) - equalStringsIgnoreNewlines(t, e.expected, string(b)) + return } + + require.NoError(t, err) + equalStringsIgnoreNewlines(t, e.expected, string(b)) + + // make sure the output is always valid TOML + defaultMap := map[string]interface{}{} + err = toml.Unmarshal(b, &defaultMap) + require.NoError(t, err) + + // checks that the TablesInline mode generates valid, + // equivalent TOML + t.Run("tables inline", func(t *testing.T) { + var buf bytes.Buffer + + enc := toml.NewEncoder(&buf) + enc.SetTablesInline(true) + + err := enc.Encode(e.v) + require.NoError(t, err) + + inlineMap := map[string]interface{}{} + err = toml.Unmarshal(buf.Bytes(), &inlineMap) + require.NoError(t, err) + + require.Equal(t, defaultMap, inlineMap) + }) }) } } From 9ba52996d8336c93a92b8021ae6ee3a3bbbba142 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Wed, 21 Apr 2021 22:13:45 -0400 Subject: [PATCH 181/228] Encoder multiline array (#520) --- marshaler.go | 59 ++++++++++++++++++++++++++++---- marshaler_test.go | 85 ++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 133 insertions(+), 11 deletions(-) diff --git a/marshaler.go b/marshaler.go index 31531089..24910152 100644 --- a/marshaler.go +++ b/marshaler.go @@ -33,21 +33,36 @@ type Encoder struct { w io.Writer // global settings - tablesInline bool + tablesInline bool + arraysMultiline bool + indentSymbol string } // NewEncoder returns a new Encoder that writes to w. func NewEncoder(w io.Writer) *Encoder { return &Encoder{ - w: w, + w: w, + indentSymbol: " ", } } // SetTablesInline forces the encoder to emit all tables inline. +// +// This behavior can be controled on an individual struct field basis with the +// `inline="true"` tag. func (e *Encoder) SetTablesInline(inline bool) { e.tablesInline = inline } +// SetArraysMultiline forces the encoder to emit all arrays with one element per +// line. +// +// This behavior can be controled on an individual struct field basis with the +// `multiline="true"` tag. +func (e *Encoder) SetArraysMultiline(multiline bool) { + e.arraysMultiline = multiline +} + // Encode writes a TOML representation of v to the stream. // // If v cannot be represented to TOML it returns an error. @@ -121,6 +136,10 @@ type encoderCtx struct { // Should the next table be encoded as inline inline bool + // Indentation level + indent int + + // Options coming from struct tags options valueOptions } @@ -544,6 +563,7 @@ func (enc *Encoder) encodeTable(b []byte, ctx encoderCtx, t table) ([]byte, erro for _, table := range t.tables { ctx.setKey(table.Key) + ctx.options = table.Options b, err = enc.encode(b, ctx, table.Value) if err != nil { return nil, err @@ -721,25 +741,52 @@ func (enc *Encoder) encodeSliceAsArrayTable(b []byte, ctx encoderCtx, v reflect. } func (enc *Encoder) encodeSliceAsArray(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) { + multiline := ctx.options.multiline || enc.arraysMultiline + separator := ", " + b = append(b, '[') + subCtx := ctx + subCtx.options = valueOptions{} + + if multiline { + separator = ",\n" + b = append(b, '\n') + subCtx.indent++ + } + var err error first := true for i := 0; i < v.Len(); i++ { - if !first { - b = append(b, ", "...) + if first { + first = false + } else { + b = append(b, separator...) } - first = false + if multiline { + b = enc.indent(subCtx.indent, b) + } - b, err = enc.encode(b, ctx, v.Index(i)) + b, err = enc.encode(b, subCtx, v.Index(i)) if err != nil { return nil, err } } + if multiline { + b = append(b, '\n') + b = enc.indent(ctx.indent, b) + } b = append(b, ']') return b, nil } + +func (enc *Encoder) indent(level int, b []byte) []byte { + for i := 0; i < level; i++ { + b = append(b, enc.indentSymbol...) + } + return b +} diff --git a/marshaler_test.go b/marshaler_test.go index 3304f174..646de244 100644 --- a/marshaler_test.go +++ b/marshaler_test.go @@ -3,6 +3,7 @@ package toml_test import ( "bytes" "encoding/json" + "fmt" "strings" "testing" @@ -262,6 +263,39 @@ world"""`, A = {isinline = 'yes'} [B] isinline = 'no' +`, + }, + { + desc: "mutiline array int", + v: struct { + A []int `multiline:"true"` + B []int + }{ + A: []int{1, 2, 3, 4}, + B: []int{1, 2, 3, 4}, + }, + expected: ` +A = [ + 1, + 2, + 3, + 4 +] +B = [1, 2, 3, 4] +`, + }, + { + desc: "mutiline array in array", + v: struct { + A [][]int `multiline:"true"` + }{ + A: [][]int{{1, 2}, {3, 4}}, + }, + expected: ` +A = [ + [1, 2], + [3, 4] +] `, }, } @@ -285,13 +319,10 @@ isinline = 'no' err = toml.Unmarshal(b, &defaultMap) require.NoError(t, err) - // checks that the TablesInline mode generates valid, - // equivalent TOML - t.Run("tables inline", func(t *testing.T) { + testWithAllFlags(t, func(t *testing.T, flags int) { var buf bytes.Buffer - enc := toml.NewEncoder(&buf) - enc.SetTablesInline(true) + setFlags(enc, flags) err := enc.Encode(e.v) require.NoError(t, err) @@ -306,6 +337,50 @@ isinline = 'no' } } +type flagsSetters []struct { + name string + f func(enc *toml.Encoder, flag bool) +} + +var allFlags = flagsSetters{ + {"arrays-multiline", (*toml.Encoder).SetArraysMultiline}, + {"tables-inline", (*toml.Encoder).SetTablesInline}, +} + +func setFlags(enc *toml.Encoder, flags int) { + for i := 0; i < len(allFlags); i++ { + enabled := flags&1 > 0 + allFlags[i].f(enc, enabled) + } +} + +func testWithAllFlags(t *testing.T, testfn func(t *testing.T, flags int)) { + t.Helper() + testWithFlags(t, 0, allFlags, testfn) +} + +func testWithFlags(t *testing.T, flags int, setters flagsSetters, testfn func(t *testing.T, flags int)) { + t.Helper() + + if len(setters) == 0 { + testfn(t, flags) + return + } + + s := setters[0] + + for _, enabled := range []bool{false, true} { + name := fmt.Sprintf("%s=%t", s.name, enabled) + newFlags := flags << 1 + if enabled { + newFlags++ + } + t.Run(name, func(t *testing.T) { + testWithFlags(t, newFlags, setters[1:], testfn) + }) + } +} + func equalStringsIgnoreNewlines(t *testing.T, expected string, actual string) { t.Helper() cutset := "\n" From 21445f51702aabd46132374c38c08eb832063e68 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Wed, 21 Apr 2021 22:27:30 -0400 Subject: [PATCH 182/228] Add test for issue #424 --- marshaler_test.go | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/marshaler_test.go b/marshaler_test.go index 646de244..e0a349f0 100644 --- a/marshaler_test.go +++ b/marshaler_test.go @@ -407,3 +407,32 @@ c = 'd' ` equalStringsIgnoreNewlines(t, expected, buf.String()) } + +func TestIssue424(t *testing.T) { + type Message1 struct { + Text string + } + + type Message2 struct { + Text string `multiline:"true"` + } + + msg1 := Message1{"Hello\\World"} + msg2 := Message2{"Hello\\World"} + + toml1, err := toml.Marshal(msg1) + require.NoError(t, err) + + toml2, err := toml.Marshal(msg2) + require.NoError(t, err) + + msg1parsed := Message1{} + err = toml.Unmarshal(toml1, &msg1parsed) + require.NoError(t, err) + require.Equal(t, msg1, msg1parsed) + + msg2parsed := Message2{} + err = toml.Unmarshal(toml2, &msg2parsed) + require.NoError(t, err) + require.Equal(t, msg2, msg2parsed) +} From 2b1c52dddd19ec1099d98464c1f79456301fc507 Mon Sep 17 00:00:00 2001 From: Vincent Serpoul Date: Thu, 22 Apr 2021 21:29:23 +0800 Subject: [PATCH 183/228] golangci-lint: decoder/unmarshal (#518) --- .golangci.toml | 2 +- decode.go | 9 +- errors.go | 7 +- localtime_test.go | 29 +++-- parser.go | 317 ++++++++++++++++++++++++++++++---------------- parser_test.go | 64 ++-------- 6 files changed, 252 insertions(+), 176 deletions(-) diff --git a/.golangci.toml b/.golangci.toml index b27872a7..3d338e1e 100644 --- a/.golangci.toml +++ b/.golangci.toml @@ -21,7 +21,7 @@ enable = [ "errcheck", "errorlint", "exhaustive", - "exhaustivestruct", + # "exhaustivestruct", "exportloopref", "forbidigo", "forcetypeassert", diff --git a/decode.go b/decode.go index bd6b7216..87330a78 100644 --- a/decode.go +++ b/decode.go @@ -174,9 +174,12 @@ var errParseLocalTimeWrongLength = errors.New("times are expected to have the fo // parseLocalTime is a bit different because it also returns the remaining // []byte that is didn't need. This is to allow parseDateTime to parse those // remaining bytes as a timezone. +//nolint:cyclop,funlen func parseLocalTime(b []byte) (LocalTime, []byte, error) { - var nspow = [10]int{0, 1e8, 1e7, 1e6, 1e5, 1e4, 1e3, 1e2, 1e1, 1e0} - var t LocalTime + var ( + nspow = [10]int{0, 1e8, 1e7, 1e6, 1e5, 1e4, 1e3, 1e2, 1e1, 1e0} + t LocalTime + ) const localTimeByteLen = 8 if len(b) < localTimeByteLen { @@ -220,6 +223,8 @@ func parseLocalTime(b []byte) (LocalTime, []byte, error) { break } + + //nolint:gomnd if i >= 9 { return t, nil, newDecodeError(b[i:i+1], "maximum precision for date time is nanosecond") } diff --git a/errors.go b/errors.go index 39446dcb..7773796b 100644 --- a/errors.go +++ b/errors.go @@ -33,7 +33,7 @@ type StrictMissingError struct { Errors []DecodeError } -// Error returns the cannonical string for this error. +// Error returns the canonical string for this error. func (s *StrictMissingError) Error() string { return "strict mode: fields in the document are missing in the target struct" } @@ -41,12 +41,15 @@ func (s *StrictMissingError) Error() string { // String returns a human readable description of all errors. func (s *StrictMissingError) String() string { var buf strings.Builder + for i, e := range s.Errors { if i > 0 { buf.WriteString("\n---\n") } + buf.WriteString(e.String()) } + return buf.String() } @@ -87,7 +90,7 @@ func (e *DecodeError) Position() (row int, column int) { return e.line, e.column } -// Key that was being processed when the error occured. +// Key that was being processed when the error occurred. func (e *DecodeError) Key() Key { return e.key } diff --git a/localtime_test.go b/localtime_test.go index a3275777..a103bc06 100644 --- a/localtime_test.go +++ b/localtime_test.go @@ -293,9 +293,9 @@ func TestDateTimeToString(t *testing.T) { dateTime LocalDateTime roundTrip bool // ParseLocalDateTime(str).String() == str? }{ - {"2016-03-22T13:26:33", LocalDateTime{LocalDate{2016, 03, 22}, LocalTime{13, 26, 33, 0}}, true}, - {"2016-03-22T13:26:33.000000600", LocalDateTime{LocalDate{2016, 03, 22}, LocalTime{13, 26, 33, 600}}, true}, - {"2016-03-22t13:26:33", LocalDateTime{LocalDate{2016, 03, 22}, LocalTime{13, 26, 33, 0}}, false}, + {"2016-03-22T13:26:33", LocalDateTime{LocalDate{2016, 3, 22}, LocalTime{13, 26, 33, 0}}, true}, + {"2016-03-22T13:26:33.000000600", LocalDateTime{LocalDate{2016, 3, 22}, LocalTime{13, 26, 33, 600}}, true}, + {"2016-03-22t13:26:33", LocalDateTime{LocalDate{2016, 3, 22}, LocalTime{13, 26, 33, 0}}, false}, } { gotDateTime, err := ParseLocalDateTime(test.str) if err != nil { @@ -338,10 +338,14 @@ func TestDateTimeOf(t *testing.T) { time time.Time want LocalDateTime }{ - {time.Date(2014, 8, 20, 15, 8, 43, 1, time.Local), - LocalDateTime{LocalDate{2014, 8, 20}, LocalTime{15, 8, 43, 1}}}, - {time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC), - LocalDateTime{LocalDate{1, 1, 1}, LocalTime{0, 0, 0, 0}}}, + { + time.Date(2014, 8, 20, 15, 8, 43, 1, time.Local), + LocalDateTime{LocalDate{2014, 8, 20}, LocalTime{15, 8, 43, 1}}, + }, + { + time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC), + LocalDateTime{LocalDate{1, 1, 1}, LocalTime{0, 0, 0, 0}}, + }, } { if got := LocalDateTimeOf(test.time); got != test.want { t.Errorf("LocalDateTimeOf(%v) = %+v, want %+v", test.time, got, test.want) @@ -449,9 +453,11 @@ func TestMarshalJSON(t *testing.T) { func TestUnmarshalJSON(t *testing.T) { t.Parallel() - var d LocalDate - var tm LocalTime - var dt LocalDateTime + var ( + d LocalDate + tm LocalTime + dt LocalDateTime + ) for _, test := range []struct { data string @@ -471,7 +477,8 @@ func TestUnmarshalJSON(t *testing.T) { } } - for _, bad := range []string{"", `""`, `"bad"`, `"1987-04-15x"`, + for _, bad := range []string{ + "", `""`, `"bad"`, `"1987-04-15x"`, `19870415`, // a JSON number `11987-04-15x`, // not a JSON string diff --git a/parser.go b/parser.go index 89fadf6a..bf83cd93 100644 --- a/parser.go +++ b/parser.go @@ -2,6 +2,7 @@ package toml import ( "bytes" + "errors" "fmt" "strconv" @@ -26,6 +27,7 @@ func (p *parser) Reset(b []byte) { p.first = true } +//nolint:cyclop func (p *parser) NextExpression() bool { if len(p.left) == 0 || p.err != nil { return false @@ -69,22 +71,26 @@ func (p *parser) Error() error { return p.err } +var errUnexpectedByte = errors.New("expected newline but got something else") + func (p *parser) parseNewline(b []byte) ([]byte, error) { if b[0] == '\n' { return b[1:], nil } + if b[0] == '\r' { _, rest, err := scanWindowsNewline(b) + return rest, err } - return nil, fmt.Errorf("expected newline but got %#U", b[0]) + + return nil, fmt.Errorf("parseNewline: %w - %#U", errUnexpectedByte, b[0]) } func (p *parser) parseExpression(b []byte) (ast.Reference, []byte, error) { - //expression = ws [ comment ] - //expression =/ ws keyval ws [ comment ] - //expression =/ ws table ws [ comment ] - + // expression = ws [ comment ] + // expression =/ ws keyval ws [ comment ] + // expression =/ ws table ws [ comment ] var ref ast.Reference b = p.parseWhitespace(b) @@ -95,8 +101,10 @@ func (p *parser) parseExpression(b []byte) (ast.Reference, []byte, error) { if b[0] == '#' { _, rest, err := scanComment(b) + return ref, rest, err } + if b[0] == '\n' || b[0] == '\r' { return ref, b, nil } @@ -107,6 +115,7 @@ func (p *parser) parseExpression(b []byte) (ast.Reference, []byte, error) { } else { ref, b, err = p.parseKeyval(b) } + if err != nil { return ref, nil, err } @@ -115,6 +124,7 @@ func (p *parser) parseExpression(b []byte) (ast.Reference, []byte, error) { if len(b) > 0 && b[0] == '#' { _, rest, err := scanComment(b) + return ref, rest, err } @@ -122,49 +132,54 @@ func (p *parser) parseExpression(b []byte) (ast.Reference, []byte, error) { } func (p *parser) parseTable(b []byte) (ast.Reference, []byte, error) { - //table = std-table / array-table + // table = std-table / array-table if len(b) > 1 && b[1] == '[' { return p.parseArrayTable(b) } + return p.parseStdTable(b) } func (p *parser) parseArrayTable(b []byte) (ast.Reference, []byte, error) { - //array-table = array-table-open key array-table-close - //array-table-open = %x5B.5B ws ; [[ Double left square bracket - //array-table-close = ws %x5D.5D ; ]] Double right square bracket - + // array-table = array-table-open key array-table-close + // array-table-open = %x5B.5B ws ; [[ Double left square bracket + // array-table-close = ws %x5D.5D ; ]] Double right square bracket ref := p.builder.Push(ast.Node{ Kind: ast.ArrayTable, }) b = b[2:] b = p.parseWhitespace(b) + k, b, err := p.parseKey(b) if err != nil { return ref, nil, err } + p.builder.AttachChild(ref, k) b = p.parseWhitespace(b) + b, err = expect(']', b) if err != nil { return ref, nil, err } + b, err = expect(']', b) + return ref, b, err } func (p *parser) parseStdTable(b []byte) (ast.Reference, []byte, error) { - //std-table = std-table-open key std-table-close - //std-table-open = %x5B ws ; [ Left square bracket - //std-table-close = ws %x5D ; ] Right square bracket - + // std-table = std-table-open key std-table-close + // std-table-open = %x5B ws ; [ Left square bracket + // std-table-close = ws %x5D ; ] Right square bracket ref := p.builder.Push(ast.Node{ Kind: ast.Table, }) b = b[1:] b = p.parseWhitespace(b) + key, b, err := p.parseKey(b) if err != nil { return ref, nil, err @@ -180,8 +195,7 @@ func (p *parser) parseStdTable(b []byte) (ast.Reference, []byte, error) { } func (p *parser) parseKeyval(b []byte) (ast.Reference, []byte, error) { - //keyval = key keyval-sep val - + // keyval = key keyval-sep val ref := p.builder.Push(ast.Node{ Kind: ast.KeyValue, }) @@ -191,31 +205,41 @@ func (p *parser) parseKeyval(b []byte) (ast.Reference, []byte, error) { return ast.Reference{}, nil, err } - //keyval-sep = ws %x3D ws ; = + // keyval-sep = ws %x3D ws ; = b = p.parseWhitespace(b) + b, err = expect('=', b) if err != nil { return ast.Reference{}, nil, err } + b = p.parseWhitespace(b) valRef, b, err := p.parseVal(b) if err != nil { return ref, b, err } + p.builder.Chain(valRef, key) p.builder.AttachChild(ref, valRef) return ref, b, err } +var ( + errExpectedValNotEOF = errors.New("expected value, not eof") + errExpectedTrue = errors.New("expected 'true'") + errExpectedFalse = errors.New("expected 'false'") +) + +//nolint:cyclop,funlen func (p *parser) parseVal(b []byte) (ast.Reference, []byte, error) { // val = string / boolean / array / inline-table / date-time / float / integer var ref ast.Reference if len(b) == 0 { - return ref, nil, fmt.Errorf("expected value, not eof") + return ref, nil, errExpectedValNotEOF } var err error @@ -229,12 +253,14 @@ func (p *parser) parseVal(b []byte) (ast.Reference, []byte, error) { } else { v, b, err = p.parseBasicString(b) } + if err == nil { ref = p.builder.Push(ast.Node{ Kind: ast.String, Data: v, }) } + return ref, b, err case '\'': var v []byte @@ -243,30 +269,36 @@ func (p *parser) parseVal(b []byte) (ast.Reference, []byte, error) { } else { v, b, err = p.parseLiteralString(b) } + if err == nil { ref = p.builder.Push(ast.Node{ Kind: ast.String, Data: v, }) } + return ref, b, err case 't': if !scanFollowsTrue(b) { - return ref, nil, fmt.Errorf("expected 'true'") + return ref, nil, errExpectedTrue } + ref = p.builder.Push(ast.Node{ Kind: ast.Bool, Data: b[:4], }) + return ref, b[4:], nil case 'f': if !scanFollowsFalse(b) { - return ast.Reference{}, nil, fmt.Errorf("expected 'false'") + return ast.Reference{}, nil, errExpectedFalse } + ref = p.builder.Push(ast.Node{ Kind: ast.Bool, Data: b[:5], }) + return ref, b[5:], nil case '[': return p.parseValArray(b) @@ -282,26 +314,28 @@ func (p *parser) parseLiteralString(b []byte) ([]byte, []byte, error) { if err != nil { return nil, nil, err } + return v[1 : len(v)-1], rest, nil } func (p *parser) parseInlineTable(b []byte) (ast.Reference, []byte, error) { - //inline-table = inline-table-open [ inline-table-keyvals ] inline-table-close - //inline-table-open = %x7B ws ; { - //inline-table-close = ws %x7D ; } - //inline-table-sep = ws %x2C ws ; , Comma - //inline-table-keyvals = keyval [ inline-table-sep inline-table-keyvals ] - + // inline-table = inline-table-open [ inline-table-keyvals ] inline-table-close + // inline-table-open = %x7B ws ; { + // inline-table-close = ws %x7D ; } + // inline-table-sep = ws %x2C ws ; , Comma + // inline-table-keyvals = keyval [ inline-table-sep inline-table-keyvals ] parent := p.builder.Push(ast.Node{ Kind: ast.InlineTable, }) first := true + var child ast.Reference b = b[1:] var err error + for len(b) > 0 { b = p.parseWhitespace(b) if b[0] == '}' { @@ -315,7 +349,9 @@ func (p *parser) parseInlineTable(b []byte) (ast.Reference, []byte, error) { } b = p.parseWhitespace(b) } + var kv ast.Reference + kv, b, err = p.parseKeyval(b) if err != nil { return parent, nil, err @@ -323,7 +359,6 @@ func (p *parser) parseInlineTable(b []byte) (ast.Reference, []byte, error) { if first { p.builder.AttachChild(parent, kv) - first = false } else { p.builder.Chain(child, kv) } @@ -333,18 +368,21 @@ func (p *parser) parseInlineTable(b []byte) (ast.Reference, []byte, error) { } rest, err := expect('}', b) + return parent, rest, err } -func (p *parser) parseValArray(b []byte) (ast.Reference, []byte, error) { - //array = array-open [ array-values ] ws-comment-newline array-close - //array-open = %x5B ; [ - //array-close = %x5D ; ] - //array-values = ws-comment-newline val ws-comment-newline array-sep array-values - //array-values =/ ws-comment-newline val ws-comment-newline [ array-sep ] - //array-sep = %x2C ; , Comma - //ws-comment-newline = *( wschar / [ comment ] newline ) +var errArrayCanNotStartWithComma = errors.New("array cannot start with comma") +//nolint:funlen,cyclop +func (p *parser) parseValArray(b []byte) (ast.Reference, []byte, error) { + // array = array-open [ array-values ] ws-comment-newline array-close + // array-open = %x5B ; [ + // array-close = %x5D ; ] + // array-values = ws-comment-newline val ws-comment-newline array-sep array-values + // array-values =/ ws-comment-newline val ws-comment-newline [ array-sep ] + // array-sep = %x2C ; , Comma + // ws-comment-newline = *( wschar / [ comment ] newline ) b = b[1:] parent := p.builder.Push(ast.Node{ @@ -352,6 +390,7 @@ func (p *parser) parseValArray(b []byte) (ast.Reference, []byte, error) { }) first := true + var lastChild ast.Reference var err error @@ -362,17 +401,20 @@ func (p *parser) parseValArray(b []byte) (ast.Reference, []byte, error) { } if len(b) == 0 { + //nolint:godox return parent, nil, unexpectedCharacter{b: b} // TODO: should be unexpected EOF } if b[0] == ']' { break } + if b[0] == ',' { if first { - return parent, nil, fmt.Errorf("array cannot start with comma") + return parent, nil, errArrayCanNotStartWithComma } b = b[1:] + b, err = p.parseOptionalWhitespaceCommentNewline(b) if err != nil { return parent, nil, err @@ -385,6 +427,7 @@ func (p *parser) parseValArray(b []byte) (ast.Reference, []byte, error) { } var valueRef ast.Reference + valueRef, b, err = p.parseVal(b) if err != nil { return parent, nil, err @@ -392,7 +435,6 @@ func (p *parser) parseValArray(b []byte) (ast.Reference, []byte, error) { if first { p.builder.AttachChild(parent, valueRef) - first = false } else { p.builder.Chain(lastChild, valueRef) } @@ -406,6 +448,7 @@ func (p *parser) parseValArray(b []byte) (ast.Reference, []byte, error) { } rest, err := expect(']', b) + return parent, rest, err } @@ -413,15 +456,18 @@ func (p *parser) parseOptionalWhitespaceCommentNewline(b []byte) ([]byte, error) for len(b) > 0 { var err error b = p.parseWhitespace(b) + if len(b) > 0 && b[0] == '#' { _, b, err = scanComment(b) if err != nil { return nil, err } } + if len(b) == 0 { break } + if b[0] == '\n' || b[0] == '\r' { b, err = p.parseNewline(b) if err != nil { @@ -431,6 +477,7 @@ func (p *parser) parseOptionalWhitespaceCommentNewline(b []byte) ([]byte, error) break } } + return b, nil } @@ -448,25 +495,29 @@ func (p *parser) parseMultilineLiteralString(b []byte) ([]byte, []byte, error) { } else if token[i] == '\r' && token[i+1] == '\n' { i += 2 } + return token[i : len(token)-3], rest, err } +var errInvalidEscapeChar = errors.New("invalid escaped character") + +//nolint:funlen,gocognit,cyclop func (p *parser) parseMultilineBasicString(b []byte) ([]byte, []byte, error) { - //ml-basic-string = ml-basic-string-delim [ newline ] ml-basic-body - //ml-basic-string-delim - //ml-basic-string-delim = 3quotation-mark - //ml-basic-body = *mlb-content *( mlb-quotes 1*mlb-content ) [ mlb-quotes ] + // ml-basic-string = ml-basic-string-delim [ newline ] ml-basic-body + // ml-basic-string-delim + // ml-basic-string-delim = 3quotation-mark + // ml-basic-body = *mlb-content *( mlb-quotes 1*mlb-content ) [ mlb-quotes ] // - //mlb-content = mlb-char / newline / mlb-escaped-nl - //mlb-char = mlb-unescaped / escaped - //mlb-quotes = 1*2quotation-mark - //mlb-unescaped = wschar / %x21 / %x23-5B / %x5D-7E / non-ascii - //mlb-escaped-nl = escape ws newline *( wschar / newline ) - + // mlb-content = mlb-char / newline / mlb-escaped-nl + // mlb-char = mlb-unescaped / escaped + // mlb-quotes = 1*2quotation-mark + // mlb-unescaped = wschar / %x21 / %x23-5B / %x5D-7E / non-ascii + // mlb-escaped-nl = escape ws newline *( wschar / newline ) token, rest, err := scanMultilineBasicString(b) if err != nil { return nil, nil, err } + var builder bytes.Buffer i := 3 @@ -482,6 +533,8 @@ func (p *parser) parseMultilineBasicString(b []byte) ([]byte, []byte, error) { // escapes are balanced. for ; i < len(token)-3; i++ { c := token[i] + + //nolint:nestif if c == '\\' { // When the last non-whitespace character on a line is an unescaped \, // it will be trimmed along with all whitespace (including newlines) up @@ -492,15 +545,18 @@ func (p *parser) parseMultilineBasicString(b []byte) ([]byte, []byte, error) { c := token[i] if !(c == '\n' || c == '\r' || c == ' ' || c == '\t') { i-- + break } } + continue } // handle escaping i++ c = token[i] + switch c { case '"', '\\': builder.WriteByte(c) @@ -519,6 +575,7 @@ func (p *parser) parseMultilineBasicString(b []byte) ([]byte, []byte, error) { if err != nil { return nil, nil, err } + builder.WriteString(x) i += 4 case 'U': @@ -526,10 +583,11 @@ func (p *parser) parseMultilineBasicString(b []byte) ([]byte, []byte, error) { if err != nil { return nil, nil, err } + builder.WriteString(x) i += 8 default: - return nil, nil, fmt.Errorf("invalid escaped character: %#U", c) + return nil, nil, fmt.Errorf("parseMultilineBasicString: %w - %#U", errInvalidEscapeChar, c) } } else { builder.WriteByte(c) @@ -540,15 +598,14 @@ func (p *parser) parseMultilineBasicString(b []byte) ([]byte, []byte, error) { } func (p *parser) parseKey(b []byte) (ast.Reference, []byte, error) { - //key = simple-key / dotted-key - //simple-key = quoted-key / unquoted-key + // key = simple-key / dotted-key + // simple-key = quoted-key / unquoted-key // - //unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _ - //quoted-key = basic-string / literal-string - //dotted-key = simple-key 1*( dot-sep simple-key ) + // unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _ + // quoted-key = basic-string / literal-string + // dotted-key = simple-key 1*( dot-sep simple-key ) // - //dot-sep = ws %x2E ws ; . Period - + // dot-sep = ws %x2E ws ; . Period key, b, err := p.parseSimpleKey(b) if err != nil { return ast.Reference{}, nil, err @@ -566,11 +623,14 @@ func (p *parser) parseKey(b []byte) (ast.Reference, []byte, error) { if err != nil { return ref, nil, err } + b = p.parseWhitespace(b) + key, b, err = p.parseSimpleKey(b) if err != nil { return ref, nil, err } + p.builder.PushAndChain(ast.Node{ Kind: ast.Key, Data: key, @@ -584,46 +644,48 @@ func (p *parser) parseKey(b []byte) (ast.Reference, []byte, error) { } func (p *parser) parseSimpleKey(b []byte) (key, rest []byte, err error) { - //simple-key = quoted-key / unquoted-key - //unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _ - //quoted-key = basic-string / literal-string - + // simple-key = quoted-key / unquoted-key + // unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _ + // quoted-key = basic-string / literal-string if len(b) == 0 { + //nolint:godox return nil, nil, unexpectedCharacter{b: b} // TODO: should be unexpected EOF } - if b[0] == '\'' { - key, rest, err = p.parseLiteralString(b) - } else if b[0] == '"' { - key, rest, err = p.parseBasicString(b) - } else if isUnquotedKeyChar(b[0]) { - key, rest, err = scanUnquotedKey(b) - } else { - err = unexpectedCharacter{b: b} // TODO: should contain expected characters + switch { + case b[0] == '\'': + return p.parseLiteralString(b) + case b[0] == '"': + return p.parseBasicString(b) + case isUnquotedKeyChar(b[0]): + return scanUnquotedKey(b) + default: + //nolint:godox + return nil, nil, unexpectedCharacter{b: b} // TODO: should be unexpected EOF } - return } +//nolint:funlen,cyclop func (p *parser) parseBasicString(b []byte) ([]byte, []byte, error) { - //basic-string = quotation-mark *basic-char quotation-mark - //quotation-mark = %x22 ; " - //basic-char = basic-unescaped / escaped - //basic-unescaped = wschar / %x21 / %x23-5B / %x5D-7E / non-ascii - //escaped = escape escape-seq-char - //escape-seq-char = %x22 ; " quotation mark U+0022 - //escape-seq-char =/ %x5C ; \ reverse solidus U+005C - //escape-seq-char =/ %x62 ; b backspace U+0008 - //escape-seq-char =/ %x66 ; f form feed U+000C - //escape-seq-char =/ %x6E ; n line feed U+000A - //escape-seq-char =/ %x72 ; r carriage return U+000D - //escape-seq-char =/ %x74 ; t tab U+0009 - //escape-seq-char =/ %x75 4HEXDIG ; uXXXX U+XXXX - //escape-seq-char =/ %x55 8HEXDIG ; UXXXXXXXX U+XXXXXXXX - + // basic-string = quotation-mark *basic-char quotation-mark + // quotation-mark = %x22 ; " + // basic-char = basic-unescaped / escaped + // basic-unescaped = wschar / %x21 / %x23-5B / %x5D-7E / non-ascii + // escaped = escape escape-seq-char + // escape-seq-char = %x22 ; " quotation mark U+0022 + // escape-seq-char =/ %x5C ; \ reverse solidus U+005C + // escape-seq-char =/ %x62 ; b backspace U+0008 + // escape-seq-char =/ %x66 ; f form feed U+000C + // escape-seq-char =/ %x6E ; n line feed U+000A + // escape-seq-char =/ %x72 ; r carriage return U+000D + // escape-seq-char =/ %x74 ; t tab U+0009 + // escape-seq-char =/ %x75 4HEXDIG ; uXXXX U+XXXX + // escape-seq-char =/ %x55 8HEXDIG ; UXXXXXXXX U+XXXXXXXX token, rest, err := scanBasicString(b) if err != nil { return nil, nil, err } + var builder bytes.Buffer // The scanner ensures that the token starts and ends with quotes and that @@ -633,6 +695,7 @@ func (p *parser) parseBasicString(b []byte) ([]byte, []byte, error) { if c == '\\' { i++ c = token[i] + switch c { case '"', '\\': builder.WriteByte(c) @@ -651,6 +714,7 @@ func (p *parser) parseBasicString(b []byte) ([]byte, []byte, error) { if err != nil { return nil, nil, err } + builder.WriteString(x) i += 4 case 'U': @@ -658,10 +722,11 @@ func (p *parser) parseBasicString(b []byte) ([]byte, []byte, error) { if err != nil { return nil, nil, err } + builder.WriteString(x) i += 8 default: - return nil, nil, fmt.Errorf("invalid escaped character: %#U", c) + return nil, nil, fmt.Errorf("parseBasicString: %w - %#U", errInvalidEscapeChar, c) } } else { builder.WriteByte(c) @@ -671,41 +736,54 @@ func (p *parser) parseBasicString(b []byte) ([]byte, []byte, error) { return builder.Bytes(), rest, nil } +var errUnicodePointNeedsRightCountChar = errors.New("unicode point needs right number of hex characters") + func hexToString(b []byte, length int) (string, error) { if len(b) < length { - return "", fmt.Errorf("unicode point needs %d hex characters", length) + return "", fmt.Errorf("hexToString: %w - %d", errUnicodePointNeedsRightCountChar, length) } + + //nolint:godox // TODO: slow intcode, err := strconv.ParseInt(string(b[:length]), 16, 32) if err != nil { - return "", err + return "", fmt.Errorf("hexToString: %w", err) } + return string(rune(intcode)), nil } func (p *parser) parseWhitespace(b []byte) []byte { - //ws = *wschar - //wschar = %x20 ; Space - //wschar =/ %x09 ; Horizontal tab - + // ws = *wschar + // wschar = %x20 ; Space + // wschar =/ %x09 ; Horizontal tab _, rest := scanWhitespace(b) + return rest } +var ( + errExpectedInf = errors.New("expected 'inf'") + errExpectedNan = errors.New("expected 'nan'") +) + +//nolint:cyclop func (p *parser) parseIntOrFloatOrDateTime(b []byte) (ast.Reference, []byte, error) { switch b[0] { case 'i': if !scanFollowsInf(b) { - return ast.Reference{}, nil, fmt.Errorf("expected 'inf'") + return ast.Reference{}, nil, errExpectedInf } + return p.builder.Push(ast.Node{ Kind: ast.Float, Data: b[:3], }), b[3:], nil case 'n': if !scanFollowsNan(b) { - return ast.Reference{}, nil, fmt.Errorf("expected 'nan'") + return ast.Reference{}, nil, errExpectedNan } + return p.builder.Push(ast.Node{ Kind: ast.Float, Data: b[:3], @@ -714,60 +792,73 @@ func (p *parser) parseIntOrFloatOrDateTime(b []byte) (ast.Reference, []byte, err return p.scanIntOrFloat(b) } + //nolint:gomnd if len(b) < 3 { return p.scanIntOrFloat(b) } + s := 5 if len(b) < s { s = len(b) } + for idx, c := range b[:s] { if isDigit(c) { continue } + if idx == 2 && c == ':' || (idx == 4 && c == '-') { return p.scanDateTime(b) } } + return p.scanIntOrFloat(b) } func digitsToInt(b []byte) int { x := 0 + for _, d := range b { x *= 10 x += int(d - '0') } + return x } +var errTimezoneButNoTimeComponent = errors.New("possible DateTime cannot have a timezone but no time component") + +//nolint:gocognit,cyclop func (p *parser) scanDateTime(b []byte) (ast.Reference, []byte, error) { // scans for contiguous characters in [0-9T:Z.+-], and up to one space if // followed by a digit. - hasTime := false hasTz := false seenSpace := false i := 0 +byteLoop: for ; i < len(b); i++ { c := b[i] - if isDigit(c) || c == '-' { - } else if c == 'T' || c == ':' || c == '.' { + + switch { + case isDigit(c) || c == '-': + case c == 'T' || c == ':' || c == '.': hasTime = true - continue - } else if c == '+' || c == '-' || c == 'Z' { + + continue byteLoop + case c == '+' || c == '-' || c == 'Z': hasTz = true - } else if c == ' ' { + case c == ' ': if !seenSpace && i+1 < len(b) && isDigit(b[i+1]) { i += 2 seenSpace = true hasTime = true } else { - break + break byteLoop } - } else { - break + default: + break byteLoop } } @@ -781,7 +872,7 @@ func (p *parser) scanDateTime(b []byte) (ast.Reference, []byte, error) { } } else { if hasTz { - return ast.Reference{}, nil, fmt.Errorf("possible DateTime cannot have a timezone but no time component") + return ast.Reference{}, nil, errTimezoneButNoTimeComponent } kind = ast.LocalDate } @@ -792,11 +883,19 @@ func (p *parser) scanDateTime(b []byte) (ast.Reference, []byte, error) { }), b[i:], nil } +var ( + errUnexpectedCharI = fmt.Errorf("unexpected character i while scanning for a number") + errUnexpectedCharN = fmt.Errorf("unexpected character n while scanning for a number") + errExpectedIntOrFloat = fmt.Errorf("expected integer or float") +) + +//nolint:funlen,gocognit,cyclop func (p *parser) scanIntOrFloat(b []byte) (ast.Reference, []byte, error) { i := 0 if len(b) > 2 && b[0] == '0' && b[1] != '.' { var isValidRune validRuneFn + switch b[1] { case 'x': isValidRune = isValidHexRune @@ -834,6 +933,7 @@ func (p *parser) scanIntOrFloat(b []byte) (ast.Reference, []byte, error) { if c == '.' || c == 'e' || c == 'E' { isFloat = true + continue } @@ -844,8 +944,10 @@ func (p *parser) scanIntOrFloat(b []byte) (ast.Reference, []byte, error) { Data: b[:i+3], }), b[i+3:], nil } - return ast.Reference{}, nil, fmt.Errorf("unexpected character i while scanning for a number") + + return ast.Reference{}, nil, errUnexpectedCharI } + if c == 'n' { if scanFollowsNan(b[i:]) { return p.builder.Push(ast.Node{ @@ -853,14 +955,15 @@ func (p *parser) scanIntOrFloat(b []byte) (ast.Reference, []byte, error) { Data: b[:i+3], }), b[i+3:], nil } - return ast.Reference{}, nil, fmt.Errorf("unexpected character n while scanning for a number") + + return ast.Reference{}, nil, errUnexpectedCharN } break } if i == 0 { - return ast.Reference{}, b, fmt.Errorf("expected integer or float") + return ast.Reference{}, b, errExpectedIntOrFloat } kind := ast.Integer @@ -900,9 +1003,11 @@ func expect(x byte, b []byte) ([]byte, error) { if len(b) == 0 { return nil, newDecodeError(b[:0], "expecting %#U", x) } + if b[0] != x { return nil, newDecodeError(b[0:1], "expected character %U", x) } + return b[1:], nil } @@ -914,7 +1019,7 @@ type unexpectedCharacter struct { func (u unexpectedCharacter) Error() string { if len(u.b) == 0 { return fmt.Sprintf("expected %#U, not EOF", u.r) - } + return fmt.Sprintf("expected %#U, not %#U", u.r, u.b[0]) } diff --git a/parser_test.go b/parser_test.go index e4b82a33..bb3d3bd9 100644 --- a/parser_test.go +++ b/parser_test.go @@ -7,7 +7,10 @@ import ( "github.com/stretchr/testify/require" ) +//nolint:funlen func TestParser_AST_Numbers(t *testing.T) { + t.Parallel() + examples := []struct { desc string input string @@ -132,7 +135,9 @@ func TestParser_AST_Numbers(t *testing.T) { } for _, e := range examples { + e := e t.Run(e.desc, func(t *testing.T) { + t.Parallel() p := parser{} p.Reset([]byte(`A = ` + e.input)) p.NextExpression() @@ -156,7 +161,6 @@ func TestParser_AST_Numbers(t *testing.T) { } type ( - astRoot []astNode astNode struct { Kind ast.Kind Data []byte @@ -164,11 +168,6 @@ type ( } ) -func compareAST(t *testing.T, expected astRoot, actual *ast.Root) { - it := actual.Iterator() - compareIterator(t, expected, it) -} - func compareNode(t *testing.T, e astNode, n ast.Node) { t.Helper() require.Equal(t, e.Kind, n.Kind) @@ -199,55 +198,10 @@ func compareIterator(t *testing.T, expected []astNode, actual ast.Iterator) { } } -func (r astRoot) toOrig() *ast.Root { - builder := &ast.Builder{} - - var last ast.Reference - - for i, n := range r { - ref := builder.Push(ast.Node{ - Kind: n.Kind, - Data: n.Data, - }) - - if i > 0 { - builder.Chain(last, ref) - } - last = ref - - if len(n.Children) > 0 { - c := childrenToOrig(builder, n.Children) - builder.AttachChild(ref, c) - } - } - - return builder.Tree() -} - -func childrenToOrig(b *ast.Builder, nodes []astNode) ast.Reference { - var first ast.Reference - var last ast.Reference - for i, n := range nodes { - ref := b.Push(ast.Node{ - Kind: n.Kind, - Data: n.Data, - }) - if i == 0 { - first = ref - } else { - b.Chain(last, ref) - } - last = ref - - if len(n.Children) > 0 { - c := childrenToOrig(b, n.Children) - b.AttachChild(ref, c) - } - } - return first -} - +//nolint:funlen func TestParser_AST(t *testing.T) { + t.Parallel() + examples := []struct { desc string input string @@ -384,7 +338,9 @@ func TestParser_AST(t *testing.T) { } for _, e := range examples { + e := e t.Run(e.desc, func(t *testing.T) { + t.Parallel() p := parser{} p.Reset([]byte(e.input)) p.NextExpression() From e443b4fdb84c016e57ebec820cc1239a2bb877c3 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Thu, 22 Apr 2021 10:13:41 -0400 Subject: [PATCH 184/228] encoder: support TextMarshaler (#522) Fixes #521 --- .../imported_tests/marshal_imported_test.go | 32 +++++++++++++++++++ .../imported_tests/unmarshal_imported_test.go | 10 ------ marshaler.go | 24 +++++++++++--- 3 files changed, 51 insertions(+), 15 deletions(-) diff --git a/internal/imported_tests/marshal_imported_test.go b/internal/imported_tests/marshal_imported_test.go index 98ad71fe..578cf577 100644 --- a/internal/imported_tests/marshal_imported_test.go +++ b/internal/imported_tests/marshal_imported_test.go @@ -4,6 +4,7 @@ package imported_tests // defaults of v2. import ( + "fmt" "testing" "time" @@ -164,3 +165,34 @@ stringlist = [] require.Equal(t, string(expected), string(result)) } + +type textMarshaler struct { + FirstName string + LastName string +} + +func (m textMarshaler) MarshalText() ([]byte, error) { + fullName := fmt.Sprintf("%s %s", m.FirstName, m.LastName) + return []byte(fullName), nil +} + +func TestTextMarshaler(t *testing.T) { + type wrap struct { + TM textMarshaler + } + + m := textMarshaler{FirstName: "Sally", LastName: "Fields"} + + t.Run("at root", func(t *testing.T) { + _, err := toml.Marshal(m) + // in v2 we do not allow TextMarshaler at root + require.Error(t, err) + }) + + t.Run("leaf", func(t *testing.T) { + res, err := toml.Marshal(wrap{m}) + require.NoError(t, err) + + require.Equal(t, "TM = 'Sally Fields'\n", string(res)) + }) +} diff --git a/internal/imported_tests/unmarshal_imported_test.go b/internal/imported_tests/unmarshal_imported_test.go index 8dbfec45..d3f54d87 100644 --- a/internal/imported_tests/unmarshal_imported_test.go +++ b/internal/imported_tests/unmarshal_imported_test.go @@ -612,16 +612,6 @@ func (x *IntOrString) MarshalTOML() ([]byte, error) { return []byte(s), nil } -type textMarshaler struct { - FirstName string - LastName string -} - -func (m textMarshaler) MarshalText() ([]byte, error) { - fullName := fmt.Sprintf("%s %s", m.FirstName, m.LastName) - return []byte(fullName), nil -} - func TestUnmarshalTextMarshaler(t *testing.T) { var nested = struct { Friends textMarshaler `toml:"friends"` diff --git a/marshaler.go b/marshaler.go index 24910152..06f11a1c 100644 --- a/marshaler.go +++ b/marshaler.go @@ -2,6 +2,7 @@ package toml import ( "bytes" + "encoding" "errors" "fmt" "io" @@ -165,14 +166,27 @@ func (ctx *encoderCtx) isRoot() bool { } var errUnsupportedValue = errors.New("unsupported encode value kind") +var errTextMarshalerCannotBeAtRoot = errors.New("type implementing TextMarshaler cannot be at root") //nolint:cyclop func (enc *Encoder) encode(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) { //nolint:gocritic,godox - switch i := v.Interface().(type) { - case time.Time: // TODO: add TextMarshaler + + if v.Type() == timeType { + i := v.Interface().(time.Time) b = i.AppendFormat(b, time.RFC3339) + return b, nil + } + if v.Type().Implements(textMarshalerType) { + if ctx.isRoot() { + return nil, errTextMarshalerCannotBeAtRoot + } + text, err := v.Interface().(encoding.TextMarshaler).MarshalText() + if err != nil { + return nil, err + } + b = enc.encodeString(b, string(text), ctx.options) return b, nil } @@ -620,10 +634,10 @@ func (enc *Encoder) encodeTableInline(b []byte, ctx encoderCtx, t table) ([]byte var errNilInterface = errors.New("nil interface not supported") +var textMarshalerType = reflect.TypeOf(new(encoding.TextMarshaler)).Elem() + func willConvertToTable(ctx encoderCtx, v reflect.Value) (bool, error) { - //nolint:gocritic,godox - switch v.Interface().(type) { - case time.Time: // TODO: add TextMarshaler + if v.Type() == timeType || v.Type().Implements(textMarshalerType) { return false, nil } From 466faaab9f7dbcdb51b286b8416edd36fb6fea65 Mon Sep 17 00:00:00 2001 From: Vincent Serpoul Date: Fri, 23 Apr 2021 22:41:21 +0800 Subject: [PATCH 185/228] golangci-lint: marshaler, strict (#523) --- .golangci.toml | 2 +- marshaler.go | 47 ++++++++++++++++++++++++++++------------------- marshaler_test.go | 8 ++++++++ strict.go | 9 +++++++++ 4 files changed, 46 insertions(+), 20 deletions(-) diff --git a/.golangci.toml b/.golangci.toml index 3d338e1e..a38c60b0 100644 --- a/.golangci.toml +++ b/.golangci.toml @@ -27,7 +27,7 @@ enable = [ "forcetypeassert", "funlen", "gci", - "gochecknoglobals", + # "gochecknoglobals", "gochecknoinits", "gocognit", "goconst", diff --git a/marshaler.go b/marshaler.go index 06f11a1c..67513b01 100644 --- a/marshaler.go +++ b/marshaler.go @@ -49,19 +49,19 @@ func NewEncoder(w io.Writer) *Encoder { // SetTablesInline forces the encoder to emit all tables inline. // -// This behavior can be controled on an individual struct field basis with the +// This behavior can be controlled on an individual struct field basis with the // `inline="true"` tag. -func (e *Encoder) SetTablesInline(inline bool) { - e.tablesInline = inline +func (enc *Encoder) SetTablesInline(inline bool) { + enc.tablesInline = inline } // SetArraysMultiline forces the encoder to emit all arrays with one element per // line. // -// This behavior can be controled on an individual struct field basis with the +// This behavior can be controlled on an individual struct field basis with the // `multiline="true"` tag. -func (e *Encoder) SetArraysMultiline(multiline bool) { - e.arraysMultiline = multiline +func (enc *Encoder) SetArraysMultiline(multiline bool) { + enc.arraysMultiline = multiline } // Encode writes a TOML representation of v to the stream. @@ -165,18 +165,18 @@ func (ctx *encoderCtx) isRoot() bool { return len(ctx.parentKey) == 0 && !ctx.hasKey } -var errUnsupportedValue = errors.New("unsupported encode value kind") -var errTextMarshalerCannotBeAtRoot = errors.New("type implementing TextMarshaler cannot be at root") +var ( + errUnsupportedValue = errors.New("unsupported encode value kind") + errTextMarshalerCannotBeAtRoot = errors.New("type implementing TextMarshaler cannot be at root") +) -//nolint:cyclop +//nolint:cyclop,funlen func (enc *Encoder) encode(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) { - //nolint:gocritic,godox - - if v.Type() == timeType { - i := v.Interface().(time.Time) - b = i.AppendFormat(b, time.RFC3339) - return b, nil + i, ok := v.Interface().(time.Time) + if ok { + return i.AppendFormat(b, time.RFC3339), nil } + if v.Type().Implements(textMarshalerType) { if ctx.isRoot() { return nil, errTextMarshalerCannotBeAtRoot @@ -184,9 +184,11 @@ func (enc *Encoder) encode(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, e text, err := v.Interface().(encoding.TextMarshaler).MarshalText() if err != nil { - return nil, err + return nil, fmt.Errorf("encode: %w", err) } + b = enc.encodeString(b, string(text), ctx.options) + return b, nil } @@ -543,6 +545,7 @@ func (enc *Encoder) encodeStruct(b []byte, ctx encoderCtx, v reflect.Value) ([]b func fieldBoolTag(field reflect.StructField, tag string) bool { x, ok := field.Tag.Lookup(tag) + return ok && x == "true" } @@ -578,6 +581,7 @@ func (enc *Encoder) encodeTable(b []byte, ctx encoderCtx, t table) ([]byte, erro ctx.setKey(table.Key) ctx.options = table.Options + b, err = enc.encode(b, ctx, table.Value) if err != nil { return nil, err @@ -632,9 +636,10 @@ func (enc *Encoder) encodeTableInline(b []byte, ctx encoderCtx, t table) ([]byte return b, nil } -var errNilInterface = errors.New("nil interface not supported") - -var textMarshalerType = reflect.TypeOf(new(encoding.TextMarshaler)).Elem() +var ( + errNilInterface = errors.New("nil interface not supported") + textMarshalerType = reflect.TypeOf(new(encoding.TextMarshaler)).Elem() +) func willConvertToTable(ctx encoderCtx, v reflect.Value) (bool, error) { if v.Type() == timeType || v.Type().Implements(textMarshalerType) { @@ -765,7 +770,9 @@ func (enc *Encoder) encodeSliceAsArray(b []byte, ctx encoderCtx, v reflect.Value if multiline { separator = ",\n" + b = append(b, '\n') + subCtx.indent++ } @@ -793,6 +800,7 @@ func (enc *Encoder) encodeSliceAsArray(b []byte, ctx encoderCtx, v reflect.Value b = append(b, '\n') b = enc.indent(ctx.indent, b) } + b = append(b, ']') return b, nil @@ -802,5 +810,6 @@ func (enc *Encoder) indent(level int, b []byte) []byte { for i := 0; i < level; i++ { b = append(b, enc.indentSymbol...) } + return b } diff --git a/marshaler_test.go b/marshaler_test.go index e0a349f0..8d641820 100644 --- a/marshaler_test.go +++ b/marshaler_test.go @@ -308,6 +308,7 @@ A = [ b, err := toml.Marshal(e.v) if e.err { require.Error(t, err) + return } @@ -320,6 +321,8 @@ A = [ require.NoError(t, err) testWithAllFlags(t, func(t *testing.T, flags int) { + t.Helper() + var buf bytes.Buffer enc := toml.NewEncoder(&buf) setFlags(enc, flags) @@ -364,6 +367,7 @@ func testWithFlags(t *testing.T, flags int, setters flagsSetters, testfn func(t if len(setters) == 0 { testfn(t, flags) + return } @@ -372,9 +376,11 @@ func testWithFlags(t *testing.T, flags int, setters flagsSetters, testfn func(t for _, enabled := range []bool{false, true} { name := fmt.Sprintf("%s=%t", s.name, enabled) newFlags := flags << 1 + if enabled { newFlags++ } + t.Run(name, func(t *testing.T) { testWithFlags(t, newFlags, setters[1:], testfn) }) @@ -409,6 +415,8 @@ c = 'd' } func TestIssue424(t *testing.T) { + t.Parallel() + type Message1 struct { Text string } diff --git a/strict.go b/strict.go index 236ad643..2b2e7d65 100644 --- a/strict.go +++ b/strict.go @@ -18,6 +18,7 @@ func (s *strict) EnterTable(node ast.Node) { if !s.Enabled { return } + s.key.UpdateTable(node) } @@ -25,6 +26,7 @@ func (s *strict) EnterArrayTable(node ast.Node) { if !s.Enabled { return } + s.key.UpdateArrayTable(node) } @@ -32,6 +34,7 @@ func (s *strict) EnterKeyValue(node ast.Node) { if !s.Enabled { return } + s.key.Push(node) } @@ -39,6 +42,7 @@ func (s *strict) ExitKeyValue(node ast.Node) { if !s.Enabled { return } + s.key.Pop(node) } @@ -46,6 +50,7 @@ func (s *strict) MissingTable(node ast.Node) { if !s.Enabled { return } + s.missing = append(s.missing, decodeError{ highlight: keyLocation(node), message: "missing table", @@ -57,6 +62,7 @@ func (s *strict) MissingField(node ast.Node) { if !s.Enabled { return } + s.missing = append(s.missing, decodeError{ highlight: keyLocation(node), message: "missing field", @@ -72,8 +78,11 @@ func (s *strict) Error(doc []byte) error { err := &StrictMissingError{ Errors: make([]DecodeError, 0, len(s.missing)), } + for _, derr := range s.missing { + derr := derr err.Errors = append(err.Errors, *wrapDecodeError(doc, &derr)) } + return err } From a533331aeeda0ec42b063ca0dccf5d38efb0f69b Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Fri, 23 Apr 2021 15:21:41 -0400 Subject: [PATCH 186/228] v2: benchdiff (#524) --- .github/workflows/benchmark.yml | 38 ++++++++++++++++++ .github/workflows/workflow.yml | 3 -- benchmark/bench_datasets_test.go | 39 +++++++------------ benchmark/benchmark_test.go | 67 ++++++++++---------------------- benchmark/go.mod | 14 ------- benchmark/go.sum | 16 -------- 6 files changed, 72 insertions(+), 105 deletions(-) create mode 100644 .github/workflows/benchmark.yml delete mode 100644 benchmark/go.mod delete mode 100644 benchmark/go.sum diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml new file mode 100644 index 00000000..a39d2b6b --- /dev/null +++ b/.github/workflows/benchmark.yml @@ -0,0 +1,38 @@ +name: benchmark +on: + push: + branches: + - v2 + pull_request: + branches: + - v2 + +jobs: + base: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + with: + fetch-depth: 0 + - name: Setup go + uses: actions/setup-go@master + with: + go-version: "~1.16" + - uses: WillAbides/benchdiff-action@main + with: + benchdiff_version: 0.7.0 + status_sha: ${{ github.sha }} + status_name: bench-result + status_on_degraded: neutral + benchdiff_args: | + --packages ./... + --cpu=1,2 + --count=10 + --warmup-count=1 + --warmup-time=10ms + --tolerance=10 + --base-ref origin/v2 + --debug + --benchmem + --geomean + --sort=name diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 7d358e7b..f9e073ea 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -23,6 +23,3 @@ jobs: go-version: ${{ matrix.go }} - name: Run unit tests run: go test -race ./... - - name: Run benchmark tests - run: go test -race ./... - working-directory: benchmark diff --git a/benchmark/bench_datasets_test.go b/benchmark/bench_datasets_test.go index 7c0165aa..1d668d90 100644 --- a/benchmark/bench_datasets_test.go +++ b/benchmark/bench_datasets_test.go @@ -8,6 +8,7 @@ import ( "path/filepath" "testing" + "github.com/pelletier/go-toml/v2" "github.com/stretchr/testify/require" ) @@ -32,20 +33,12 @@ func TestUnmarshalDatasetCode(t *testing.T) { for _, tc := range bench_inputs { buf := fixture(t, tc.name) t.Run(tc.name, func(t *testing.T) { - for _, r := range runners { - if r.name == "bs" && tc.name == "canada" { - t.Skip("skipping: burntsushi can't handle mixed arrays") - } + var v interface{} + check(t, toml.Unmarshal(buf, &v)) - t.Run(r.name, func(t *testing.T) { - var v interface{} - check(t, r.unmarshal(buf, &v)) - - b, err := json.Marshal(v) - check(t, err) - require.Equal(t, len(b), tc.jsonLen) - }) - } + b, err := json.Marshal(v) + check(t, err) + require.Equal(t, len(b), tc.jsonLen) }) } } @@ -54,19 +47,13 @@ func BenchmarkUnmarshalDataset(b *testing.B) { for _, tc := range bench_inputs { buf := fixture(b, tc.name) b.Run(tc.name, func(b *testing.B) { - bench(b, func(r runner, b *testing.B) { - if r.name == "bs" && tc.name == "canada" { - b.Skip("skipping: burntsushi can't handle mixed arrays") - } - - b.SetBytes(int64(len(buf))) - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - var v interface{} - check(b, r.unmarshal(buf, &v)) - } - }) + b.SetBytes(int64(len(buf))) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + var v interface{} + check(b, toml.Unmarshal(buf, &v)) + } }) } } diff --git a/benchmark/benchmark_test.go b/benchmark/benchmark_test.go index 65797b25..3e6c4857 100644 --- a/benchmark/benchmark_test.go +++ b/benchmark/benchmark_test.go @@ -5,44 +5,21 @@ import ( "testing" "time" - tomlbs "github.com/BurntSushi/toml" - tomlv1 "github.com/pelletier/go-toml-v1" "github.com/pelletier/go-toml/v2" "github.com/stretchr/testify/require" ) -type runner struct { - name string - unmarshal func([]byte, interface{}) error -} - -var runners = []runner{ - {"v2", toml.Unmarshal}, - {"v1", tomlv1.Unmarshal}, - {"bs", tomlbs.Unmarshal}, -} - -func bench(b *testing.B, f func(r runner, b *testing.B)) { - for _, r := range runners { - b.Run(r.name, func(b *testing.B) { - f(r, b) - }) - } -} - func BenchmarkUnmarshalSimple(b *testing.B) { - bench(b, func(r runner, b *testing.B) { - d := struct { - A string - }{} - doc := []byte(`A = "hello"`) - for i := 0; i < b.N; i++ { - err := r.unmarshal(doc, &d) - if err != nil { - panic(err) - } + d := struct { + A string + }{} + doc := []byte(`A = "hello"`) + for i := 0; i < b.N; i++ { + err := toml.Unmarshal(doc, &d) + if err != nil { + panic(err) } - }) + } } type benchmarkDoc struct { @@ -152,22 +129,20 @@ type benchmarkDoc struct { } func BenchmarkReferenceFile(b *testing.B) { - bench(b, func(r runner, b *testing.B) { - bytes, err := ioutil.ReadFile("benchmark.toml") + bytes, err := ioutil.ReadFile("benchmark.toml") + if err != nil { + b.Fatal(err) + } + b.SetBytes(int64(len(bytes))) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + d := benchmarkDoc{} + err := toml.Unmarshal(bytes, &d) if err != nil { - b.Fatal(err) + panic(err) } - b.SetBytes(int64(len(bytes))) - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - d := benchmarkDoc{} - err := r.unmarshal(bytes, &d) - if err != nil { - panic(err) - } - } - }) + } } func TestReferenceFile(t *testing.T) { diff --git a/benchmark/go.mod b/benchmark/go.mod deleted file mode 100644 index c1bf154e..00000000 --- a/benchmark/go.mod +++ /dev/null @@ -1,14 +0,0 @@ -module github.com/pelletier/go-toml/v2/benchmark - -go 1.16 - -replace github.com/pelletier/go-toml/v2 => ../ - -replace github.com/pelletier/go-toml-v1 => github.com/pelletier/go-toml v1.8.1 - -require ( - github.com/BurntSushi/toml v0.3.1 - github.com/pelletier/go-toml-v1 v0.0.0-00010101000000-000000000000 - github.com/pelletier/go-toml/v2 v2.0.0-00010101000000-000000000000 - github.com/stretchr/testify v1.7.0 -) diff --git a/benchmark/go.sum b/benchmark/go.sum deleted file mode 100644 index bcc29496..00000000 --- a/benchmark/go.sum +++ /dev/null @@ -1,16 +0,0 @@ -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM= -github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 931f02a51969a7cfe78671cbff163a208b7128a6 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Fri, 23 Apr 2021 17:08:27 -0400 Subject: [PATCH 187/228] encoder: support indentation (#525) --- marshaler.go | 30 +++++++++++++++++++---- marshaler_test.go | 61 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 5 deletions(-) diff --git a/marshaler.go b/marshaler.go index 67513b01..b3028934 100644 --- a/marshaler.go +++ b/marshaler.go @@ -37,6 +37,7 @@ type Encoder struct { tablesInline bool arraysMultiline bool indentSymbol string + indentTables bool } // NewEncoder returns a new Encoder that writes to w. @@ -64,6 +65,18 @@ func (enc *Encoder) SetArraysMultiline(multiline bool) { enc.arraysMultiline = multiline } +// SetIndentSymbol defines the string that should be used for indentation. The +// provided string is repeated for each indentation level. Defaults to two +// spaces. +func (enc *Encoder) SetIndentSymbol(s string) { + enc.indentSymbol = s +} + +// SetIndentTables forces the encoder to intent tables and array tables. +func (enc *Encoder) SetIndentTables(indent bool) { + enc.indentTables = indent +} + // Encode writes a TOML representation of v to the stream. // // If v cannot be represented to TOML it returns an error. @@ -257,6 +270,8 @@ func (enc *Encoder) encodeKv(b []byte, ctx encoderCtx, options valueOptions, v r return b, nil } + b = enc.indent(ctx.indent, b) + b, err = enc.encodeKey(b, ctx.key) if err != nil { return nil, err @@ -367,21 +382,23 @@ func (enc *Encoder) encodeUnquotedKey(b []byte, v string) []byte { return append(b, v...) } -func (enc *Encoder) encodeTableHeader(b []byte, key []string) ([]byte, error) { - if len(key) == 0 { +func (enc *Encoder) encodeTableHeader(ctx encoderCtx, b []byte) ([]byte, error) { + if len(ctx.parentKey) == 0 { return b, nil } + b = enc.indent(ctx.indent, b) + b = append(b, '[') var err error - b, err = enc.encodeKey(b, key[0]) + b, err = enc.encodeKey(b, ctx.parentKey[0]) if err != nil { return nil, err } - for _, k := range key[1:] { + for _, k := range ctx.parentKey[1:] { b = append(b, '.') b, err = enc.encodeKey(b, k) @@ -559,10 +576,13 @@ func (enc *Encoder) encodeTable(b []byte, ctx encoderCtx, t table) ([]byte, erro } if !ctx.skipTableHeader { - b, err = enc.encodeTableHeader(b, ctx.parentKey) + b, err = enc.encodeTableHeader(ctx, b) if err != nil { return nil, err } + if enc.indentTables && len(ctx.parentKey) > 0 { + ctx.indent++ + } } ctx.skipTableHeader = false diff --git a/marshaler_test.go b/marshaler_test.go index 8d641820..78d452ea 100644 --- a/marshaler_test.go +++ b/marshaler_test.go @@ -348,6 +348,7 @@ type flagsSetters []struct { var allFlags = flagsSetters{ {"arrays-multiline", (*toml.Encoder).SetArraysMultiline}, {"tables-inline", (*toml.Encoder).SetTablesInline}, + {"indent-tables", (*toml.Encoder).SetIndentTables}, } func setFlags(enc *toml.Encoder, flags int) { @@ -393,6 +394,66 @@ func equalStringsIgnoreNewlines(t *testing.T, expected string, actual string) { assert.Equal(t, strings.Trim(expected, cutset), strings.Trim(actual, cutset)) } +func TestMarshalIndentTables(t *testing.T) { + examples := []struct { + desc string + v interface{} + expected string + }{ + { + desc: "one kv", + v: map[string]interface{}{ + "foo": "bar", + }, + expected: `foo = 'bar'`, + }, + { + desc: "one level table", + v: map[string]map[string]string{ + "foo": { + "one": "value1", + "two": "value2", + }, + }, + expected: ` +[foo] + one = 'value1' + two = 'value2' +`, + }, + { + desc: "two levels table", + v: map[string]interface{}{ + "root": "value0", + "level1": map[string]interface{}{ + "one": "value1", + "level2": map[string]interface{}{ + "two": "value2", + }, + }, + }, + expected: ` +root = 'value0' +[level1] + one = 'value1' + [level1.level2] + two = 'value2' +`, + }, + } + + for _, e := range examples { + t.Run(e.desc, func(t *testing.T) { + var buf strings.Builder + enc := toml.NewEncoder(&buf) + enc.SetIndentTables(true) + err := enc.Encode(e.v) + require.NoError(t, err) + equalStringsIgnoreNewlines(t, e.expected, buf.String()) + }) + } +} + func TestIssue436(t *testing.T) { t.Parallel() From 1e802675584612ffc5d49dd4cd6322780569f78a Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Sat, 24 Apr 2021 09:57:21 -0400 Subject: [PATCH 188/228] parser: require \n after parsing integer in kv (#527) Fixes #526 --- parser.go | 8 +++----- unmarshaler_test.go | 11 +++++++++++ 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/parser.go b/parser.go index bf83cd93..71913369 100644 --- a/parser.go +++ b/parser.go @@ -55,11 +55,11 @@ func (p *parser) NextExpression() bool { return false } + p.first = false + if p.ref.Valid() { return true } - - p.first = false } } @@ -71,8 +71,6 @@ func (p *parser) Error() error { return p.err } -var errUnexpectedByte = errors.New("expected newline but got something else") - func (p *parser) parseNewline(b []byte) ([]byte, error) { if b[0] == '\n' { return b[1:], nil @@ -84,7 +82,7 @@ func (p *parser) parseNewline(b []byte) ([]byte, error) { return rest, err } - return nil, fmt.Errorf("parseNewline: %w - %#U", errUnexpectedByte, b[0]) + return nil, newDecodeError(b[0:1], "expected newline but got %#U", b[0]) } func (p *parser) parseExpression(b []byte) (ast.Reference, []byte, error) { diff --git a/unmarshaler_test.go b/unmarshaler_test.go index 7d1bd07d..b0f62d23 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -747,6 +747,17 @@ B = "data"`, } }, }, + { + desc: "no newline (#526)", + input: `a = 1z = 2`, + gen: func() test { + m := map[string]interface{}{} + return test{ + target: &m, + err: true, + } + }, + }, } for _, e := range examples { From 201d5dd422220e157a53633b139fdc8401b4dd2b Mon Sep 17 00:00:00 2001 From: Vincent Serpoul Date: Wed, 28 Apr 2021 08:29:00 +0800 Subject: [PATCH 189/228] golangci-lint: misc (#529) --- marshaler.go | 2 + marshaler_test.go | 6 +++ parser.go | 13 +++--- scanner.go | 92 ++++++++++++++++++++++----------------- unmarshaler.go | 103 +++++++++++++++++++++++++++++++++++--------- unmarshaler_test.go | 99 ++++++++++++++++++++++++++++++++++++------ 6 files changed, 233 insertions(+), 82 deletions(-) diff --git a/marshaler.go b/marshaler.go index b3028934..172c0334 100644 --- a/marshaler.go +++ b/marshaler.go @@ -566,6 +566,7 @@ func fieldBoolTag(field reflect.StructField, tag string) bool { return ok && x == "true" } +//nolint:cyclop func (enc *Encoder) encodeTable(b []byte, ctx encoderCtx, t table) ([]byte, error) { var err error @@ -580,6 +581,7 @@ func (enc *Encoder) encodeTable(b []byte, ctx encoderCtx, t table) ([]byte, erro if err != nil { return nil, err } + if enc.indentTables && len(ctx.parentKey) > 0 { ctx.indent++ } diff --git a/marshaler_test.go b/marshaler_test.go index 78d452ea..470bead0 100644 --- a/marshaler_test.go +++ b/marshaler_test.go @@ -394,7 +394,10 @@ func equalStringsIgnoreNewlines(t *testing.T, expected string, actual string) { assert.Equal(t, strings.Trim(expected, cutset), strings.Trim(actual, cutset)) } +//nolint:funlen func TestMarshalIndentTables(t *testing.T) { + t.Parallel() + examples := []struct { desc string v interface{} @@ -443,7 +446,10 @@ root = 'value0' } for _, e := range examples { + e := e t.Run(e.desc, func(t *testing.T) { + t.Parallel() + var buf strings.Builder enc := toml.NewEncoder(&buf) enc.SetIndentTables(true) diff --git a/parser.go b/parser.go index 71913369..1ff2272f 100644 --- a/parser.go +++ b/parser.go @@ -98,9 +98,9 @@ func (p *parser) parseExpression(b []byte) (ast.Reference, []byte, error) { } if b[0] == '#' { - _, rest, err := scanComment(b) + _, rest := scanComment(b) - return ref, rest, err + return ref, rest, nil } if b[0] == '\n' || b[0] == '\r' { @@ -121,9 +121,9 @@ func (p *parser) parseExpression(b []byte) (ast.Reference, []byte, error) { b = p.parseWhitespace(b) if len(b) > 0 && b[0] == '#' { - _, rest, err := scanComment(b) + _, rest := scanComment(b) - return ref, rest, err + return ref, rest, nil } return ref, b, nil @@ -456,10 +456,7 @@ func (p *parser) parseOptionalWhitespaceCommentNewline(b []byte) ([]byte, error) b = p.parseWhitespace(b) if len(b) > 0 && b[0] == '#' { - _, b, err = scanComment(b) - if err != nil { - return nil, err - } + _, b = scanComment(b) } if len(b) == 0 { diff --git a/scanner.go b/scanner.go index 8ba6f99a..b63ccb40 100644 --- a/scanner.go +++ b/scanner.go @@ -1,9 +1,12 @@ package toml -import "fmt" +import ( + "errors" +) func scanFollows(b []byte, pattern string) bool { n := len(pattern) + return len(b) >= n && string(b[:n]) == pattern } @@ -38,6 +41,7 @@ func scanUnquotedKey(b []byte) ([]byte, []byte, error) { return b[:i], b[i:], nil } } + return b, b[len(b):], nil } @@ -57,38 +61,44 @@ func scanLiteralString(b []byte) ([]byte, []byte, error) { return nil, nil, newDecodeError(b[i:i+1], "literal strings cannot have new lines") } } + return nil, nil, newDecodeError(b[len(b):], "unterminated literal string") } func scanMultilineLiteralString(b []byte) ([]byte, []byte, error) { - //ml-literal-string = ml-literal-string-delim [ newline ] ml-literal-body - //ml-literal-string-delim - //ml-literal-string-delim = 3apostrophe - //ml-literal-body = *mll-content *( mll-quotes 1*mll-content ) [ mll-quotes ] + // ml-literal-string = ml-literal-string-delim [ newline ] ml-literal-body + // ml-literal-string-delim + // ml-literal-string-delim = 3apostrophe + // ml-literal-body = *mll-content *( mll-quotes 1*mll-content ) [ mll-quotes ] // - //mll-content = mll-char / newline - //mll-char = %x09 / %x20-26 / %x28-7E / non-ascii - //mll-quotes = 1*2apostrophe + // mll-content = mll-char / newline + // mll-char = %x09 / %x20-26 / %x28-7E / non-ascii + // mll-quotes = 1*2apostrophe for i := 3; i < len(b); i++ { - switch b[i] { - case '\'': - if scanFollowsMultilineLiteralStringDelimiter(b[i:]) { - return b[:i+3], b[i+3:], nil - } + if b[i] == '\'' && scanFollowsMultilineLiteralStringDelimiter(b[i:]) { + return b[:i+3], b[i+3:], nil } } return nil, nil, newDecodeError(b[len(b):], `multiline literal string not terminated by '''`) } +var ( + errWindowsNewLineMissing = errors.New(`windows new line missing \n`) + errWindowsNewLineCRLF = errors.New(`windows new line should be \r\n`) +) + func scanWindowsNewline(b []byte) ([]byte, []byte, error) { - if len(b) < 2 { - return nil, nil, fmt.Errorf(`windows new line missing \n`) + const lenLF = 2 + if len(b) < lenLF { + return nil, nil, errWindowsNewLineMissing } + if b[1] != '\n' { - return nil, nil, fmt.Errorf(`windows new line should be \r\n`) + return nil, nil, errWindowsNewLineCRLF } - return b[:2], b[2:], nil + + return b[:lenLF], b[lenLF:], nil } func scanWhitespace(b []byte) ([]byte, []byte) { @@ -100,27 +110,31 @@ func scanWhitespace(b []byte) ([]byte, []byte) { return b[:i], b[i:] } } + return b, b[len(b):] } -func scanComment(b []byte) ([]byte, []byte, error) { - //;; Comment +//nolint:unparam +func scanComment(b []byte) ([]byte, []byte) { + // ;; Comment // - //comment-start-symbol = %x23 ; # - //non-ascii = %x80-D7FF / %xE000-10FFFF - //non-eol = %x09 / %x20-7F / non-ascii + // comment-start-symbol = %x23 ; # + // non-ascii = %x80-D7FF / %xE000-10FFFF + // non-eol = %x09 / %x20-7F / non-ascii // - //comment = comment-start-symbol *non-eol - + // comment = comment-start-symbol *non-eol for i := 1; i < len(b); i++ { - switch b[i] { - case '\n': - return b[:i], b[i:], nil + if b[i] == '\n' { + return b[:i], b[i:] } } - return b, nil, nil + + return b, nil } +var errBasicLineNotTerminatedByQuote = errors.New(`basic string not terminated by "`) + +//nolint:godox // TODO perform validation on the string? func scanBasicString(b []byte) ([]byte, []byte, error) { // basic-string = quotation-mark *basic-char quotation-mark @@ -142,22 +156,22 @@ func scanBasicString(b []byte) ([]byte, []byte, error) { } } - return nil, nil, fmt.Errorf(`basic string not terminated by "`) + return nil, nil, errBasicLineNotTerminatedByQuote } +//nolint:godox // TODO perform validation on the string? func scanMultilineBasicString(b []byte) ([]byte, []byte, error) { - //ml-basic-string = ml-basic-string-delim [ newline ] ml-basic-body - //ml-basic-string-delim - //ml-basic-string-delim = 3quotation-mark - //ml-basic-body = *mlb-content *( mlb-quotes 1*mlb-content ) [ mlb-quotes ] + // ml-basic-string = ml-basic-string-delim [ newline ] ml-basic-body + // ml-basic-string-delim + // ml-basic-string-delim = 3quotation-mark + // ml-basic-body = *mlb-content *( mlb-quotes 1*mlb-content ) [ mlb-quotes ] // - //mlb-content = mlb-char / newline / mlb-escaped-nl - //mlb-char = mlb-unescaped / escaped - //mlb-quotes = 1*2quotation-mark - //mlb-unescaped = wschar / %x21 / %x23-5B / %x5D-7E / non-ascii - //mlb-escaped-nl = escape ws newline *( wschar / newline ) - + // mlb-content = mlb-char / newline / mlb-escaped-nl + // mlb-char = mlb-unescaped / escaped + // mlb-quotes = 1*2quotation-mark + // mlb-unescaped = wschar / %x21 / %x23-5B / %x5D-7E / non-ascii + // mlb-escaped-nl = escape ws newline *( wschar / newline ) for i := 3; i < len(b); i++ { switch b[i] { case '"': diff --git a/unmarshaler.go b/unmarshaler.go index b4d5954b..54604578 100644 --- a/unmarshaler.go +++ b/unmarshaler.go @@ -2,6 +2,7 @@ package toml import ( "encoding" + "errors" "fmt" "io" "io/ioutil" @@ -17,6 +18,7 @@ func Unmarshal(data []byte, v interface{}) error { p := parser{} p.Reset(data) d := decoder{} + return d.FromParser(&p, v) } @@ -54,8 +56,9 @@ func (d *Decoder) SetStrict(strict bool) { func (d *Decoder) Decode(v interface{}) error { b, err := ioutil.ReadAll(d.r) if err != nil { - return err + return fmt.Errorf("Decode: %w", err) } + p := parser{} p.Reset(b) dec := decoder{ @@ -63,6 +66,7 @@ func (d *Decoder) Decode(v interface{}) error { Enabled: d.strict, }, } + return dec.FromParser(&p, v) } @@ -90,19 +94,19 @@ func (d *decoder) arrayIndex(append bool, v reflect.Value) int { idx++ d.arrayIndexes[v] = idx } + return idx } func (d *decoder) FromParser(p *parser, v interface{}) error { err := d.fromParser(p, v) - if err != nil { - de, ok := err.(*decodeError) - if ok { - err = wrapDecodeError(p.data, de) - } - } if err == nil { - err = d.strict.Error(p.data) + return d.strict.Error(p.data) + } + + var e *decodeError + if errors.As(err, &e) { + return wrapDecodeError(p.data, e) } return err @@ -110,29 +114,43 @@ func (d *decoder) FromParser(p *parser, v interface{}) error { func keyLocation(node ast.Node) []byte { k := node.Key() + hasOne := k.Next() if !hasOne { panic("should not be called with empty key") } + start := k.Node().Data end := k.Node().Data + for k.Next() { end = k.Node().Data } + return unsafe.BytesRange(start, end) } +var ( + errFromParserExpectingPointer = errors.New("expecting a pointer as target") + errFromParserExpectingNonNilPointer = errors.New("expecting non nil pointer as target") +) + +//nolint:funlen,cyclop func (d *decoder) fromParser(p *parser, v interface{}) error { r := reflect.ValueOf(v) if r.Kind() != reflect.Ptr { - return fmt.Errorf("need to target a pointer, not %s", r.Kind()) + return fmt.Errorf("fromParser: %w, not %s", errFromParserExpectingPointer, r.Kind()) } + if r.IsNil() { - return fmt.Errorf("target pointer must be non-nil") + return errFromParserExpectingNonNilPointer } - var skipUntilTable bool - var root target = valueTarget(r.Elem()) + var ( + skipUntilTable bool + root target = valueTarget(r.Elem()) + ) + current := root for p.NextExpression() { @@ -144,10 +162,11 @@ func (d *decoder) fromParser(p *parser, v interface{}) error { err := d.seen.CheckExpression(node) if err != nil { - return err + return fmt.Errorf("fromParser: %w", err) } var found bool + switch node.Kind { case ast.KeyValue: err = d.unmarshalKeyValue(current, node) @@ -167,7 +186,7 @@ func (d *decoder) fromParser(p *parser, v interface{}) error { d.strict.EnterArrayTable(node) current, found, err = d.scopeWithArrayTable(root, node.Key()) default: - panic(fmt.Errorf("this should not be a top level node type: %s", node.Kind)) + panic(fmt.Sprintf("fromParser: this should not be a top level node type: %s", node.Kind)) } if err != nil { @@ -176,6 +195,7 @@ func (d *decoder) fromParser(p *parser, v interface{}) error { if !found { skipUntilTable = true + d.strict.MissingTable(node) } } @@ -192,38 +212,49 @@ func (d *decoder) fromParser(p *parser, v interface{}) error { // When encountering slices, it should always use its last element, and error // if the slice does not have any. func (d *decoder) scopeWithKey(x target, key ast.Iterator) (target, bool, error) { - var err error - found := true + var ( + err error + found bool + ) for key.Next() { n := key.Node() + x, found, err = d.scopeTableTarget(false, x, string(n.Data)) if err != nil || !found { return nil, found, err } } + return x, true, nil } +//nolint:cyclop // scopeWithArrayTable performs target scoping when unmarshaling an // ast.ArrayTable node. // // It is the same as scopeWithKey, but when scoping the last part of the key // it creates a new element in the array instead of using the last one. func (d *decoder) scopeWithArrayTable(x target, key ast.Iterator) (target, bool, error) { - var err error - found := true + var ( + err error + found bool + ) + for key.Next() { n := key.Node() if !n.Next().Valid() { // want to stop at one before last break } + x, found, err = d.scopeTableTarget(false, x, string(n.Data)) if err != nil || !found { return nil, found, err } } + n := key.Node() + x, found, err = d.scopeTableTarget(false, x, string(n.Data)) if err != nil || !found { return x, found, err @@ -236,6 +267,7 @@ func (d *decoder) scopeWithArrayTable(x target, key ast.Iterator) (target, bool, if err != nil { return x, false, err } + v = x.get() } @@ -244,6 +276,7 @@ func (d *decoder) scopeWithArrayTable(x target, key ast.Iterator) (target, bool, if err != nil { return x, found, err } + v = x.get() } @@ -252,6 +285,7 @@ func (d *decoder) scopeWithArrayTable(x target, key ast.Iterator) (target, bool, x, err = scopeSlice(true, x) case reflect.Array: x, err = d.scopeArray(true, x) + default: } return x, found, err @@ -295,22 +329,28 @@ func tryTextUnmarshaler(x target, node ast.Node) (bool, error) { if v.Type().Implements(textUnmarshalerType) { return true, v.Interface().(encoding.TextUnmarshaler).UnmarshalText(node.Data) } + if v.CanAddr() && v.Addr().Type().Implements(textUnmarshalerType) { return true, v.Addr().Interface().(encoding.TextUnmarshaler).UnmarshalText(node.Data) } + return false, nil } +//nolint:cyclop func (d *decoder) unmarshalValue(x target, node ast.Node) error { v := x.get() + if v.Kind() == reflect.Ptr { if !v.Elem().IsValid() { err := x.set(reflect.New(v.Type().Elem())) if err != nil { return err } + v = x.get() } + return d.unmarshalValue(valueTarget(v.Elem()), node) } @@ -339,37 +379,44 @@ func (d *decoder) unmarshalValue(x target, node ast.Node) error { case ast.LocalDate: return unmarshalLocalDate(x, node) default: - panic(fmt.Errorf("unhandled unmarshalValue kind %s", node.Kind)) + panic(fmt.Sprintf("unmarshalValue: unhandled unmarshalValue kind %s", node.Kind)) } } func unmarshalLocalDate(x target, node ast.Node) error { assertNode(ast.LocalDate, node) + v, err := parseLocalDate(node.Data) if err != nil { return err } + return setDate(x, v) } func unmarshalLocalDateTime(x target, node ast.Node) error { assertNode(ast.LocalDateTime, node) + v, rest, err := parseLocalDateTime(node.Data) if err != nil { return err } + if len(rest) > 0 { return newDecodeError(rest, "extra characters at the end of a local date time") } + return setLocalDateTime(x, v) } func unmarshalDateTime(x target, node ast.Node) error { assertNode(ast.DateTime, node) + v, err := parseDateTime(node.Data) if err != nil { return err } + return setDateTime(x, v) } @@ -378,6 +425,7 @@ func setLocalDateTime(x target, v LocalDateTime) error { cast := v.In(time.Local) return setDateTime(x, cast) } + return x.set(reflect.ValueOf(v)) } @@ -398,21 +446,25 @@ func setDate(x target, v LocalDate) error { func unmarshalString(x target, node ast.Node) error { assertNode(ast.String, node) + return setString(x, string(node.Data)) } func unmarshalBool(x target, node ast.Node) error { assertNode(ast.Bool, node) v := node.Data[0] == 't' + return setBool(x, v) } func unmarshalInteger(x target, node ast.Node) error { assertNode(ast.Integer, node) + v, err := parseInteger(node.Data) if err != nil { return err } + return setInt64(x, v) } @@ -422,6 +474,7 @@ func unmarshalFloat(x target, node ast.Node) error { if err != nil { return err } + return setFloat64(x, v) } @@ -433,11 +486,13 @@ func (d *decoder) unmarshalInlineTable(x target, node ast.Node) error { it := node.Children() for it.Next() { n := it.Node() + err := d.unmarshalKeyValue(x, n) if err != nil { return err } } + return nil } @@ -449,30 +504,36 @@ func (d *decoder) unmarshalArray(x target, node ast.Node) error { return err } - it := node.Children() idx := 0 + + it := node.Children() for it.Next() { n := it.Node() + v, err := elementAt(x, idx) if err != nil { return err } + if v == nil { // when we go out of bound for an array just stop processing it to // mimic encoding/json break } + err = d.unmarshalValue(v, n) if err != nil { return err } + idx++ } + return nil } func assertNode(expected ast.Kind, node ast.Node) { if node.Kind != expected { - panic(fmt.Errorf("expected node of kind %s, not %s", expected, node.Kind)) + panic(fmt.Sprintf("expected node of kind %s, not %s", expected, node.Kind)) } } diff --git a/unmarshaler_test.go b/unmarshaler_test.go index b0f62d23..45f6e589 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -1,6 +1,7 @@ package toml_test import ( + "errors" "fmt" "math" "strconv" @@ -14,6 +15,8 @@ import ( ) func TestUnmarshal_Integers(t *testing.T) { + t.Parallel() + examples := []struct { desc string input string @@ -62,7 +65,10 @@ func TestUnmarshal_Integers(t *testing.T) { } for _, e := range examples { + e := e t.Run(e.desc, func(t *testing.T) { + t.Parallel() + doc := doc{} err := toml.Unmarshal([]byte(`A = `+e.input), &doc) require.NoError(t, err) @@ -71,7 +77,10 @@ func TestUnmarshal_Integers(t *testing.T) { } } +//nolint:funlen func TestUnmarshal_Floats(t *testing.T) { + t.Parallel() + examples := []struct { desc string input string @@ -161,7 +170,10 @@ func TestUnmarshal_Floats(t *testing.T) { } for _, e := range examples { + e := e t.Run(e.desc, func(t *testing.T) { + t.Parallel() + doc := doc{} err := toml.Unmarshal([]byte(`A = `+e.input), &doc) require.NoError(t, err) @@ -174,7 +186,10 @@ func TestUnmarshal_Floats(t *testing.T) { } } +//nolint:funlen func TestUnmarshal(t *testing.T) { + t.Parallel() + type test struct { target interface{} expected interface{} @@ -193,6 +208,7 @@ func TestUnmarshal(t *testing.T) { type doc struct { A string } + return test{ target: &doc{}, expected: &doc{A: "foo"}, @@ -205,6 +221,7 @@ func TestUnmarshal(t *testing.T) { fruit . flavor = "banana"`, gen: func() test { m := map[string]interface{}{} + return test{ target: &m, expected: &map[string]interface{}{ @@ -222,6 +239,7 @@ func TestUnmarshal(t *testing.T) { "\"b\"" = 2`, gen: func() test { m := map[string]interface{}{} + return test{ target: &m, expected: &map[string]interface{}{ @@ -239,6 +257,7 @@ func TestUnmarshal(t *testing.T) { type doc struct { A string } + return test{ target: &doc{}, expected: &doc{A: "Test"}, @@ -252,6 +271,7 @@ func TestUnmarshal(t *testing.T) { type doc struct { A bool } + return test{ target: &doc{}, expected: &doc{A: true}, @@ -265,6 +285,7 @@ func TestUnmarshal(t *testing.T) { type doc struct { A bool } + return test{ target: &doc{A: true}, expected: &doc{A: false}, @@ -278,6 +299,7 @@ func TestUnmarshal(t *testing.T) { type doc struct { A []string } + return test{ target: &doc{}, expected: &doc{A: []string{"foo", "bar"}}, @@ -295,6 +317,7 @@ B = "data"`, type doc struct { A A } + return test{ target: &doc{}, expected: &doc{A: A{B: "data"}}, @@ -306,6 +329,7 @@ B = "data"`, input: `[A]`, gen: func() test { var v map[string]interface{} + return test{ target: &v, expected: &map[string]interface{}{`A`: map[string]interface{}{}}, @@ -323,6 +347,7 @@ B = "data"`, type doc struct { Name name } + return test{ target: &doc{}, expected: &doc{Name: name{ @@ -337,6 +362,7 @@ B = "data"`, input: `A = {}`, gen: func() test { var v map[string]interface{} + return test{ target: &v, expected: &map[string]interface{}{`A`: map[string]interface{}{}}, @@ -354,6 +380,7 @@ B = "data"`, type doc struct { Names []name } + return test{ target: &doc{}, expected: &doc{ @@ -376,6 +403,7 @@ B = "data"`, input: `A = "foo"`, gen: func() test { doc := map[string]interface{}{} + return test{ target: &doc, expected: &map[string]interface{}{ @@ -390,6 +418,7 @@ B = "data"`, B = 42`, gen: func() test { doc := map[string]interface{}{} + return test{ target: &doc, expected: &map[string]interface{}{ @@ -404,6 +433,7 @@ B = "data"`, input: `A = ["foo", "bar"]`, gen: func() test { doc := map[string]interface{}{} + return test{ target: &doc, expected: &map[string]interface{}{ @@ -417,6 +447,7 @@ B = "data"`, input: `A = "foo"`, gen: func() test { doc := map[string]string{} + return test{ target: &doc, expected: &map[string]string{ @@ -430,6 +461,7 @@ B = "data"`, input: `A = 42.0`, gen: func() test { doc := map[string]string{} + return test{ target: &doc, err: true, @@ -447,6 +479,7 @@ B = "data"`, type Doc struct { First []First } + return test{ target: &Doc{}, expected: &Doc{ @@ -464,13 +497,13 @@ B = "data"`, input: `[[Products]] Name = "Hammer" Sku = 738594937 - + [[Products]] # empty table within the array - + [[Products]] Name = "Nail" Sku = 284758393 - + Color = "gray"`, gen: func() test { type Product struct { @@ -481,6 +514,7 @@ B = "data"`, type Doc struct { Products []Product } + return test{ target: &Doc{}, expected: &Doc{ @@ -498,13 +532,13 @@ B = "data"`, input: `[[Products]] Name = "Hammer" Sku = 738594937 - + [[Products]] # empty table within the array - + [[Products]] Name = "Nail" Sku = 284758393 - + Color = "gray"`, gen: func() test { return test{ @@ -654,6 +688,7 @@ B = "data"`, A *[]*string } hello := "Hello" + return test{ target: &doc{}, expected: &doc{ @@ -673,6 +708,7 @@ B = "data"`, type doc struct { A interface{} } + return test{ target: &doc{ A: inner{ @@ -700,6 +736,7 @@ B = "data"`, type doc struct { A [4]inner } + return test{ target: &doc{}, expected: &doc{ @@ -716,6 +753,7 @@ B = "data"`, input: "A = 1\r\n\r\nB = 2", gen: func() test { doc := map[string]interface{}{} + return test{ target: &doc, expected: &map[string]interface{}{ @@ -730,6 +768,7 @@ B = "data"`, input: "A = 1\r", gen: func() test { doc := map[string]interface{}{} + return test{ target: &doc, err: true, @@ -741,6 +780,7 @@ B = "data"`, input: "A = 1\rB = 2", gen: func() test { doc := map[string]interface{}{} + return test{ target: &doc, err: true, @@ -752,6 +792,7 @@ B = "data"`, input: `a = 1z = 2`, gen: func() test { m := map[string]interface{}{} + return test{ target: &m, err: true, @@ -761,7 +802,10 @@ B = "data"`, } for _, e := range examples { + e := e t.Run(e.desc, func(t *testing.T) { + t.Parallel() + if e.skip { t.Skip() } @@ -791,7 +835,7 @@ func (i Integer484) MarshalText() ([]byte, error) { func (i *Integer484) UnmarshalText(data []byte) error { conv, err := strconv.Atoi(string(data)) if err != nil { - return err + return fmt.Errorf("UnmarshalText: %w", err) } i.Value = conv return nil @@ -803,6 +847,7 @@ type Config484 struct { func TestIssue484(t *testing.T) { raw := []byte(`integers = ["1","2","3","100"]`) + var cfg Config484 err := toml.Unmarshal(raw, &cfg) require.NoError(t, err) @@ -864,6 +909,7 @@ func TestIssue494(t *testing.T) { foo = 2021-04-08 bar = 2021-04-08 ` + type s struct { Foo time.Time `toml:"foo"` Bar time.Time `toml:"bar"` @@ -880,7 +926,10 @@ func TestIssue507(t *testing.T) { require.Error(t, err) } +//nolint:funlen func TestUnmarshalDecodeErrors(t *testing.T) { + t.Parallel() + examples := []struct { desc string data string @@ -955,14 +1004,19 @@ world'`, } for _, e := range examples { + e := e t.Run(e.desc, func(t *testing.T) { + t.Parallel() + m := map[string]interface{}{} err := toml.Unmarshal([]byte(e.data), &m) require.Error(t, err) - de, ok := err.(*toml.DecodeError) - if !ok { + + var de *toml.DecodeError + if !errors.As(err, &de) { t.Fatalf("err should have been a *toml.DecodeError, but got %s (%T)", err, err) } + if e.msg != "" { t.Log("\n" + de.String()) require.Equal(t, e.msg, de.Error()) @@ -971,6 +1025,7 @@ world'`, } } +//nolint:funlen func TestLocalDateTime(t *testing.T) { t.Parallel() @@ -1058,6 +1113,7 @@ func TestIssue508(t *testing.T) { type head struct { Title string `toml:"title"` } + type text struct { head } @@ -1070,7 +1126,10 @@ func TestIssue508(t *testing.T) { require.Equal(t, "This is a title", t1.head.Title) } +//nolint:funlen func TestDecoderStrict(t *testing.T) { + t.Parallel() + examples := []struct { desc string input string @@ -1139,7 +1198,10 @@ bar = 42 } for _, e := range examples { + e := e t.Run(e.desc, func(t *testing.T) { + t.Parallel() + r := strings.NewReader(e.input) d := toml.NewDecoder(r) d.SetStrict(true) @@ -1148,8 +1210,13 @@ bar = 42 x = &struct{}{} } err := d.Decode(x) - details := err.(*toml.StrictMissingError) - equalStringsIgnoreNewlines(t, e.expected, details.String()) + + var tsm *toml.StrictMissingError + if errors.As(err, &tsm) { + equalStringsIgnoreNewlines(t, e.expected, tsm.String()) + } else { + t.Fatalf("err should have been a *toml.StrictMissingError, but got %s (%T)", err, err) + } }) } } @@ -1171,11 +1238,15 @@ key3 = "value3" err := d.Decode(&s) fmt.Println(err.Error()) - // Output: strict mode: fields in the document are missing in the target struct - details := err.(*toml.StrictMissingError) + var details *toml.StrictMissingError + if !errors.As(err, &details) { + panic(fmt.Sprintf("err should have been a *toml.StrictMissingError, but got %s (%T)", err, err)) + } + fmt.Println(details.String()) - // Ouput: + // Output: + // strict mode: fields in the document are missing in the target struct // 2| key1 = "value1" // 3| key2 = "value2" // | ~~~~ missing field From 3f2bb0b36386a476cc920330c97abc7451b0bac4 Mon Sep 17 00:00:00 2001 From: Vincent Serpoul Date: Fri, 7 May 2021 10:29:21 +0800 Subject: [PATCH 190/228] golangci-lint (#530) --- .golangci.toml | 2 +- localtime_test.go | 11 + parser.go | 4 +- targets.go | 408 +++++++++++++++++++++++++++-------- targets_test.go | 29 ++- toml_testgen_support_test.go | 52 +++-- toml_testgen_test.go | 152 ++++++++++++- unmarshaler.go | 56 ++++- unmarshaler_test.go | 15 ++ 9 files changed, 611 insertions(+), 118 deletions(-) diff --git a/.golangci.toml b/.golangci.toml index a38c60b0..0e71b204 100644 --- a/.golangci.toml +++ b/.golangci.toml @@ -79,6 +79,6 @@ enable = [ "varcheck", "wastedassign", "whitespace", - "wrapcheck", + # "wrapcheck", "wsl" ] diff --git a/localtime_test.go b/localtime_test.go index a103bc06..67415041 100644 --- a/localtime_test.go +++ b/localtime_test.go @@ -56,6 +56,7 @@ func TestDates(t *testing.T) { if got := test.date.String(); got != test.wantStr { t.Errorf("%#v.String() = %q, want %q", test.date, got, test.wantStr) } + if got := test.date.In(test.loc); !got.Equal(test.wantTime) { t.Errorf("%#v.In(%v) = %v, want %v", test.date, test.loc, got, test.wantTime) } @@ -109,6 +110,7 @@ func TestParseDate(t *testing.T) { if got != test.want { t.Errorf("ParseLocalDate(%q) = %+v, want %+v", test.str, got, test.want) } + if err != nil && test.want != (emptyDate) { t.Errorf("Unexpected error %v from ParseLocalDate(%q)", err, test.str) } @@ -170,6 +172,7 @@ func TestDateArithmetic(t *testing.T) { if got := test.start.AddDays(test.days); got != test.end { t.Errorf("[%s] %#v.AddDays(%v) = %#v, want %#v", test.desc, test.start, test.days, got, test.end) } + if got := test.end.DaysSince(test.start); got != test.days { t.Errorf("[%s] %#v.Sub(%#v) = %v, want %v", test.desc, test.end, test.start, got, test.days) } @@ -231,9 +234,11 @@ func TestTimeToString(t *testing.T) { continue } + if gotTime != test.time { t.Errorf("ParseLocalTime(%q) = %+v, want %+v", test.str, gotTime, test.time) } + if test.roundTrip { gotStr := test.time.String() if gotStr != test.str { @@ -303,9 +308,11 @@ func TestDateTimeToString(t *testing.T) { continue } + if gotDateTime != test.dateTime { t.Errorf("ParseLocalDateTime(%q) = %+v, want %+v", test.str, gotDateTime, test.dateTime) } + if test.roundTrip { gotStr := test.dateTime.String() if gotStr != test.str { @@ -444,6 +451,7 @@ func TestMarshalJSON(t *testing.T) { if err != nil { t.Fatal(err) } + if got := string(bgot); got != test.want { t.Errorf("%#v: got %s, want %s", test.value, got, test.want) } @@ -472,6 +480,7 @@ func TestUnmarshalJSON(t *testing.T) { if err := json.Unmarshal([]byte(test.data), test.ptr); err != nil { t.Fatalf("%s: %v", test.data, err) } + if !cmpEqual(test.ptr, test.want) { t.Errorf("%s: got %#v, want %#v", test.data, test.ptr, test.want) } @@ -486,9 +495,11 @@ func TestUnmarshalJSON(t *testing.T) { if json.Unmarshal([]byte(bad), &d) == nil { t.Errorf("%q, LocalDate: got nil, want error", bad) } + if json.Unmarshal([]byte(bad), &tm) == nil { t.Errorf("%q, LocalTime: got nil, want error", bad) } + if json.Unmarshal([]byte(bad), &dt) == nil { t.Errorf("%q, LocalDateTime: got nil, want error", bad) } diff --git a/parser.go b/parser.go index 1ff2272f..9724190a 100644 --- a/parser.go +++ b/parser.go @@ -370,7 +370,7 @@ func (p *parser) parseInlineTable(b []byte) (ast.Reference, []byte, error) { return parent, rest, err } -var errArrayCanNotStartWithComma = errors.New("array cannot start with comma") +var errArrayCannotStartWithComma = errors.New("array cannot start with comma") //nolint:funlen,cyclop func (p *parser) parseValArray(b []byte) (ast.Reference, []byte, error) { @@ -409,7 +409,7 @@ func (p *parser) parseValArray(b []byte) (ast.Reference, []byte, error) { if b[0] == ',' { if first { - return parent, nil, errArrayCanNotStartWithComma + return parent, nil, errArrayCannotStartWithComma } b = b[1:] diff --git a/targets.go b/targets.go index b25b2039..68f94287 100644 --- a/targets.go +++ b/targets.go @@ -1,6 +1,7 @@ package toml import ( + "errors" "fmt" "math" "reflect" @@ -38,26 +39,31 @@ func (t valueTarget) get() reflect.Value { func (t valueTarget) set(v reflect.Value) error { reflect.Value(t).Set(v) + return nil } func (t valueTarget) setString(v string) error { t.get().SetString(v) + return nil } func (t valueTarget) setBool(v bool) error { t.get().SetBool(v) + return nil } func (t valueTarget) setInt64(v int64) error { t.get().SetInt(v) + return nil } func (t valueTarget) setFloat64(v float64) error { t.get().SetFloat(v) + return nil } @@ -71,23 +77,48 @@ func (t interfaceTarget) get() reflect.Value { } func (t interfaceTarget) set(v reflect.Value) error { - return t.x.set(v) + err := t.x.set(v) + if err != nil { + return fmt.Errorf("interfaceTarget set: %w", err) + } + + return nil } func (t interfaceTarget) setString(v string) error { - return t.x.setString(v) + err := t.x.setString(v) + if err != nil { + return fmt.Errorf("interfaceTarget setString: %w", err) + } + + return nil } func (t interfaceTarget) setBool(v bool) error { - return t.x.setBool(v) + err := t.x.setBool(v) + if err != nil { + return fmt.Errorf("interfaceTarget setBool: %w", err) + } + + return nil } func (t interfaceTarget) setInt64(v int64) error { - return t.x.setInt64(v) + err := t.x.setInt64(v) + if err != nil { + return fmt.Errorf("interfaceTarget setInt64: %w", err) + } + + return nil } func (t interfaceTarget) setFloat64(v float64) error { - return t.x.setFloat64(v) + err := t.x.setFloat64(v) + if err != nil { + return fmt.Errorf("interfaceTarget setFloat64: %w", err) + } + + return nil } // mapTarget targets a specific key of a map. @@ -102,6 +133,7 @@ func (t mapTarget) get() reflect.Value { func (t mapTarget) set(v reflect.Value) error { t.v.SetMapIndex(t.k, v) + return nil } @@ -121,6 +153,12 @@ func (t mapTarget) setFloat64(v float64) error { return t.set(reflect.ValueOf(v)) } +var ( + errValIndexExpectingSlice = errors.New("expecting a slice") + errValIndexCannotInitSlice = errors.New("cannot initialize a slice") +) + +//nolint:cyclop // makes sure that the value pointed at by t is indexable (Slice, Array), or // dereferences to an indexable (Ptr, Interface). func ensureValueIndexable(t target) error { @@ -129,159 +167,319 @@ func ensureValueIndexable(t target) error { switch f.Type().Kind() { case reflect.Slice: if f.IsNil() { - return t.set(reflect.MakeSlice(f.Type(), 0, 0)) + err := t.set(reflect.MakeSlice(f.Type(), 0, 0)) + if err != nil { + return fmt.Errorf("ensureValueIndexable: %w", err) + } + + return nil } case reflect.Interface: if f.IsNil() || f.Elem().Type() != sliceInterfaceType { - return t.set(reflect.MakeSlice(sliceInterfaceType, 0, 0)) + err := t.set(reflect.MakeSlice(sliceInterfaceType, 0, 0)) + if err != nil { + return fmt.Errorf("ensureValueIndexable: %w", err) + } + + return nil } + if f.Elem().Type().Kind() != reflect.Slice { - return fmt.Errorf("interface is pointing to a %s, not a slice", f.Kind()) + return fmt.Errorf("ensureValueIndexable: %w, not a %s", errValIndexExpectingSlice, f.Kind()) } case reflect.Ptr: if f.IsNil() { ptr := reflect.New(f.Type().Elem()) + err := t.set(ptr) if err != nil { - return err + return fmt.Errorf("ensureValueIndexable: %w", err) } + f = t.get() } + return ensureValueIndexable(valueTarget(f.Elem())) case reflect.Array: // arrays are always initialized. default: - return fmt.Errorf("cannot initialize a slice in %s", f.Kind()) + return fmt.Errorf("ensureValueIndexable: %w with %s", errValIndexCannotInitSlice, f.Kind()) } + return nil } -var sliceInterfaceType = reflect.TypeOf([]interface{}{}) -var mapStringInterfaceType = reflect.TypeOf(map[string]interface{}{}) +var ( + sliceInterfaceType = reflect.TypeOf([]interface{}{}) + mapStringInterfaceType = reflect.TypeOf(map[string]interface{}{}) +) -func ensureMapIfInterface(x target) { +func ensureMapIfInterface(x target) error { v := x.get() + if v.Kind() == reflect.Interface && v.IsNil() { newElement := reflect.MakeMap(mapStringInterfaceType) - x.set(newElement) + + err := x.set(newElement) + if err != nil { + return fmt.Errorf("ensureMapIfInterface: %w", err) + } } + + return nil } +var errSetStringCannotAssignString = errors.New("cannot assign string") + func setString(t target, v string) error { f := t.get() switch f.Kind() { case reflect.String: - return t.setString(v) + err := t.setString(v) + if err != nil { + return fmt.Errorf("setString: %w", err) + } + + return nil case reflect.Interface: - return t.set(reflect.ValueOf(v)) + err := t.set(reflect.ValueOf(v)) + if err != nil { + return fmt.Errorf("setString: %w", err) + } + + return nil default: - return fmt.Errorf("cannot assign string to a %s", f.Kind()) + return fmt.Errorf("setString: %w to a %s", errSetStringCannotAssignString, f.Kind()) } } +var errSetBoolCannotAssignBool = errors.New("cannot assign bool") + func setBool(t target, v bool) error { f := t.get() switch f.Kind() { case reflect.Bool: - return t.setBool(v) + err := t.setBool(v) + if err != nil { + return fmt.Errorf("setBool: %w", err) + } + + return nil case reflect.Interface: - return t.set(reflect.ValueOf(v)) + err := t.set(reflect.ValueOf(v)) + if err != nil { + return fmt.Errorf("setBool: %w", err) + } + + return nil default: - return fmt.Errorf("cannot assign bool to a %s", f.String()) + return fmt.Errorf("setBool: %w to a %s", errSetBoolCannotAssignBool, f.String()) } } -const maxInt = int64(^uint(0) >> 1) -const minInt = -maxInt - 1 +const ( + maxInt = int64(^uint(0) >> 1) + minInt = -maxInt - 1 +) +var ( + errSetInt64InInt32 = errors.New("does not fit in an int32") + errSetInt64InInt16 = errors.New("does not fit in an int16") + errSetInt64InInt8 = errors.New("does not fit in an int8") + errSetInt64InInt = errors.New("does not fit in an int") + errSetInt64InUint64 = errors.New("negative integer does not fit in an uint64") + errSetInt64InUint32 = errors.New("negative integer does not fit in an uint32") + errSetInt64InUint32Max = errors.New("integer does not fit in an uint32") + errSetInt64InUint16 = errors.New("negative integer does not fit in an uint16") + errSetInt64InUint16Max = errors.New("integer does not fit in an uint16") + errSetInt64InUint8 = errors.New("negative integer does not fit in an uint8") + errSetInt64InUint8Max = errors.New("integer does not fit in an uint8") + errSetInt64InUint = errors.New("negative integer does not fit in an uint") + errSetInt64Unknown = errors.New("does not fit in an uint") +) + +//nolint:funlen,gocognit,cyclop,gocyclo func setInt64(t target, v int64) error { f := t.get() switch f.Kind() { case reflect.Int64: - return t.setInt64(v) + err := t.setInt64(v) + if err != nil { + return fmt.Errorf("setInt64: %w", err) + } + + return nil case reflect.Int32: if v < math.MinInt32 || v > math.MaxInt32 { - return fmt.Errorf("integer %d does not fit in an int32", v) + return fmt.Errorf("setInt64: integer %d %w", v, errSetInt64InInt32) } - return t.set(reflect.ValueOf(int32(v))) + + err := t.set(reflect.ValueOf(int32(v))) + if err != nil { + return fmt.Errorf("setInt64: %w", err) + } + + return nil case reflect.Int16: if v < math.MinInt16 || v > math.MaxInt16 { - return fmt.Errorf("integer %d does not fit in an int16", v) + return fmt.Errorf("setInt64: integer %d %w", v, errSetInt64InInt16) } - return t.set(reflect.ValueOf(int16(v))) + + err := t.set(reflect.ValueOf(int16(v))) + if err != nil { + return fmt.Errorf("setInt64: %w", err) + } + + return nil case reflect.Int8: if v < math.MinInt8 || v > math.MaxInt8 { - return fmt.Errorf("integer %d does not fit in an int8", v) + return fmt.Errorf("setInt64: integer %d %w", v, errSetInt64InInt8) + } + + err := t.set(reflect.ValueOf(int8(v))) + if err != nil { + return fmt.Errorf("setInt64: %w", err) } - return t.set(reflect.ValueOf(int8(v))) + + return nil case reflect.Int: if v < minInt || v > maxInt { - return fmt.Errorf("integer %d does not fit in an int", v) + return fmt.Errorf("setInt64: integer %d %w", v, errSetInt64InInt) + } + + err := t.set(reflect.ValueOf(int(v))) + if err != nil { + return fmt.Errorf("setInt64: %w", err) } - return t.set(reflect.ValueOf(int(v))) + return nil case reflect.Uint64: if v < 0 { - return fmt.Errorf("negative integer %d cannot be stored in an uint64", v) + return fmt.Errorf("setInt64: %d, %w", v, errSetInt64InUint64) + } + + err := t.set(reflect.ValueOf(uint64(v))) + if err != nil { + return fmt.Errorf("setInt64: %w", err) } - return t.set(reflect.ValueOf(uint64(v))) + + return nil case reflect.Uint32: if v < 0 { - return fmt.Errorf("negative integer %d cannot be stored in an uint32", v) + return fmt.Errorf("setInt64: %d, %w", v, errSetInt64InUint32) } + if v > math.MaxUint32 { - return fmt.Errorf("integer %d cannot be stored in an uint32", v) + return fmt.Errorf("setInt64: %d, %w", v, errSetInt64InUint32Max) + } + + err := t.set(reflect.ValueOf(uint32(v))) + if err != nil { + return fmt.Errorf("setInt64: %w", err) } - return t.set(reflect.ValueOf(uint32(v))) + + return nil case reflect.Uint16: if v < 0 { - return fmt.Errorf("negative integer %d cannot be stored in an uint16", v) + return fmt.Errorf("setInt64: %d, %w", v, errSetInt64InUint16) } + if v > math.MaxUint16 { - return fmt.Errorf("integer %d cannot be stored in an uint16", v) + return fmt.Errorf("setInt64: %d, %w", v, errSetInt64InUint16Max) } - return t.set(reflect.ValueOf(uint16(v))) + + err := t.set(reflect.ValueOf(uint16(v))) + if err != nil { + return fmt.Errorf("setInt64: %w", err) + } + + return nil case reflect.Uint8: if v < 0 { - return fmt.Errorf("negative integer %d cannot be stored in an uint8", v) + return fmt.Errorf("setInt64: %d, %w", v, errSetInt64InUint8) } + if v > math.MaxUint8 { - return fmt.Errorf("integer %d cannot be stored in an uint8", v) + return fmt.Errorf("setInt64: %d, %w", v, errSetInt64InUint8Max) + } + + err := t.set(reflect.ValueOf(uint8(v))) + if err != nil { + return fmt.Errorf("setInt64: %w", err) } - return t.set(reflect.ValueOf(uint8(v))) + + return nil case reflect.Uint: if v < 0 { - return fmt.Errorf("negative integer %d cannot be stored in an uint", v) + return fmt.Errorf("setInt64: %d, %w", v, errSetInt64InUint) } - return t.set(reflect.ValueOf(uint(v))) + + err := t.set(reflect.ValueOf(uint(v))) + if err != nil { + return fmt.Errorf("setInt64: %w", err) + } + + return nil case reflect.Interface: - return t.set(reflect.ValueOf(v)) + err := t.set(reflect.ValueOf(v)) + if err != nil { + return fmt.Errorf("setInt64: %w", err) + } + + return nil default: - return fmt.Errorf("cannot assign int64 to a %s", f.String()) + return fmt.Errorf("setInt64: %s, %w", f.String(), errSetInt64Unknown) } } +var ( + errSetFloat64InFloat32Max = errors.New("does not fit in an float32") + errSetFloat64Unknown = errors.New("does not fit in an float32") +) + func setFloat64(t target, v float64) error { f := t.get() switch f.Kind() { case reflect.Float64: - return t.setFloat64(v) + err := t.setFloat64(v) + if err != nil { + return fmt.Errorf("setFloat64: %w", err) + } + + return nil case reflect.Float32: if v > math.MaxFloat32 { - return fmt.Errorf("float %f cannot be stored in a float32", v) + return fmt.Errorf("setFloat64: %f %w", v, errSetFloat64InFloat32Max) + } + + err := t.set(reflect.ValueOf(float32(v))) + if err != nil { + return fmt.Errorf("setFloat64: %w", err) } - return t.set(reflect.ValueOf(float32(v))) + + return nil case reflect.Interface: - return t.set(reflect.ValueOf(v)) + err := t.set(reflect.ValueOf(v)) + if err != nil { + return fmt.Errorf("setFloat64: %w", err) + } + + return nil default: - return fmt.Errorf("cannot assign float64 to a %s", f.String()) + return fmt.Errorf("setFloat64: %s %w", f.String(), errSetFloat64Unknown) } } +var ( + errElementAtCannotOn = errors.New("cannot elementAt") + errElementAtCannotOnUnknown = errors.New("cannot elementAt") +) + +//nolint:cyclop // Returns the element at idx of the value pointed at by target, or an error if // t does not point to an indexable. // If the target points to an Array and idx is out of bounds, it returns @@ -291,95 +489,111 @@ func elementAt(t target, idx int) (target, error) { switch f.Kind() { case reflect.Slice: + //nolint:godox // TODO: use the idx function argument and avoid alloc if possible. idx := f.Len() + err := t.set(reflect.Append(f, reflect.New(f.Type().Elem()).Elem())) if err != nil { - return nil, err + return nil, fmt.Errorf("elementAt: %w", err) } + return valueTarget(t.get().Index(idx)), nil case reflect.Array: if idx >= f.Len() { return nil, nil } + return valueTarget(f.Index(idx)), nil case reflect.Interface: if f.IsNil() { panic("interface should have been initialized") } + ifaceElem := f.Elem() if ifaceElem.Kind() != reflect.Slice { - return nil, fmt.Errorf("cannot elementAt on a %s", f.Kind()) + return nil, fmt.Errorf("elementAt: %w on a %s", errElementAtCannotOn, f.Kind()) } + idx := ifaceElem.Len() newElem := reflect.New(ifaceElem.Type().Elem()).Elem() newSlice := reflect.Append(ifaceElem, newElem) + err := t.set(newSlice) if err != nil { - return nil, err + return nil, fmt.Errorf("elementAt: %w", err) } + return valueTarget(t.get().Elem().Index(idx)), nil case reflect.Ptr: return elementAt(valueTarget(f.Elem()), idx) default: - return nil, fmt.Errorf("cannot elementAt on a %s", f.Kind()) + return nil, fmt.Errorf("elementAt: %w on a %s", errElementAtCannotOnUnknown, f.Kind()) } } -func (d *decoder) scopeTableTarget(append bool, t target, name string) (target, bool, error) { +//nolint:cyclop +func (d *decoder) scopeTableTarget(shouldAppend bool, t target, name string) (target, bool, error) { x := t.get() switch x.Kind() { // Kinds that need to recurse - case reflect.Interface: - t, err := scopeInterface(append, t) + t, err := scopeInterface(shouldAppend, t) if err != nil { - return t, false, err + return t, false, fmt.Errorf("scopeTableTarget: %w", err) } - return d.scopeTableTarget(append, t, name) + + return d.scopeTableTarget(shouldAppend, t, name) case reflect.Ptr: t, err := scopePtr(t) if err != nil { - return t, false, err + return t, false, fmt.Errorf("scopeTableTarget: %w", err) } - return d.scopeTableTarget(append, t, name) + + return d.scopeTableTarget(shouldAppend, t, name) case reflect.Slice: - t, err := scopeSlice(append, t) + t, err := scopeSlice(shouldAppend, t) if err != nil { - return t, false, err + return t, false, fmt.Errorf("scopeTableTarget: %w", err) } - append = false - return d.scopeTableTarget(append, t, name) + shouldAppend = false + + return d.scopeTableTarget(shouldAppend, t, name) case reflect.Array: - t, err := d.scopeArray(append, t) + t, err := d.scopeArray(shouldAppend, t) if err != nil { - return t, false, err + return t, false, fmt.Errorf("scopeTableTarget: %w", err) } - append = false - return d.scopeTableTarget(append, t, name) + shouldAppend = false - // Terminal kinds + return d.scopeTableTarget(shouldAppend, t, name) + // Terminal kinds case reflect.Struct: return scopeStruct(x, name) case reflect.Map: if x.IsNil() { - t.set(reflect.MakeMap(x.Type())) + err := t.set(reflect.MakeMap(x.Type())) + if err != nil { + return t, false, fmt.Errorf("scopeTableTarget: %w", err) + } + x = t.get() } return scopeMap(x, name) default: - panic(fmt.Errorf("can't scope on a %s", x.Kind())) + panic(fmt.Sprintf("can't scope on a %s", x.Kind())) } } -func scopeInterface(append bool, t target) (target, error) { - err := initInterface(append, t) +func scopeInterface(shouldAppend bool, t target) (target, error) { + err := initInterface(shouldAppend, t) if err != nil { return t, err } + return interfaceTarget{t}, nil } @@ -388,6 +602,7 @@ func scopePtr(t target) (target, error) { if err != nil { return t, err } + return valueTarget(t.get().Elem()), nil } @@ -396,13 +611,19 @@ func initPtr(t target) error { if !x.IsNil() { return nil } - return t.set(reflect.New(x.Type().Elem())) + + err := t.set(reflect.New(x.Type().Elem())) + if err != nil { + return fmt.Errorf("initPtr: %w", err) + } + + return nil } // initInterface makes sure that the interface pointed at by the target is not // nil. // Returns the target to the initialized value of the target. -func initInterface(append bool, t target) error { +func initInterface(shouldAppend bool, t target) error { x := t.get() if x.Kind() != reflect.Interface { @@ -414,54 +635,63 @@ func initInterface(append bool, t target) error { } var newElement reflect.Value - if append { + if shouldAppend { newElement = reflect.MakeSlice(sliceInterfaceType, 0, 0) } else { newElement = reflect.MakeMap(mapStringInterfaceType) } + err := t.set(newElement) if err != nil { - return err + return fmt.Errorf("initInterface: %w", err) } return nil } -func scopeSlice(append bool, t target) (target, error) { +func scopeSlice(shouldAppend bool, t target) (target, error) { v := t.get() - if append { + if shouldAppend { newElem := reflect.New(v.Type().Elem()) newSlice := reflect.Append(v, newElem.Elem()) + err := t.set(newSlice) if err != nil { - return t, err + return t, fmt.Errorf("scopeSlice: %w", err) } + v = t.get() } + return valueTarget(v.Index(v.Len() - 1)), nil } -func (d *decoder) scopeArray(append bool, t target) (target, error) { +var errScopeArrayNotEnoughSpace = errors.New("not enough space in the array") + +func (d *decoder) scopeArray(shouldAppend bool, t target) (target, error) { v := t.get() - idx := d.arrayIndex(append, v) + idx := d.arrayIndex(shouldAppend, v) if idx >= v.Len() { - return nil, fmt.Errorf("not enough space in the array") + return nil, errScopeArrayNotEnoughSpace } return valueTarget(v.Index(idx)), nil } +var errScopeMapCannotConvertStringToKey = errors.New("cannot convert string into map key type") + func scopeMap(v reflect.Value, name string) (target, bool, error) { k := reflect.ValueOf(name) keyType := v.Type().Key() if !k.Type().AssignableTo(keyType) { if !k.Type().ConvertibleTo(keyType) { - return nil, false, fmt.Errorf("cannot convert string into map key type %s", keyType) + return nil, false, fmt.Errorf("scopeMap: %w %s", errScopeMapCannotConvertStringToKey, keyType) } + k = k.Convert(keyType) } @@ -487,6 +717,7 @@ func (c *fieldPathsCache) get(t reflect.Type) (fieldPathsMap, bool) { c.l.RLock() paths, ok := c.m[t] c.l.RUnlock() + return paths, ok } @@ -502,13 +733,14 @@ var globalFieldPathsCache = fieldPathsCache{ } func scopeStruct(v reflect.Value, name string) (target, bool, error) { + //nolint:godox // TODO: cache this, and reduce allocations - fieldPaths, ok := globalFieldPathsCache.get(v.Type()) if !ok { fieldPaths = map[string][]int{} path := make([]int, 0, 16) + var walk func(reflect.Value) walk = func(v reflect.Value) { t := v.Type() @@ -516,6 +748,7 @@ func scopeStruct(v reflect.Value, name string) (target, bool, error) { l := len(path) path = append(path, i) f := t.Field(i) + if f.Anonymous { walk(v.Field(i)) } else if f.PkgPath == "" { @@ -545,6 +778,7 @@ func scopeStruct(v reflect.Value, name string) (target, bool, error) { if !ok { path, ok = fieldPaths[strings.ToLower(name)] } + if !ok { return nil, false, nil } diff --git a/targets_test.go b/targets_test.go index 86aab96e..7b57fe06 100644 --- a/targets_test.go +++ b/targets_test.go @@ -9,6 +9,8 @@ import ( ) func TestStructTarget_Ensure(t *testing.T) { + t.Parallel() + examples := []struct { desc string input reflect.Value @@ -31,14 +33,23 @@ func TestStructTarget_Ensure(t *testing.T) { test: func(v reflect.Value, err error) { assert.NoError(t, err) require.False(t, v.IsNil()) - s := v.Interface().([]string) + + s, ok := v.Interface().([]string) + if !ok { + t.Errorf("interface %v should be castable into []string", s) + return + } + assert.Equal(t, []string{"foo"}, s) }, }, } for _, e := range examples { + e := e t.Run(e.desc, func(t *testing.T) { + t.Parallel() + d := decoder{} target, _, err := d.scopeTableTarget(false, valueTarget(e.input), e.name) require.NoError(t, err) @@ -50,6 +61,8 @@ func TestStructTarget_Ensure(t *testing.T) { } func TestStructTarget_SetString(t *testing.T) { + t.Parallel() + str := "value" examples := []struct { @@ -86,7 +99,10 @@ func TestStructTarget_SetString(t *testing.T) { } for _, e := range examples { + e := e t.Run(e.desc, func(t *testing.T) { + t.Parallel() + d := decoder{} target, _, err := d.scopeTableTarget(false, valueTarget(e.input), e.name) require.NoError(t, err) @@ -98,7 +114,11 @@ func TestStructTarget_SetString(t *testing.T) { } func TestPushNew(t *testing.T) { + t.Parallel() + t.Run("slice of strings", func(t *testing.T) { + t.Parallel() + type Doc struct { A []string } @@ -120,6 +140,8 @@ func TestPushNew(t *testing.T) { }) t.Run("slice of interfaces", func(t *testing.T) { + t.Parallel() + type Doc struct { A []interface{} } @@ -142,6 +164,8 @@ func TestPushNew(t *testing.T) { } func TestScope_Struct(t *testing.T) { + t.Parallel() + examples := []struct { desc string input reflect.Value @@ -167,7 +191,10 @@ func TestScope_Struct(t *testing.T) { } for _, e := range examples { + e := e t.Run(e.desc, func(t *testing.T) { + t.Parallel() + dec := decoder{} x, found, err := dec.scopeTableTarget(false, valueTarget(e.input), e.name) assert.Equal(t, e.found, found) diff --git a/toml_testgen_support_test.go b/toml_testgen_support_test.go index 5edd25b5..e2617e64 100644 --- a/toml_testgen_support_test.go +++ b/toml_testgen_support_test.go @@ -30,6 +30,7 @@ func testgenValid(t *testing.T, input string, jsonRef string) { t.Logf("Input TOML:\n%s", input) doc := map[string]interface{}{} + err := toml.Unmarshal([]byte(input), &doc) if err != nil { t.Fatalf("failed parsing toml: %s", err) @@ -49,25 +50,23 @@ func testgenValid(t *testing.T, input string, jsonRef string) { require.Equal(t, refDoc, doc2) } -type testGenDescNode struct { - Type string - Value interface{} -} - func testgenBuildRefDoc(jsonRef string) map[string]interface{} { descTree := map[string]interface{}{} + err := json.Unmarshal([]byte(jsonRef), &descTree) if err != nil { - panic(fmt.Errorf("reference doc should be valid JSON: %s", err)) + panic(fmt.Sprintf("reference doc should be valid JSON: %s", err)) } doc := testGenTranslateDesc(descTree) if doc == nil { return map[string]interface{}{} } + return doc.(map[string]interface{}) } +//nolint:funlen,gocognit,cyclop func testGenTranslateDesc(input interface{}) interface{} { a, ok := input.([]interface{}) if ok { @@ -75,48 +74,69 @@ func testGenTranslateDesc(input interface{}) interface{} { for i, v := range a { xs[i] = testGenTranslateDesc(v) } + return xs } - d := input.(map[string]interface{}) + d, ok := input.(map[string]interface{}) + if !ok { + panic(fmt.Sprintf("input should be valid map[string]: %v", input)) + } - var dtype string - var dvalue interface{} + var ( + dtype string + dvalue interface{} + ) + //nolint:nestif if len(d) == 2 { dtypeiface, ok := d["type"] if ok { dvalue, ok = d["value"] if ok { - dtype = dtypeiface.(string) + var okdt bool + + dtype, okdt = dtypeiface.(string) + if !okdt { + panic(fmt.Sprintf("dtypeiface should be valid string: %v", dtypeiface)) + } + switch dtype { case "string": return dvalue.(string) case "float": v, err := strconv.ParseFloat(dvalue.(string), 64) if err != nil { - panic(fmt.Errorf("invalid float '%s': %s", dvalue, err)) + panic(fmt.Sprintf("invalid float '%s': %s", dvalue, err)) } + return v case "integer": v, err := strconv.ParseInt(dvalue.(string), 10, 64) if err != nil { - panic(fmt.Errorf("invalid int '%s': %s", dvalue, err)) + panic(fmt.Sprintf("invalid int '%s': %s", dvalue, err)) } + return v case "bool": return dvalue.(string) == "true" case "datetime": dt, err := time.Parse("2006-01-02T15:04:05Z", dvalue.(string)) if err != nil { - panic(fmt.Errorf("invalid datetime '%s': %s", dvalue, err)) + panic(fmt.Sprintf("invalid datetime '%s': %s", dvalue, err)) } + return dt case "array": if dvalue == nil { return nil } - a := dvalue.([]interface{}) + + a, oka := dvalue.([]interface{}) + if !oka { + panic(fmt.Sprintf("a should be valid []interface{}: %v", a)) + } + xs := make([]interface{}, len(a)) for i, v := range a { @@ -125,7 +145,8 @@ func testGenTranslateDesc(input interface{}) interface{} { return xs } - panic(fmt.Errorf("unknown type: %s", dtype)) + + panic(fmt.Sprintf("unknown type: %s", dtype)) } } } @@ -134,5 +155,6 @@ func testGenTranslateDesc(input interface{}) interface{} { for k, v := range d { dest[k] = testGenTranslateDesc(v) } + return dest } diff --git a/toml_testgen_test.go b/toml_testgen_test.go index c850b0a4..b0d82cda 100644 --- a/toml_testgen_test.go +++ b/toml_testgen_test.go @@ -6,26 +6,36 @@ import ( ) func TestInvalidDatetimeMalformedNoLeads(t *testing.T) { + t.Parallel() + input := `no-leads = 1987-7-05T17:45:00Z` testgenInvalid(t, input) } func TestInvalidDatetimeMalformedNoSecs(t *testing.T) { + t.Parallel() + input := `no-secs = 1987-07-05T17:45Z` testgenInvalid(t, input) } func TestInvalidDatetimeMalformedNoT(t *testing.T) { + t.Parallel() + input := `no-t = 1987-07-0517:45:00Z` testgenInvalid(t, input) } func TestInvalidDatetimeMalformedWithMilli(t *testing.T) { + t.Parallel() + input := `with-milli = 1987-07-5T17:45:00.12Z` testgenInvalid(t, input) } func TestInvalidDuplicateKeyTable(t *testing.T) { + t.Parallel() + input := `[fruit] type = "apple" @@ -35,71 +45,97 @@ apple = "yes"` } func TestInvalidDuplicateKeys(t *testing.T) { + t.Parallel() + input := `dupe = false dupe = true` testgenInvalid(t, input) } func TestInvalidDuplicateTables(t *testing.T) { + t.Parallel() + input := `[a] [a]` testgenInvalid(t, input) } func TestInvalidEmptyImplicitTable(t *testing.T) { + t.Parallel() + input := `[naughty..naughty]` testgenInvalid(t, input) } func TestInvalidEmptyTable(t *testing.T) { + t.Parallel() + input := `[]` testgenInvalid(t, input) } func TestInvalidFloatNoLeadingZero(t *testing.T) { + t.Parallel() + input := `answer = .12345 neganswer = -.12345` testgenInvalid(t, input) } func TestInvalidFloatNoTrailingDigits(t *testing.T) { + t.Parallel() + input := `answer = 1. neganswer = -1.` testgenInvalid(t, input) } func TestInvalidKeyEmpty(t *testing.T) { + t.Parallel() + input := ` = 1` testgenInvalid(t, input) } func TestInvalidKeyHash(t *testing.T) { + t.Parallel() + input := `a# = 1` testgenInvalid(t, input) } func TestInvalidKeyNewline(t *testing.T) { + t.Parallel() + input := `a = 1` testgenInvalid(t, input) } func TestInvalidKeyOpenBracket(t *testing.T) { + t.Parallel() + input := `[abc = 1` testgenInvalid(t, input) } func TestInvalidKeySingleOpenBracket(t *testing.T) { + t.Parallel() + input := `[` testgenInvalid(t, input) } func TestInvalidKeySpace(t *testing.T) { + t.Parallel() + input := `a b = 1` testgenInvalid(t, input) } func TestInvalidKeyStartBracket(t *testing.T) { + t.Parallel() + input := `[a] [xyz = 5 [b]` @@ -107,31 +143,43 @@ func TestInvalidKeyStartBracket(t *testing.T) { } func TestInvalidKeyTwoEquals(t *testing.T) { + t.Parallel() + input := `key= = 1` testgenInvalid(t, input) } func TestInvalidStringBadByteEscape(t *testing.T) { + t.Parallel() + input := `naughty = "\xAg"` testgenInvalid(t, input) } func TestInvalidStringBadEscape(t *testing.T) { + t.Parallel() + input := `invalid-escape = "This string has a bad \a escape character."` testgenInvalid(t, input) } func TestInvalidStringByteEscapes(t *testing.T) { + t.Parallel() + input := `answer = "\x33"` testgenInvalid(t, input) } func TestInvalidStringNoClose(t *testing.T) { + t.Parallel() + input := `no-ending-quote = "One time, at band camp` testgenInvalid(t, input) } func TestInvalidTableArrayImplicit(t *testing.T) { + t.Parallel() + input := "# This test is a bit tricky. It should fail because the first use of\n" + "# `[[albums.songs]]` without first declaring `albums` implies that `albums`\n" + "# must be a table. The alternative would be quite weird. Namely, it wouldn't\n" + @@ -150,46 +198,62 @@ func TestInvalidTableArrayImplicit(t *testing.T) { } func TestInvalidTableArrayMalformedBracket(t *testing.T) { + t.Parallel() + input := `[[albums] name = "Born to Run"` testgenInvalid(t, input) } func TestInvalidTableArrayMalformedEmpty(t *testing.T) { + t.Parallel() + input := `[[]] name = "Born to Run"` testgenInvalid(t, input) } func TestInvalidTableEmpty(t *testing.T) { + t.Parallel() + input := `[]` testgenInvalid(t, input) } func TestInvalidTableNestedBracketsClose(t *testing.T) { + t.Parallel() + input := `[a]b] zyx = 42` testgenInvalid(t, input) } func TestInvalidTableNestedBracketsOpen(t *testing.T) { + t.Parallel() + input := `[a[b] zyx = 42` testgenInvalid(t, input) } func TestInvalidTableWhitespace(t *testing.T) { + t.Parallel() + input := `[invalid key]` testgenInvalid(t, input) } func TestInvalidTableWithPound(t *testing.T) { + t.Parallel() + input := `[key#group] answer = 42` testgenInvalid(t, input) } func TestInvalidTextAfterArrayEntries(t *testing.T) { + t.Parallel() + input := `array = [ "Is there life after an array separator?", No "Entry" @@ -198,21 +262,29 @@ func TestInvalidTextAfterArrayEntries(t *testing.T) { } func TestInvalidTextAfterInteger(t *testing.T) { + t.Parallel() + input := `answer = 42 the ultimate answer?` testgenInvalid(t, input) } func TestInvalidTextAfterString(t *testing.T) { + t.Parallel() + input := `string = "Is there life after strings?" No.` testgenInvalid(t, input) } func TestInvalidTextAfterTable(t *testing.T) { + t.Parallel() + input := `[error] this shouldn't be here` testgenInvalid(t, input) } func TestInvalidTextBeforeArraySeparator(t *testing.T) { + t.Parallel() + input := `array = [ "Is there life before an array separator?" No, "Entry" @@ -221,6 +293,8 @@ func TestInvalidTextBeforeArraySeparator(t *testing.T) { } func TestInvalidTextInArray(t *testing.T) { + t.Parallel() + input := `array = [ "Entry 1", I don't belong, @@ -230,6 +304,8 @@ func TestInvalidTextInArray(t *testing.T) { } func TestValidArrayEmpty(t *testing.T) { + t.Parallel() + input := `thevoid = [[[[[]]]]]` jsonRef := `{ "thevoid": { "type": "array", "value": [ @@ -246,6 +322,8 @@ func TestValidArrayEmpty(t *testing.T) { } func TestValidArrayNospaces(t *testing.T) { + t.Parallel() + input := `ints = [1,2,3]` jsonRef := `{ "ints": { @@ -261,6 +339,8 @@ func TestValidArrayNospaces(t *testing.T) { } func TestValidArraysHetergeneous(t *testing.T) { + t.Parallel() + input := `mixed = [[1, 2], ["a", "b"], [1.1, 2.1]]` jsonRef := `{ "mixed": { @@ -285,6 +365,8 @@ func TestValidArraysHetergeneous(t *testing.T) { } func TestValidArraysNested(t *testing.T) { + t.Parallel() + input := `nest = [["a"], ["b"]]` jsonRef := `{ "nest": { @@ -303,6 +385,8 @@ func TestValidArraysNested(t *testing.T) { } func TestValidArrays(t *testing.T) { + t.Parallel() + input := `ints = [1, 2, 3] floats = [1.1, 2.1, 3.1] strings = ["a", "b", "c"] @@ -349,6 +433,8 @@ dates = [ } func TestValidBool(t *testing.T) { + t.Parallel() + input := `t = true f = false` jsonRef := `{ @@ -359,6 +445,8 @@ f = false` } func TestValidCommentsEverywhere(t *testing.T) { + t.Parallel() + input := `# Top comment. # Top comment. # Top comment. @@ -368,7 +456,7 @@ func TestValidCommentsEverywhere(t *testing.T) { [group] # Comment answer = 42 # Comment # no-extraneous-keys-please = 999 -# Inbetween comment. +# In between comment. more = [ # Comment # What about multiple # comments? # Can you handle it? @@ -399,6 +487,8 @@ more = [ # Comment } func TestValidDatetime(t *testing.T) { + t.Parallel() + input := `bestdayever = 1987-07-05T17:45:00Z` jsonRef := `{ "bestdayever": {"type": "datetime", "value": "1987-07-05T17:45:00Z"} @@ -407,12 +497,16 @@ func TestValidDatetime(t *testing.T) { } func TestValidEmpty(t *testing.T) { + t.Parallel() + input := `` jsonRef := `{}` testgenValid(t, input, jsonRef) } func TestValidExample(t *testing.T) { + t.Parallel() + input := `best-day-ever = 1987-07-05T17:45:00Z [numtheory] @@ -436,6 +530,8 @@ perfection = [6, 28, 496]` } func TestValidFloat(t *testing.T) { + t.Parallel() + input := `pi = 3.14 negpi = -3.14` jsonRef := `{ @@ -446,6 +542,8 @@ negpi = -3.14` } func TestValidImplicitAndExplicitAfter(t *testing.T) { + t.Parallel() + input := `[a.b.c] answer = 42 @@ -465,6 +563,8 @@ better = 43` } func TestValidImplicitAndExplicitBefore(t *testing.T) { + t.Parallel() + input := `[a] better = 43 @@ -484,6 +584,8 @@ answer = 42` } func TestValidImplicitGroups(t *testing.T) { + t.Parallel() + input := `[a.b.c] answer = 42` jsonRef := `{ @@ -499,6 +601,8 @@ answer = 42` } func TestValidInteger(t *testing.T) { + t.Parallel() + input := `answer = 42 neganswer = -42` jsonRef := `{ @@ -509,6 +613,8 @@ neganswer = -42` } func TestValidKeyEqualsNospace(t *testing.T) { + t.Parallel() + input := `answer=42` jsonRef := `{ "answer": {"type": "integer", "value": "42"} @@ -517,6 +623,8 @@ func TestValidKeyEqualsNospace(t *testing.T) { } func TestValidKeySpace(t *testing.T) { + t.Parallel() + input := `"a b" = 1` jsonRef := `{ "a b": {"type": "integer", "value": "1"} @@ -525,6 +633,8 @@ func TestValidKeySpace(t *testing.T) { } func TestValidKeySpecialChars(t *testing.T) { + t.Parallel() + input := "\"~!@$^&*()_+-`1234567890[]|/?><.,;:'\" = 1\n" jsonRef := "{\n" + " \"~!@$^&*()_+-`1234567890[]|/?><.,;:'\": {\n" + @@ -535,6 +645,8 @@ func TestValidKeySpecialChars(t *testing.T) { } func TestValidLongFloat(t *testing.T) { + t.Parallel() + input := `longpi = 3.141592653589793 neglongpi = -3.141592653589793` jsonRef := `{ @@ -545,6 +657,8 @@ neglongpi = -3.141592653589793` } func TestValidLongInteger(t *testing.T) { + t.Parallel() + input := `answer = 9223372036854775807 neganswer = -9223372036854775808` jsonRef := `{ @@ -555,6 +669,8 @@ neganswer = -9223372036854775808` } func TestValidMultilineString(t *testing.T) { + t.Parallel() + input := `multiline_empty_one = """""" multiline_empty_two = """ """ @@ -612,6 +728,8 @@ equivalent_three = """\ } func TestValidRawMultilineString(t *testing.T) { + t.Parallel() + input := `oneline = '''This string has a ' quote character.''' firstnl = ''' This string has a ' quote character.''' @@ -639,6 +757,8 @@ in it.'''` } func TestValidRawString(t *testing.T) { + t.Parallel() + input := `backspace = 'This string has a \b backspace character.' tab = 'This string has a \t tab character.' newline = 'This string has a \n new line character.' @@ -680,6 +800,8 @@ backslash = 'This string has a \\ backslash character.'` } func TestValidStringEmpty(t *testing.T) { + t.Parallel() + input := `answer = ""` jsonRef := `{ "answer": { @@ -691,6 +813,8 @@ func TestValidStringEmpty(t *testing.T) { } func TestValidStringEscapes(t *testing.T) { + t.Parallel() + input := `backspace = "This string has a \b backspace character." tab = "This string has a \t tab character." newline = "This string has a \n new line character." @@ -752,6 +876,8 @@ notunicode4 = "This string does not have a unicode \\\u0075 escape."` } func TestValidStringSimple(t *testing.T) { + t.Parallel() + input := `answer = "You are not drinking enough whisky."` jsonRef := `{ "answer": { @@ -763,6 +889,8 @@ func TestValidStringSimple(t *testing.T) { } func TestValidStringWithPound(t *testing.T) { + t.Parallel() + input := `pound = "We see no # comments here." poundcomment = "But there are # some comments here." # Did I # mess you up?` jsonRef := `{ @@ -776,6 +904,8 @@ poundcomment = "But there are # some comments here." # Did I # mess you up?` } func TestValidTableArrayImplicit(t *testing.T) { + t.Parallel() + input := `[[albums.songs]] name = "Glory Days"` jsonRef := `{ @@ -789,6 +919,8 @@ name = "Glory Days"` } func TestValidTableArrayMany(t *testing.T) { + t.Parallel() + input := `[[people]] first_name = "Bruce" last_name = "Springsteen" @@ -820,6 +952,8 @@ last_name = "Seger"` } func TestValidTableArrayNest(t *testing.T) { + t.Parallel() + input := `[[albums]] name = "Born to Run" @@ -831,7 +965,7 @@ name = "Born to Run" [[albums]] name = "Born in the USA" - + [[albums.songs]] name = "Glory Days" @@ -859,6 +993,8 @@ name = "Born in the USA" } func TestValidTableArrayOne(t *testing.T) { + t.Parallel() + input := `[[people]] first_name = "Bruce" last_name = "Springsteen"` @@ -874,6 +1010,8 @@ last_name = "Springsteen"` } func TestValidTableEmpty(t *testing.T) { + t.Parallel() + input := `[a]` jsonRef := `{ "a": {} @@ -882,6 +1020,8 @@ func TestValidTableEmpty(t *testing.T) { } func TestValidTableSubEmpty(t *testing.T) { + t.Parallel() + input := `[a] [a.b]` jsonRef := `{ @@ -891,6 +1031,8 @@ func TestValidTableSubEmpty(t *testing.T) { } func TestValidTableWhitespace(t *testing.T) { + t.Parallel() + input := `["valid key"]` jsonRef := `{ "valid key": {} @@ -899,6 +1041,8 @@ func TestValidTableWhitespace(t *testing.T) { } func TestValidTableWithPound(t *testing.T) { + t.Parallel() + input := `["key#group"] answer = 42` jsonRef := `{ @@ -910,6 +1054,8 @@ answer = 42` } func TestValidUnicodeEscape(t *testing.T) { + t.Parallel() + input := `answer4 = "\u03B4" answer8 = "\U000003B4"` jsonRef := `{ @@ -920,6 +1066,8 @@ answer8 = "\U000003B4"` } func TestValidUnicodeLiteral(t *testing.T) { + t.Parallel() + input := `answer = "δ"` jsonRef := `{ "answer": {"type": "string", "value": "δ"} diff --git a/unmarshaler.go b/unmarshaler.go index 54604578..47a9aeca 100644 --- a/unmarshaler.go +++ b/unmarshaler.go @@ -81,7 +81,7 @@ type decoder struct { strict strict } -func (d *decoder) arrayIndex(append bool, v reflect.Value) int { +func (d *decoder) arrayIndex(shouldAppend bool, v reflect.Value) int { if d.arrayIndexes == nil { d.arrayIndexes = make(map[reflect.Value]int, 1) } @@ -90,7 +90,7 @@ func (d *decoder) arrayIndex(append bool, v reflect.Value) int { if !ok { d.arrayIndexes[v] = 0 - } else if append { + } else if shouldAppend { idx++ d.arrayIndexes[v] = idx } @@ -173,6 +173,7 @@ func (d *decoder) fromParser(p *parser, v interface{}) error { found = true case ast.Table: d.strict.EnterTable(node) + current, found, err = d.scopeWithKey(root, node.Key()) if err == nil && found { // In case this table points to an interface, @@ -180,7 +181,10 @@ func (d *decoder) fromParser(p *parser, v interface{}) error { // looks like a table. Otherwise the information // of a table is lost, and marshal cannot do the // round trip. - ensureMapIfInterface(current) + err := ensureMapIfInterface(current) + if err != nil { + panic(fmt.Sprintf("ensureMapIfInterface: %s", err)) + } } case ast.ArrayTable: d.strict.EnterArrayTable(node) @@ -305,6 +309,7 @@ func (d *decoder) unmarshalKeyValue(x target, node ast.Node) error { // A struct in the path was not found. Skip this value. if !found { d.strict.MissingField(node) + return nil } @@ -327,11 +332,21 @@ func tryTextUnmarshaler(x target, node ast.Node) (bool, error) { } if v.Type().Implements(textUnmarshalerType) { - return true, v.Interface().(encoding.TextUnmarshaler).UnmarshalText(node.Data) + err := v.Interface().(encoding.TextUnmarshaler).UnmarshalText(node.Data) + if err != nil { + return false, fmt.Errorf("tryTextUnmarshaler: %w", err) + } + + return true, nil } if v.CanAddr() && v.Addr().Type().Implements(textUnmarshalerType) { - return true, v.Addr().Interface().(encoding.TextUnmarshaler).UnmarshalText(node.Data) + err := v.Addr().Interface().(encoding.TextUnmarshaler).UnmarshalText(node.Data) + if err != nil { + return false, fmt.Errorf("tryTextUnmarshaler: %w", err) + } + + return true, nil } return false, nil @@ -345,7 +360,7 @@ func (d *decoder) unmarshalValue(x target, node ast.Node) error { if !v.Elem().IsValid() { err := x.set(reflect.New(v.Type().Elem())) if err != nil { - return err + return fmt.Errorf("unmarshalValue: %w", err) } v = x.get() @@ -423,14 +438,25 @@ func unmarshalDateTime(x target, node ast.Node) error { func setLocalDateTime(x target, v LocalDateTime) error { if x.get().Type() == timeType { cast := v.In(time.Local) + return setDateTime(x, cast) } - return x.set(reflect.ValueOf(v)) + err := x.set(reflect.ValueOf(v)) + if err != nil { + return fmt.Errorf("setLocalDateTime: %w", err) + } + + return nil } func setDateTime(x target, v time.Time) error { - return x.set(reflect.ValueOf(v)) + err := x.set(reflect.ValueOf(v)) + if err != nil { + return fmt.Errorf("setDateTime: %w", err) + } + + return nil } var timeType = reflect.TypeOf(time.Time{}) @@ -438,10 +464,16 @@ var timeType = reflect.TypeOf(time.Time{}) func setDate(x target, v LocalDate) error { if x.get().Type() == timeType { cast := v.In(time.Local) + return setDateTime(x, cast) } - return x.set(reflect.ValueOf(v)) + err := x.set(reflect.ValueOf(v)) + if err != nil { + return fmt.Errorf("setDate: %w", err) + } + + return nil } func unmarshalString(x target, node ast.Node) error { @@ -470,6 +502,7 @@ func unmarshalInteger(x target, node ast.Node) error { func unmarshalFloat(x target, node ast.Node) error { assertNode(ast.Float, node) + v, err := parseFloat(node.Data) if err != nil { return err @@ -481,7 +514,10 @@ func unmarshalFloat(x target, node ast.Node) error { func (d *decoder) unmarshalInlineTable(x target, node ast.Node) error { assertNode(ast.InlineTable, node) - ensureMapIfInterface(x) + err := ensureMapIfInterface(x) + if err != nil { + return fmt.Errorf("unmarshalInlineTable: %w", err) + } it := node.Children() for it.Next() { diff --git a/unmarshaler_test.go b/unmarshaler_test.go index 45f6e589..6caaadb1 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -838,6 +838,7 @@ func (i *Integer484) UnmarshalText(data []byte) error { return fmt.Errorf("UnmarshalText: %w", err) } i.Value = conv + return nil } @@ -846,6 +847,8 @@ type Config484 struct { } func TestIssue484(t *testing.T) { + t.Parallel() + raw := []byte(`integers = ["1","2","3","100"]`) var cfg Config484 @@ -866,6 +869,8 @@ func (m Map458) A(s string) Slice458 { } func TestIssue458(t *testing.T) { + t.Parallel() + s := []byte(`[[package]] dependencies = ["regex"] name = "decode" @@ -885,6 +890,8 @@ version = "0.1.0"`) } func TestIssue252(t *testing.T) { + t.Parallel() + type config struct { Val1 string `toml:"val1"` Val2 string `toml:"val2"` @@ -905,6 +912,8 @@ val1 = "test1" } func TestIssue494(t *testing.T) { + t.Parallel() + data := ` foo = 2021-04-08 bar = 2021-04-08 @@ -920,6 +929,8 @@ bar = 2021-04-08 } func TestIssue507(t *testing.T) { + t.Parallel() + data := []byte{'0', '=', '\n', '0', 'a', 'm', 'e'} m := map[string]interface{}{} err := toml.Unmarshal(data, &m) @@ -1094,6 +1105,8 @@ func TestLocalDateTime(t *testing.T) { } func TestIssue287(t *testing.T) { + t.Parallel() + b := `y=[[{}]]` v := map[string]interface{}{} err := toml.Unmarshal([]byte(b), &v) @@ -1110,6 +1123,8 @@ func TestIssue287(t *testing.T) { } func TestIssue508(t *testing.T) { + t.Parallel() + type head struct { Title string `toml:"title"` } From 4545a3e94bab26925e3445ce7c5a90a0898ac34b Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Fri, 7 May 2021 23:34:17 -0400 Subject: [PATCH 191/228] ci: remove benchmarks Both github actions and my own VPS have too much noise to be useful. --- .github/workflows/benchmark.yml | 38 --------------------------------- 1 file changed, 38 deletions(-) delete mode 100644 .github/workflows/benchmark.yml diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml deleted file mode 100644 index a39d2b6b..00000000 --- a/.github/workflows/benchmark.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: benchmark -on: - push: - branches: - - v2 - pull_request: - branches: - - v2 - -jobs: - base: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@master - with: - fetch-depth: 0 - - name: Setup go - uses: actions/setup-go@master - with: - go-version: "~1.16" - - uses: WillAbides/benchdiff-action@main - with: - benchdiff_version: 0.7.0 - status_sha: ${{ github.sha }} - status_name: bench-result - status_on_degraded: neutral - benchdiff_args: | - --packages ./... - --cpu=1,2 - --count=10 - --warmup-count=1 - --warmup-time=10ms - --tolerance=10 - --base-ref origin/v2 - --debug - --benchmem - --geomean - --sort=name From ea225df3ed84319c3a35aaf19a5779dee1d40951 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Sat, 8 May 2021 16:04:25 -0400 Subject: [PATCH 192/228] v2: errors (#534) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ``` name old time/op new time/op delta UnmarshalDataset/config-32 86.7ms ± 2% 87.5ms ± 2% ~ (p=0.113 n=9+10) UnmarshalDataset/canada-32 129ms ± 4% 106ms ± 3% -17.94% (p=0.000 n=10+10) UnmarshalDataset/citm_catalog-32 59.4ms ± 5% 58.7ms ± 5% ~ (p=0.393 n=10+10) UnmarshalDataset/twitter-32 27.0ms ± 7% 26.9ms ± 6% ~ (p=0.720 n=10+9) UnmarshalDataset/code-32 326ms ± 4% 322ms ± 7% ~ (p=0.661 n=9+10) UnmarshalDataset/example-32 510µs ±11% 526µs ± 7% ~ (p=0.182 n=10+9) UnmarshalSimple-32 1.41µs ± 6% 1.41µs ± 4% ~ (p=0.736 n=10+9) ReferenceFile-32 45.6µs ± 3% 43.9µs ±10% ~ (p=0.089 n=10+10) name old speed new speed delta UnmarshalDataset/config-32 12.1MB/s ± 2% 12.0MB/s ± 2% ~ (p=0.108 n=9+10) UnmarshalDataset/canada-32 17.1MB/s ± 4% 20.9MB/s ± 3% +21.86% (p=0.000 n=10+10) UnmarshalDataset/citm_catalog-32 9.41MB/s ± 5% 9.51MB/s ± 5% ~ (p=0.362 n=10+10) UnmarshalDataset/twitter-32 16.4MB/s ± 8% 16.5MB/s ± 6% ~ (p=0.704 n=10+9) UnmarshalDataset/code-32 8.24MB/s ± 4% 8.34MB/s ± 7% ~ (p=0.675 n=9+10) UnmarshalDataset/example-32 15.9MB/s ±11% 15.4MB/s ± 7% ~ (p=0.182 n=10+9) ReferenceFile-32 115MB/s ± 4% 120MB/s ±10% ~ (p=0.085 n=10+10) name old alloc/op new alloc/op delta UnmarshalDataset/config-32 16.9MB ± 0% 16.9MB ± 0% -0.02% (p=0.000 n=10+10) UnmarshalDataset/canada-32 76.8MB ± 0% 74.3MB ± 0% -3.31% (p=0.000 n=10+10) UnmarshalDataset/citm_catalog-32 37.3MB ± 0% 37.1MB ± 0% -0.60% (p=0.000 n=9+10) UnmarshalDataset/twitter-32 15.6MB ± 0% 15.6MB ± 0% -0.09% (p=0.000 n=10+10) UnmarshalDataset/code-32 60.2MB ± 0% 59.3MB ± 0% -1.51% (p=0.000 n=10+9) UnmarshalDataset/example-32 238kB ± 0% 238kB ± 0% -0.18% (p=0.000 n=10+10) ReferenceFile-32 11.8kB ± 0% 11.8kB ± 0% ~ (all equal) name old allocs/op new allocs/op delta UnmarshalDataset/config-32 653k ± 0% 645k ± 0% -1.20% (p=0.000 n=10+6) UnmarshalDataset/canada-32 1.01M ± 0% 0.90M ± 0% -11.04% (p=0.000 n=9+10) UnmarshalDataset/citm_catalog-32 384k ± 0% 370k ± 0% -3.75% (p=0.000 n=10+10) UnmarshalDataset/twitter-32 160k ± 0% 157k ± 0% -1.32% (p=0.000 n=10+10) UnmarshalDataset/code-32 2.97M ± 0% 2.91M ± 0% -2.15% (p=0.000 n=10+7) UnmarshalDataset/example-32 3.69k ± 0% 3.63k ± 0% -1.52% (p=0.000 n=10+10) ReferenceFile-32 253 ± 0% 253 ± 0% ~ (all equal) ``` --- decode.go | 167 +++++-------- errors.go | 4 +- internal/tracker/seen.go | 8 +- localtime.go | 6 +- marshaler.go | 35 +-- parser.go | 64 ++--- scanner.go | 29 +-- targets.go | 469 +++++++++-------------------------- targets_test.go | 16 +- toml_testgen_support_test.go | 12 +- unmarshaler.go | 101 +++----- unmarshaler_test.go | 60 ++++- 12 files changed, 320 insertions(+), 651 deletions(-) diff --git a/decode.go b/decode.go index 87330a78..afa6db35 100644 --- a/decode.go +++ b/decode.go @@ -1,11 +1,8 @@ package toml import ( - "errors" - "fmt" "math" "strconv" - "strings" "time" ) @@ -59,14 +56,12 @@ func parseLocalDate(b []byte) (LocalDate, error) { return date, nil } -var errNotDigit = errors.New("not a digit") - func parseDecimalDigits(b []byte) (int, error) { v := 0 - for _, c := range b { + for i, c := range b { if !isDigit(c) { - return 0, fmt.Errorf("%s: %w", b, errNotDigit) + return 0, newDecodeError(b[i:i+1], "should be a digit (0-9)") } v *= 10 @@ -76,13 +71,14 @@ func parseDecimalDigits(b []byte) (int, error) { return v, nil } -var errParseDateTimeMissingInfo = errors.New("date-time missing timezone information") - func parseDateTime(b []byte) (time.Time, error) { // offset-date-time = full-date time-delim full-time // full-time = partial-time time-offset // time-offset = "Z" / time-numoffset // time-numoffset = ( "+" / "-" ) time-hour ":" time-minute + + originalBytes := b + dt, b, err := parseLocalDateTime(b) if err != nil { return time.Time{}, err @@ -91,7 +87,7 @@ func parseDateTime(b []byte) (time.Time, error) { var zone *time.Location if len(b) == 0 { - return time.Time{}, errParseDateTimeMissingInfo + return time.Time{}, newDecodeError(originalBytes, "date-time is missing timezone") } if b[0] == 'Z' { @@ -134,19 +130,12 @@ func parseDateTime(b []byte) (time.Time, error) { return t, nil } -var ( - errParseLocalDateTimeWrongLength = errors.New( - "local datetimes are expected to have the format YYYY-MM-DDTHH:MM:SS[.NNNNNNNNN]", - ) - errParseLocalDateTimeWrongSeparator = errors.New("datetime separator is expected to be T or a space") -) - func parseLocalDateTime(b []byte) (LocalDateTime, []byte, error) { var dt LocalDateTime const localDateTimeByteMinLen = 11 if len(b) < localDateTimeByteMinLen { - return dt, nil, errParseLocalDateTimeWrongLength + return dt, nil, newDecodeError(b, "local datetimes are expected to have the format YYYY-MM-DDTHH:MM:SS[.NNNNNNNNN]") } date, err := parseLocalDate(b[:10]) @@ -157,7 +146,7 @@ func parseLocalDateTime(b []byte) (LocalDateTime, []byte, error) { sep := b[10] if sep != 'T' && sep != ' ' { - return dt, nil, errParseLocalDateTimeWrongSeparator + return dt, nil, newDecodeError(b[10:11], "datetime separator is expected to be T or a space") } t, rest, err := parseLocalTime(b[11:]) @@ -169,8 +158,6 @@ func parseLocalDateTime(b []byte) (LocalDateTime, []byte, error) { return dt, rest, nil } -var errParseLocalTimeWrongLength = errors.New("times are expected to have the format HH:MM:SS[.NNNNNN]") - // parseLocalTime is a bit different because it also returns the remaining // []byte that is didn't need. This is to allow parseDateTime to parse those // remaining bytes as a timezone. @@ -183,7 +170,7 @@ func parseLocalTime(b []byte) (LocalTime, []byte, error) { const localTimeByteLen = 8 if len(b) < localTimeByteLen { - return t, nil, errParseLocalTimeWrongLength + return t, nil, newDecodeError(b, "times are expected to have the format HH:MM:SS[.NNNNNN]") } var err error @@ -242,11 +229,6 @@ func parseLocalTime(b []byte) (LocalTime, []byte, error) { return t, b[8:], nil } -var ( - errParseFloatStartDot = errors.New("float cannot start with a dot") - errParseFloatEndDot = errors.New("float cannot end with a dot") -) - //nolint:cyclop func parseFloat(b []byte) (float64, error) { //nolint:godox @@ -255,150 +237,123 @@ func parseFloat(b []byte) (float64, error) { return math.NaN(), nil } - tok := string(b) - - err := numberContainsInvalidUnderscore(tok) + cleaned, err := checkAndRemoveUnderscores(b) if err != nil { return 0, err } - cleanedVal := cleanupNumberToken(tok) - if cleanedVal[0] == '.' { - return 0, errParseFloatStartDot + if cleaned[0] == '.' { + return 0, newDecodeError(b, "float cannot start with a dot") } - if cleanedVal[len(cleanedVal)-1] == '.' { - return 0, errParseFloatEndDot + if cleaned[len(cleaned)-1] == '.' { + return 0, newDecodeError(b, "float cannot end with a dot") } - f, err := strconv.ParseFloat(cleanedVal, 64) + f, err := strconv.ParseFloat(string(cleaned), 64) if err != nil { - return 0, fmt.Errorf("coudn't ParseFloat %w", err) + return 0, newDecodeError(b, "coudn't parse float: %w", err) } return f, nil } func parseIntHex(b []byte) (int64, error) { - tok := string(b) - cleanedVal := cleanupNumberToken(tok) - - err := hexNumberContainsInvalidUnderscore(cleanedVal) + cleaned, err := checkAndRemoveUnderscores(b[2:]) if err != nil { return 0, err } - i, err := strconv.ParseInt(cleanedVal[2:], 16, 64) + i, err := strconv.ParseInt(string(cleaned), 16, 64) if err != nil { - return 0, fmt.Errorf("coudn't ParseIntHex %w", err) + return 0, newDecodeError(b, "couldn't parse hexadecimal number: %w", err) } return i, nil } func parseIntOct(b []byte) (int64, error) { - tok := string(b) - cleanedVal := cleanupNumberToken(tok) - - err := numberContainsInvalidUnderscore(cleanedVal) + cleaned, err := checkAndRemoveUnderscores(b[2:]) if err != nil { return 0, err } - i, err := strconv.ParseInt(cleanedVal[2:], 8, 64) + i, err := strconv.ParseInt(string(cleaned), 8, 64) if err != nil { - return 0, fmt.Errorf("coudn't ParseIntOct %w", err) + return 0, newDecodeError(b, "couldn't parse octal number: %w", err) } return i, nil } func parseIntBin(b []byte) (int64, error) { - tok := string(b) - cleanedVal := cleanupNumberToken(tok) - - err := numberContainsInvalidUnderscore(cleanedVal) + cleaned, err := checkAndRemoveUnderscores(b[2:]) if err != nil { return 0, err } - i, err := strconv.ParseInt(cleanedVal[2:], 2, 64) + i, err := strconv.ParseInt(string(cleaned), 2, 64) if err != nil { - return 0, fmt.Errorf("coudn't ParseIntBin %w", err) + return 0, newDecodeError(b, "couldn't parse binary number: %w", err) } return i, nil } func parseIntDec(b []byte) (int64, error) { - tok := string(b) - cleanedVal := cleanupNumberToken(tok) - - err := numberContainsInvalidUnderscore(cleanedVal) + cleaned, err := checkAndRemoveUnderscores(b) if err != nil { return 0, err } - i, err := strconv.ParseInt(cleanedVal, 10, 64) + i, err := strconv.ParseInt(string(cleaned), 10, 64) if err != nil { - return 0, fmt.Errorf("coudn't parseIntDec %w", err) + return 0, newDecodeError(b, "couldn't parse decimal number: %w", err) } return i, nil } -func numberContainsInvalidUnderscore(value string) error { - // For large numbers, you may use underscores between digits to enhance - // readability. Each underscore must be surrounded by at least one digit on - // each side. - hasBefore := false - - for idx, r := range value { - if r == '_' { - if !hasBefore || idx+1 >= len(value) { - // can't end with an underscore - return errInvalidUnderscore - } - } - hasBefore = isDigitRune(r) +func checkAndRemoveUnderscores(b []byte) ([]byte, error) { + if len(b) == 0 { + return b, nil } - return nil -} + if b[0] == '_' { + return nil, newDecodeError(b[0:1], "number cannot start with underscore") + } -func hexNumberContainsInvalidUnderscore(value string) error { - hasBefore := false + if b[len(b)-1] == '_' { + return nil, newDecodeError(b[len(b)-1:], "number cannot end with underscore") + } - for idx, r := range value { - if r == '_' { - if !hasBefore || idx+1 >= len(value) { - // can't end with an underscore - return errInvalidUnderscoreHex - } + // fast path + i := 0 + for ; i < len(b); i++ { + if b[i] == '_' { + break } - hasBefore = isHexDigit(r) + } + if i == len(b) { + return b, nil } - return nil -} - -func cleanupNumberToken(value string) string { - cleanedVal := strings.ReplaceAll(value, "_", "") - - return cleanedVal -} + before := false + cleaned := make([]byte, i, len(b)) + copy(cleaned, b) -func isHexDigit(r rune) bool { - return isDigitRune(r) || - (r >= 'a' && r <= 'f') || - (r >= 'A' && r <= 'F') -} + for i++; i < len(b); i++ { + c := b[i] + if c == '_' { + if !before { + return nil, newDecodeError(b[i-1:i+1], "number must have at least one digit between underscores") + } + before = false + } else { + before = true + cleaned = append(cleaned, c) + } + } -func isDigitRune(r rune) bool { - return r >= '0' && r <= '9' + return cleaned, nil } - -var ( - errInvalidUnderscore = errors.New("invalid use of _ in number") - errInvalidUnderscoreHex = errors.New("invalid use of _ in hex number") -) diff --git a/errors.go b/errors.go index 7773796b..82c21ef4 100644 --- a/errors.go +++ b/errors.go @@ -70,13 +70,13 @@ func (de *decodeError) Error() string { func newDecodeError(highlight []byte, format string, args ...interface{}) error { return &decodeError{ highlight: highlight, - message: fmt.Sprintf(format, args...), + message: fmt.Errorf(format, args...).Error(), } } // Error returns the error message contained in the DecodeError. func (e *DecodeError) Error() string { - return e.message + return "toml: " + e.message } // String returns the human-readable contextualized error. This string is multi-line. diff --git a/internal/tracker/seen.go b/internal/tracker/seen.go index f80052db..e245aace 100644 --- a/internal/tracker/seen.go +++ b/internal/tracker/seen.go @@ -123,10 +123,10 @@ func (s *SeenTracker) checkTable(node ast.Node) error { i, found := s.current.Has(k) if found { if i.kind != tableKind { - return fmt.Errorf("key %s should be a table", k) + return fmt.Errorf("toml: key %s should be a table, not a %s", k, i.kind) } if i.explicit { - return fmt.Errorf("table %s already exists", k) + return fmt.Errorf("toml: table %s already exists", k) } i.explicit = true s.current = i @@ -162,7 +162,7 @@ func (s *SeenTracker) checkArrayTable(node ast.Node) error { info, found := s.current.Has(k) if found { if info.kind != arrayTableKind { - return fmt.Errorf("key %s already exists but is not an array table", k) + return fmt.Errorf("toml: key %s already exists as a %s, but should be an array table", info.kind, k) } info.Clear() } else { @@ -182,7 +182,7 @@ func (s *SeenTracker) checkKeyValue(context *info, node ast.Node) error { child, found := context.Has(k) if found { if child.kind != tableKind { - return fmt.Errorf("expected %s to be a table, not a %s", k, child.kind) + return fmt.Errorf("toml: expected %s to be a table, not a %s", k, child.kind) } } else { child = context.CreateTable(k, false) diff --git a/localtime.go b/localtime.go index f271befb..a947044a 100644 --- a/localtime.go +++ b/localtime.go @@ -53,7 +53,7 @@ func LocalDateOf(t time.Time) LocalDate { func ParseLocalDate(s string) (LocalDate, error) { t, err := time.Parse("2006-01-02", s) if err != nil { - return LocalDate{}, fmt.Errorf("ParseLocalDate: %w", err) + return LocalDate{}, err } return LocalDateOf(t), nil @@ -166,7 +166,7 @@ func LocalTimeOf(t time.Time) LocalTime { func ParseLocalTime(s string) (LocalTime, error) { t, err := time.Parse("15:04:05.999999999", s) if err != nil { - return LocalTime{}, fmt.Errorf("ParseLocalTime: %w", err) + return LocalTime{}, err } return LocalTimeOf(t), nil @@ -237,7 +237,7 @@ func ParseLocalDateTime(s string) (LocalDateTime, error) { if err != nil { t, err = time.Parse("2006-01-02t15:04:05.999999999", s) if err != nil { - return LocalDateTime{}, fmt.Errorf("ParseLocalDateTime: %w", err) + return LocalDateTime{}, err } } diff --git a/marshaler.go b/marshaler.go index 172c0334..aa8783ac 100644 --- a/marshaler.go +++ b/marshaler.go @@ -3,7 +3,6 @@ package toml import ( "bytes" "encoding" - "errors" "fmt" "io" "reflect" @@ -116,12 +115,12 @@ func (enc *Encoder) Encode(v interface{}) error { b, err := enc.encode(b, ctx, reflect.ValueOf(v)) if err != nil { - return fmt.Errorf("Encode: %w", err) + return err } _, err = enc.w.Write(b) if err != nil { - return fmt.Errorf("Encode: %w", err) + return fmt.Errorf("toml: cannot write: %w", err) } return nil @@ -178,11 +177,6 @@ func (ctx *encoderCtx) isRoot() bool { return len(ctx.parentKey) == 0 && !ctx.hasKey } -var ( - errUnsupportedValue = errors.New("unsupported encode value kind") - errTextMarshalerCannotBeAtRoot = errors.New("type implementing TextMarshaler cannot be at root") -) - //nolint:cyclop,funlen func (enc *Encoder) encode(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) { i, ok := v.Interface().(time.Time) @@ -192,12 +186,12 @@ func (enc *Encoder) encode(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, e if v.Type().Implements(textMarshalerType) { if ctx.isRoot() { - return nil, errTextMarshalerCannotBeAtRoot + return nil, fmt.Errorf("toml: type %s implementing the TextMarshaler interface cannot be a root element", v.Type()) } text, err := v.Interface().(encoding.TextMarshaler).MarshalText() if err != nil { - return nil, fmt.Errorf("encode: %w", err) + return nil, err } b = enc.encodeString(b, string(text), ctx.options) @@ -215,7 +209,7 @@ func (enc *Encoder) encode(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, e return enc.encodeSlice(b, ctx, v) case reflect.Interface: if v.IsNil() { - return nil, errNilInterface + return nil, fmt.Errorf("toml: encoding a nil interface is not supported") } return enc.encode(b, ctx, v.Elem()) @@ -244,7 +238,7 @@ func (enc *Encoder) encode(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, e case reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8, reflect.Int: b = strconv.AppendInt(b, v.Int(), 10) default: - return nil, fmt.Errorf("encode(type %s): %w", v.Kind(), errUnsupportedValue) + return nil, fmt.Errorf("toml: cannot encode value of type %s", v.Kind()) } return b, nil @@ -412,8 +406,6 @@ func (enc *Encoder) encodeTableHeader(ctx encoderCtx, b []byte) ([]byte, error) return b, nil } -var errTomlNoMultiline = errors.New("TOML does not support multiline keys") - //nolint:cyclop func (enc *Encoder) encodeKey(b []byte, k string) ([]byte, error) { needsQuotation := false @@ -425,7 +417,7 @@ func (enc *Encoder) encodeKey(b []byte, k string) ([]byte, error) { } if c == '\n' { - return nil, errTomlNoMultiline + return nil, fmt.Errorf("toml: new line characters in keys are not supported") } if c == literalQuote { @@ -445,11 +437,9 @@ func (enc *Encoder) encodeKey(b []byte, k string) ([]byte, error) { } } -var errNotSupportedAsMapKey = errors.New("type not supported as map key") - func (enc *Encoder) encodeMap(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) { if v.Type().Key().Kind() != reflect.String { - return nil, fmt.Errorf("encodeMap '%s': %w", v.Type().Key().Kind(), errNotSupportedAsMapKey) + return nil, fmt.Errorf("toml: type %s is not supported as a map key", v.Type().Key().Kind()) } var ( @@ -658,10 +648,7 @@ func (enc *Encoder) encodeTableInline(b []byte, ctx encoderCtx, t table) ([]byte return b, nil } -var ( - errNilInterface = errors.New("nil interface not supported") - textMarshalerType = reflect.TypeOf(new(encoding.TextMarshaler)).Elem() -) +var textMarshalerType = reflect.TypeOf(new(encoding.TextMarshaler)).Elem() func willConvertToTable(ctx encoderCtx, v reflect.Value) (bool, error) { if v.Type() == timeType || v.Type().Implements(textMarshalerType) { @@ -674,7 +661,7 @@ func willConvertToTable(ctx encoderCtx, v reflect.Value) (bool, error) { return !ctx.inline, nil case reflect.Interface: if v.IsNil() { - return false, errNilInterface + return false, fmt.Errorf("toml: encoding a nil interface is not supported") } return willConvertToTable(ctx, v.Elem()) @@ -694,7 +681,7 @@ func willConvertToTableOrArrayTable(ctx encoderCtx, v reflect.Value) (bool, erro if t.Kind() == reflect.Interface { if v.IsNil() { - return false, errNilInterface + return false, fmt.Errorf("toml: encoding a nil interface is not supported") } return willConvertToTableOrArrayTable(ctx, v.Elem()) diff --git a/parser.go b/parser.go index 9724190a..5b6e3bac 100644 --- a/parser.go +++ b/parser.go @@ -2,7 +2,6 @@ package toml import ( "bytes" - "errors" "fmt" "strconv" @@ -225,19 +224,13 @@ func (p *parser) parseKeyval(b []byte) (ast.Reference, []byte, error) { return ref, b, err } -var ( - errExpectedValNotEOF = errors.New("expected value, not eof") - errExpectedTrue = errors.New("expected 'true'") - errExpectedFalse = errors.New("expected 'false'") -) - //nolint:cyclop,funlen func (p *parser) parseVal(b []byte) (ast.Reference, []byte, error) { // val = string / boolean / array / inline-table / date-time / float / integer var ref ast.Reference if len(b) == 0 { - return ref, nil, errExpectedValNotEOF + return ref, nil, newDecodeError(b, "expected value, not eof") } var err error @@ -278,7 +271,7 @@ func (p *parser) parseVal(b []byte) (ast.Reference, []byte, error) { return ref, b, err case 't': if !scanFollowsTrue(b) { - return ref, nil, errExpectedTrue + return ref, nil, newDecodeError(atmost(b, 4), "expected 'true'") } ref = p.builder.Push(ast.Node{ @@ -289,7 +282,7 @@ func (p *parser) parseVal(b []byte) (ast.Reference, []byte, error) { return ref, b[4:], nil case 'f': if !scanFollowsFalse(b) { - return ast.Reference{}, nil, errExpectedFalse + return ref, nil, newDecodeError(atmost(b, 5), "expected 'false'") } ref = p.builder.Push(ast.Node{ @@ -307,6 +300,13 @@ func (p *parser) parseVal(b []byte) (ast.Reference, []byte, error) { } } +func atmost(b []byte, n int) []byte { + if n >= len(b) { + return b + } + return b[:n] +} + func (p *parser) parseLiteralString(b []byte) ([]byte, []byte, error) { v, rest, err := scanLiteralString(b) if err != nil { @@ -370,8 +370,6 @@ func (p *parser) parseInlineTable(b []byte) (ast.Reference, []byte, error) { return parent, rest, err } -var errArrayCannotStartWithComma = errors.New("array cannot start with comma") - //nolint:funlen,cyclop func (p *parser) parseValArray(b []byte) (ast.Reference, []byte, error) { // array = array-open [ array-values ] ws-comment-newline array-close @@ -409,7 +407,7 @@ func (p *parser) parseValArray(b []byte) (ast.Reference, []byte, error) { if b[0] == ',' { if first { - return parent, nil, errArrayCannotStartWithComma + return parent, nil, newDecodeError(b[0:1], "array cannot start with comma") } b = b[1:] @@ -494,8 +492,6 @@ func (p *parser) parseMultilineLiteralString(b []byte) ([]byte, []byte, error) { return token[i : len(token)-3], rest, err } -var errInvalidEscapeChar = errors.New("invalid escaped character") - //nolint:funlen,gocognit,cyclop func (p *parser) parseMultilineBasicString(b []byte) ([]byte, []byte, error) { // ml-basic-string = ml-basic-string-delim [ newline ] ml-basic-body @@ -582,7 +578,7 @@ func (p *parser) parseMultilineBasicString(b []byte) ([]byte, []byte, error) { builder.WriteString(x) i += 8 default: - return nil, nil, fmt.Errorf("parseMultilineBasicString: %w - %#U", errInvalidEscapeChar, c) + return nil, nil, newDecodeError(token[i:i+1], "invalid escaped character %#U", c) } } else { builder.WriteByte(c) @@ -721,7 +717,7 @@ func (p *parser) parseBasicString(b []byte) ([]byte, []byte, error) { builder.WriteString(x) i += 8 default: - return nil, nil, fmt.Errorf("parseBasicString: %w - %#U", errInvalidEscapeChar, c) + return nil, nil, newDecodeError(token[i:i+1], "invalid escaped character %#U", c) } } else { builder.WriteByte(c) @@ -731,18 +727,17 @@ func (p *parser) parseBasicString(b []byte) ([]byte, []byte, error) { return builder.Bytes(), rest, nil } -var errUnicodePointNeedsRightCountChar = errors.New("unicode point needs right number of hex characters") - func hexToString(b []byte, length int) (string, error) { if len(b) < length { - return "", fmt.Errorf("hexToString: %w - %d", errUnicodePointNeedsRightCountChar, length) + return "", newDecodeError(b, "unicode point needs %d character, not %d", length, len(b)) } + b = b[:length] //nolint:godox // TODO: slow - intcode, err := strconv.ParseInt(string(b[:length]), 16, 32) + intcode, err := strconv.ParseInt(string(b), 16, 32) if err != nil { - return "", fmt.Errorf("hexToString: %w", err) + return "", newDecodeError(b, "couldn't parse hexadecimal number: %w", err) } return string(rune(intcode)), nil @@ -757,17 +752,12 @@ func (p *parser) parseWhitespace(b []byte) []byte { return rest } -var ( - errExpectedInf = errors.New("expected 'inf'") - errExpectedNan = errors.New("expected 'nan'") -) - //nolint:cyclop func (p *parser) parseIntOrFloatOrDateTime(b []byte) (ast.Reference, []byte, error) { switch b[0] { case 'i': if !scanFollowsInf(b) { - return ast.Reference{}, nil, errExpectedInf + return ast.Reference{}, nil, newDecodeError(atmost(b, 3), "expected 'inf'") } return p.builder.Push(ast.Node{ @@ -776,7 +766,7 @@ func (p *parser) parseIntOrFloatOrDateTime(b []byte) (ast.Reference, []byte, err }), b[3:], nil case 'n': if !scanFollowsNan(b) { - return ast.Reference{}, nil, errExpectedNan + return ast.Reference{}, nil, newDecodeError(atmost(b, 3), "expected 'nan'") } return p.builder.Push(ast.Node{ @@ -821,8 +811,6 @@ func digitsToInt(b []byte) int { return x } -var errTimezoneButNoTimeComponent = errors.New("possible DateTime cannot have a timezone but no time component") - //nolint:gocognit,cyclop func (p *parser) scanDateTime(b []byte) (ast.Reference, []byte, error) { // scans for contiguous characters in [0-9T:Z.+-], and up to one space if @@ -867,7 +855,7 @@ byteLoop: } } else { if hasTz { - return ast.Reference{}, nil, errTimezoneButNoTimeComponent + return ast.Reference{}, nil, newDecodeError(b, "date-time has timezone but not time component") } kind = ast.LocalDate } @@ -878,12 +866,6 @@ byteLoop: }), b[i:], nil } -var ( - errUnexpectedCharI = fmt.Errorf("unexpected character i while scanning for a number") - errUnexpectedCharN = fmt.Errorf("unexpected character n while scanning for a number") - errExpectedIntOrFloat = fmt.Errorf("expected integer or float") -) - //nolint:funlen,gocognit,cyclop func (p *parser) scanIntOrFloat(b []byte) (ast.Reference, []byte, error) { i := 0 @@ -940,7 +922,7 @@ func (p *parser) scanIntOrFloat(b []byte) (ast.Reference, []byte, error) { }), b[i+3:], nil } - return ast.Reference{}, nil, errUnexpectedCharI + return ast.Reference{}, nil, newDecodeError(b[i:i+1], "unexpected character 'i' while scanning for a number") } if c == 'n' { @@ -951,14 +933,14 @@ func (p *parser) scanIntOrFloat(b []byte) (ast.Reference, []byte, error) { }), b[i+3:], nil } - return ast.Reference{}, nil, errUnexpectedCharN + return ast.Reference{}, nil, newDecodeError(b[i:i+1], "unexpected character 'n' while scanning for a number") } break } if i == 0 { - return ast.Reference{}, b, errExpectedIntOrFloat + return ast.Reference{}, b, newDecodeError(b, "incomplete number") } kind := ast.Integer diff --git a/scanner.go b/scanner.go index b63ccb40..047aef53 100644 --- a/scanner.go +++ b/scanner.go @@ -1,9 +1,5 @@ package toml -import ( - "errors" -) - func scanFollows(b []byte, pattern string) bool { n := len(pattern) @@ -83,22 +79,17 @@ func scanMultilineLiteralString(b []byte) ([]byte, []byte, error) { return nil, nil, newDecodeError(b[len(b):], `multiline literal string not terminated by '''`) } -var ( - errWindowsNewLineMissing = errors.New(`windows new line missing \n`) - errWindowsNewLineCRLF = errors.New(`windows new line should be \r\n`) -) - func scanWindowsNewline(b []byte) ([]byte, []byte, error) { - const lenLF = 2 - if len(b) < lenLF { - return nil, nil, errWindowsNewLineMissing + const lenCRLF = 2 + if len(b) < lenCRLF { + return nil, nil, newDecodeError(b, "windows new line expected") } if b[1] != '\n' { - return nil, nil, errWindowsNewLineCRLF + return nil, nil, newDecodeError(b, `windows new line should be \r\n`) } - return b[:lenLF], b[lenLF:], nil + return b[:lenCRLF], b[lenCRLF:], nil } func scanWhitespace(b []byte) ([]byte, []byte) { @@ -116,8 +107,6 @@ func scanWhitespace(b []byte) ([]byte, []byte) { //nolint:unparam func scanComment(b []byte) ([]byte, []byte) { - // ;; Comment - // // comment-start-symbol = %x23 ; # // non-ascii = %x80-D7FF / %xE000-10FFFF // non-eol = %x09 / %x20-7F / non-ascii @@ -132,10 +121,6 @@ func scanComment(b []byte) ([]byte, []byte) { return b, nil } -var errBasicLineNotTerminatedByQuote = errors.New(`basic string not terminated by "`) - -//nolint:godox -// TODO perform validation on the string? func scanBasicString(b []byte) ([]byte, []byte, error) { // basic-string = quotation-mark *basic-char quotation-mark // quotation-mark = %x22 ; " @@ -156,11 +141,9 @@ func scanBasicString(b []byte) ([]byte, []byte, error) { } } - return nil, nil, errBasicLineNotTerminatedByQuote + return nil, nil, newDecodeError(b[len(b):], `basic string not terminated by "`) } -//nolint:godox -// TODO perform validation on the string? func scanMultilineBasicString(b []byte) ([]byte, []byte, error) { // ml-basic-string = ml-basic-string-delim [ newline ] ml-basic-body // ml-basic-string-delim diff --git a/targets.go b/targets.go index 68f94287..fd3eaec8 100644 --- a/targets.go +++ b/targets.go @@ -1,7 +1,6 @@ package toml import ( - "errors" "fmt" "math" "reflect" @@ -14,19 +13,19 @@ type target interface { get() reflect.Value // Store a string at the target. - setString(v string) error + setString(v string) // Store a boolean at the target - setBool(v bool) error + setBool(v bool) // Store an int64 at the target - setInt64(v int64) error + setInt64(v int64) // Store a float64 at the target - setFloat64(v float64) error + setFloat64(v float64) // Stores any value at the target - set(v reflect.Value) error + set(v reflect.Value) } // valueTarget just contains a reflect.Value that can be set. @@ -37,34 +36,24 @@ func (t valueTarget) get() reflect.Value { return reflect.Value(t) } -func (t valueTarget) set(v reflect.Value) error { +func (t valueTarget) set(v reflect.Value) { reflect.Value(t).Set(v) - - return nil } -func (t valueTarget) setString(v string) error { +func (t valueTarget) setString(v string) { t.get().SetString(v) - - return nil } -func (t valueTarget) setBool(v bool) error { +func (t valueTarget) setBool(v bool) { t.get().SetBool(v) - - return nil } -func (t valueTarget) setInt64(v int64) error { +func (t valueTarget) setInt64(v int64) { t.get().SetInt(v) - - return nil } -func (t valueTarget) setFloat64(v float64) error { +func (t valueTarget) setFloat64(v float64) { t.get().SetFloat(v) - - return nil } // interfaceTarget wraps an other target to dereference on get. @@ -76,49 +65,24 @@ func (t interfaceTarget) get() reflect.Value { return t.x.get().Elem() } -func (t interfaceTarget) set(v reflect.Value) error { - err := t.x.set(v) - if err != nil { - return fmt.Errorf("interfaceTarget set: %w", err) - } - - return nil +func (t interfaceTarget) set(v reflect.Value) { + t.x.set(v) } -func (t interfaceTarget) setString(v string) error { - err := t.x.setString(v) - if err != nil { - return fmt.Errorf("interfaceTarget setString: %w", err) - } - - return nil +func (t interfaceTarget) setString(v string) { + t.x.setString(v) } -func (t interfaceTarget) setBool(v bool) error { - err := t.x.setBool(v) - if err != nil { - return fmt.Errorf("interfaceTarget setBool: %w", err) - } - - return nil +func (t interfaceTarget) setBool(v bool) { + t.x.setBool(v) } -func (t interfaceTarget) setInt64(v int64) error { - err := t.x.setInt64(v) - if err != nil { - return fmt.Errorf("interfaceTarget setInt64: %w", err) - } - - return nil +func (t interfaceTarget) setInt64(v int64) { + t.x.setInt64(v) } -func (t interfaceTarget) setFloat64(v float64) error { - err := t.x.setFloat64(v) - if err != nil { - return fmt.Errorf("interfaceTarget setFloat64: %w", err) - } - - return nil +func (t interfaceTarget) setFloat64(v float64) { + t.x.setFloat64(v) } // mapTarget targets a specific key of a map. @@ -131,33 +95,26 @@ func (t mapTarget) get() reflect.Value { return t.v.MapIndex(t.k) } -func (t mapTarget) set(v reflect.Value) error { +func (t mapTarget) set(v reflect.Value) { t.v.SetMapIndex(t.k, v) - - return nil } -func (t mapTarget) setString(v string) error { - return t.set(reflect.ValueOf(v)) +func (t mapTarget) setString(v string) { + t.set(reflect.ValueOf(v)) } -func (t mapTarget) setBool(v bool) error { - return t.set(reflect.ValueOf(v)) +func (t mapTarget) setBool(v bool) { + t.set(reflect.ValueOf(v)) } -func (t mapTarget) setInt64(v int64) error { - return t.set(reflect.ValueOf(v)) +func (t mapTarget) setInt64(v int64) { + t.set(reflect.ValueOf(v)) } -func (t mapTarget) setFloat64(v float64) error { - return t.set(reflect.ValueOf(v)) +func (t mapTarget) setFloat64(v float64) { + t.set(reflect.ValueOf(v)) } -var ( - errValIndexExpectingSlice = errors.New("expecting a slice") - errValIndexCannotInitSlice = errors.New("cannot initialize a slice") -) - //nolint:cyclop // makes sure that the value pointed at by t is indexable (Slice, Array), or // dereferences to an indexable (Ptr, Interface). @@ -167,43 +124,20 @@ func ensureValueIndexable(t target) error { switch f.Type().Kind() { case reflect.Slice: if f.IsNil() { - err := t.set(reflect.MakeSlice(f.Type(), 0, 0)) - if err != nil { - return fmt.Errorf("ensureValueIndexable: %w", err) - } - + t.set(reflect.MakeSlice(f.Type(), 0, 0)) return nil } case reflect.Interface: if f.IsNil() || f.Elem().Type() != sliceInterfaceType { - err := t.set(reflect.MakeSlice(sliceInterfaceType, 0, 0)) - if err != nil { - return fmt.Errorf("ensureValueIndexable: %w", err) - } - + t.set(reflect.MakeSlice(sliceInterfaceType, 0, 0)) return nil } - - if f.Elem().Type().Kind() != reflect.Slice { - return fmt.Errorf("ensureValueIndexable: %w, not a %s", errValIndexExpectingSlice, f.Kind()) - } case reflect.Ptr: - if f.IsNil() { - ptr := reflect.New(f.Type().Elem()) - - err := t.set(ptr) - if err != nil { - return fmt.Errorf("ensureValueIndexable: %w", err) - } - - f = t.get() - } - - return ensureValueIndexable(valueTarget(f.Elem())) + panic("pointer should have already been dereferenced") case reflect.Array: // arrays are always initialized. default: - return fmt.Errorf("ensureValueIndexable: %w with %s", errValIndexCannotInitSlice, f.Kind()) + return fmt.Errorf("toml: cannot store array in a %s", f.Kind()) } return nil @@ -214,69 +148,44 @@ var ( mapStringInterfaceType = reflect.TypeOf(map[string]interface{}{}) ) -func ensureMapIfInterface(x target) error { +func ensureMapIfInterface(x target) { v := x.get() if v.Kind() == reflect.Interface && v.IsNil() { newElement := reflect.MakeMap(mapStringInterfaceType) - err := x.set(newElement) - if err != nil { - return fmt.Errorf("ensureMapIfInterface: %w", err) - } + x.set(newElement) } - - return nil } -var errSetStringCannotAssignString = errors.New("cannot assign string") - func setString(t target, v string) error { f := t.get() switch f.Kind() { case reflect.String: - err := t.setString(v) - if err != nil { - return fmt.Errorf("setString: %w", err) - } - - return nil + t.setString(v) case reflect.Interface: - err := t.set(reflect.ValueOf(v)) - if err != nil { - return fmt.Errorf("setString: %w", err) - } - - return nil + t.set(reflect.ValueOf(v)) default: - return fmt.Errorf("setString: %w to a %s", errSetStringCannotAssignString, f.Kind()) + return fmt.Errorf("toml: cannot assign string to a %s", f.Kind()) } -} -var errSetBoolCannotAssignBool = errors.New("cannot assign bool") + return nil +} func setBool(t target, v bool) error { f := t.get() switch f.Kind() { case reflect.Bool: - err := t.setBool(v) - if err != nil { - return fmt.Errorf("setBool: %w", err) - } - - return nil + t.setBool(v) case reflect.Interface: - err := t.set(reflect.ValueOf(v)) - if err != nil { - return fmt.Errorf("setBool: %w", err) - } - - return nil + t.set(reflect.ValueOf(v)) default: - return fmt.Errorf("setBool: %w to a %s", errSetBoolCannotAssignBool, f.String()) + return fmt.Errorf("toml: cannot assign boolean to a %s", f.Kind()) } + + return nil } const ( @@ -284,207 +193,104 @@ const ( minInt = -maxInt - 1 ) -var ( - errSetInt64InInt32 = errors.New("does not fit in an int32") - errSetInt64InInt16 = errors.New("does not fit in an int16") - errSetInt64InInt8 = errors.New("does not fit in an int8") - errSetInt64InInt = errors.New("does not fit in an int") - errSetInt64InUint64 = errors.New("negative integer does not fit in an uint64") - errSetInt64InUint32 = errors.New("negative integer does not fit in an uint32") - errSetInt64InUint32Max = errors.New("integer does not fit in an uint32") - errSetInt64InUint16 = errors.New("negative integer does not fit in an uint16") - errSetInt64InUint16Max = errors.New("integer does not fit in an uint16") - errSetInt64InUint8 = errors.New("negative integer does not fit in an uint8") - errSetInt64InUint8Max = errors.New("integer does not fit in an uint8") - errSetInt64InUint = errors.New("negative integer does not fit in an uint") - errSetInt64Unknown = errors.New("does not fit in an uint") -) - //nolint:funlen,gocognit,cyclop,gocyclo func setInt64(t target, v int64) error { f := t.get() switch f.Kind() { case reflect.Int64: - err := t.setInt64(v) - if err != nil { - return fmt.Errorf("setInt64: %w", err) - } - - return nil + t.setInt64(v) case reflect.Int32: if v < math.MinInt32 || v > math.MaxInt32 { - return fmt.Errorf("setInt64: integer %d %w", v, errSetInt64InInt32) - } - - err := t.set(reflect.ValueOf(int32(v))) - if err != nil { - return fmt.Errorf("setInt64: %w", err) + return fmt.Errorf("toml: number %d does not fit in an int32", v) } + t.set(reflect.ValueOf(int32(v))) return nil case reflect.Int16: if v < math.MinInt16 || v > math.MaxInt16 { - return fmt.Errorf("setInt64: integer %d %w", v, errSetInt64InInt16) - } - - err := t.set(reflect.ValueOf(int16(v))) - if err != nil { - return fmt.Errorf("setInt64: %w", err) + return fmt.Errorf("toml: number %d does not fit in an int16", v) } - return nil + t.set(reflect.ValueOf(int16(v))) case reflect.Int8: if v < math.MinInt8 || v > math.MaxInt8 { - return fmt.Errorf("setInt64: integer %d %w", v, errSetInt64InInt8) - } - - err := t.set(reflect.ValueOf(int8(v))) - if err != nil { - return fmt.Errorf("setInt64: %w", err) + return fmt.Errorf("toml: number %d does not fit in an int8", v) } - return nil + t.set(reflect.ValueOf(int8(v))) case reflect.Int: if v < minInt || v > maxInt { - return fmt.Errorf("setInt64: integer %d %w", v, errSetInt64InInt) + return fmt.Errorf("toml: number %d does not fit in an int", v) } - err := t.set(reflect.ValueOf(int(v))) - if err != nil { - return fmt.Errorf("setInt64: %w", err) - } - - return nil + t.set(reflect.ValueOf(int(v))) case reflect.Uint64: if v < 0 { - return fmt.Errorf("setInt64: %d, %w", v, errSetInt64InUint64) - } - - err := t.set(reflect.ValueOf(uint64(v))) - if err != nil { - return fmt.Errorf("setInt64: %w", err) + return fmt.Errorf("toml: negative number %d does not fit in an uint64", v) } - return nil + t.set(reflect.ValueOf(uint64(v))) case reflect.Uint32: - if v < 0 { - return fmt.Errorf("setInt64: %d, %w", v, errSetInt64InUint32) - } - - if v > math.MaxUint32 { - return fmt.Errorf("setInt64: %d, %w", v, errSetInt64InUint32Max) + if v < 0 || v > math.MaxUint32 { + return fmt.Errorf("toml: negative number %d does not fit in an uint32", v) } - err := t.set(reflect.ValueOf(uint32(v))) - if err != nil { - return fmt.Errorf("setInt64: %w", err) - } - - return nil + t.set(reflect.ValueOf(uint32(v))) case reflect.Uint16: - if v < 0 { - return fmt.Errorf("setInt64: %d, %w", v, errSetInt64InUint16) + if v < 0 || v > math.MaxUint16 { + return fmt.Errorf("toml: negative number %d does not fit in an uint16", v) } - if v > math.MaxUint16 { - return fmt.Errorf("setInt64: %d, %w", v, errSetInt64InUint16Max) - } - - err := t.set(reflect.ValueOf(uint16(v))) - if err != nil { - return fmt.Errorf("setInt64: %w", err) - } - - return nil + t.set(reflect.ValueOf(uint16(v))) case reflect.Uint8: - if v < 0 { - return fmt.Errorf("setInt64: %d, %w", v, errSetInt64InUint8) - } - - if v > math.MaxUint8 { - return fmt.Errorf("setInt64: %d, %w", v, errSetInt64InUint8Max) - } - - err := t.set(reflect.ValueOf(uint8(v))) - if err != nil { - return fmt.Errorf("setInt64: %w", err) + if v < 0 || v > math.MaxUint8 { + return fmt.Errorf("toml: negative number %d does not fit in an uint8", v) } - return nil + t.set(reflect.ValueOf(uint8(v))) case reflect.Uint: if v < 0 { - return fmt.Errorf("setInt64: %d, %w", v, errSetInt64InUint) - } - - err := t.set(reflect.ValueOf(uint(v))) - if err != nil { - return fmt.Errorf("setInt64: %w", err) + return fmt.Errorf("toml: negative number %d does not fit in an uint", v) } - return nil + t.set(reflect.ValueOf(uint(v))) case reflect.Interface: - err := t.set(reflect.ValueOf(v)) - if err != nil { - return fmt.Errorf("setInt64: %w", err) - } - - return nil + t.set(reflect.ValueOf(v)) default: - return fmt.Errorf("setInt64: %s, %w", f.String(), errSetInt64Unknown) + return fmt.Errorf("toml: integer cannot be assigned to %s", f.Kind()) } -} -var ( - errSetFloat64InFloat32Max = errors.New("does not fit in an float32") - errSetFloat64Unknown = errors.New("does not fit in an float32") -) + return nil +} func setFloat64(t target, v float64) error { f := t.get() switch f.Kind() { case reflect.Float64: - err := t.setFloat64(v) - if err != nil { - return fmt.Errorf("setFloat64: %w", err) - } - - return nil + t.setFloat64(v) case reflect.Float32: if v > math.MaxFloat32 { - return fmt.Errorf("setFloat64: %f %w", v, errSetFloat64InFloat32Max) + return fmt.Errorf("toml: number %f does not fit in a float32", v) } - err := t.set(reflect.ValueOf(float32(v))) - if err != nil { - return fmt.Errorf("setFloat64: %w", err) - } - - return nil + t.set(reflect.ValueOf(float32(v))) case reflect.Interface: - err := t.set(reflect.ValueOf(v)) - if err != nil { - return fmt.Errorf("setFloat64: %w", err) - } - - return nil + t.set(reflect.ValueOf(v)) default: - return fmt.Errorf("setFloat64: %s %w", f.String(), errSetFloat64Unknown) + return fmt.Errorf("toml: float cannot be assigned to %s", f.Kind()) } -} -var ( - errElementAtCannotOn = errors.New("cannot elementAt") - errElementAtCannotOnUnknown = errors.New("cannot elementAt") -) + return nil +} //nolint:cyclop // Returns the element at idx of the value pointed at by target, or an error if // t does not point to an indexable. // If the target points to an Array and idx is out of bounds, it returns // (nil, nil) as this is not a fatal error (the unmarshaler will skip). -func elementAt(t target, idx int) (target, error) { +func elementAt(t target, idx int) target { f := t.get() switch f.Kind() { @@ -493,42 +299,30 @@ func elementAt(t target, idx int) (target, error) { // TODO: use the idx function argument and avoid alloc if possible. idx := f.Len() - err := t.set(reflect.Append(f, reflect.New(f.Type().Elem()).Elem())) - if err != nil { - return nil, fmt.Errorf("elementAt: %w", err) - } + t.set(reflect.Append(f, reflect.New(f.Type().Elem()).Elem())) - return valueTarget(t.get().Index(idx)), nil + return valueTarget(t.get().Index(idx)) case reflect.Array: if idx >= f.Len() { - return nil, nil + return nil } - return valueTarget(f.Index(idx)), nil + return valueTarget(f.Index(idx)) case reflect.Interface: - if f.IsNil() { - panic("interface should have been initialized") - } + // This function is called after ensureValueIndexable, so it's + // guaranteed that f contains an initialized slice. ifaceElem := f.Elem() - if ifaceElem.Kind() != reflect.Slice { - return nil, fmt.Errorf("elementAt: %w on a %s", errElementAtCannotOn, f.Kind()) - } - idx := ifaceElem.Len() newElem := reflect.New(ifaceElem.Type().Elem()).Elem() newSlice := reflect.Append(ifaceElem, newElem) - err := t.set(newSlice) - if err != nil { - return nil, fmt.Errorf("elementAt: %w", err) - } + t.set(newSlice) - return valueTarget(t.get().Elem().Index(idx)), nil - case reflect.Ptr: - return elementAt(valueTarget(f.Elem()), idx) + return valueTarget(t.get().Elem().Index(idx)) default: - return nil, fmt.Errorf("elementAt: %w on a %s", errElementAtCannotOnUnknown, f.Kind()) + // Why ensureValueIndexable let it go through? + panic(fmt.Errorf("elementAt received unhandled value type: %s", f.Kind())) } } @@ -539,31 +333,19 @@ func (d *decoder) scopeTableTarget(shouldAppend bool, t target, name string) (ta switch x.Kind() { // Kinds that need to recurse case reflect.Interface: - t, err := scopeInterface(shouldAppend, t) - if err != nil { - return t, false, fmt.Errorf("scopeTableTarget: %w", err) - } - + t := scopeInterface(shouldAppend, t) return d.scopeTableTarget(shouldAppend, t, name) case reflect.Ptr: - t, err := scopePtr(t) - if err != nil { - return t, false, fmt.Errorf("scopeTableTarget: %w", err) - } - + t := scopePtr(t) return d.scopeTableTarget(shouldAppend, t, name) case reflect.Slice: - t, err := scopeSlice(shouldAppend, t) - if err != nil { - return t, false, fmt.Errorf("scopeTableTarget: %w", err) - } + t := scopeSlice(shouldAppend, t) shouldAppend = false - return d.scopeTableTarget(shouldAppend, t, name) case reflect.Array: t, err := d.scopeArray(shouldAppend, t) if err != nil { - return t, false, fmt.Errorf("scopeTableTarget: %w", err) + return t, false, err } shouldAppend = false @@ -574,11 +356,7 @@ func (d *decoder) scopeTableTarget(shouldAppend bool, t target, name string) (ta return scopeStruct(x, name) case reflect.Map: if x.IsNil() { - err := t.set(reflect.MakeMap(x.Type())) - if err != nil { - return t, false, fmt.Errorf("scopeTableTarget: %w", err) - } - + t.set(reflect.MakeMap(x.Type())) x = t.get() } @@ -588,42 +366,29 @@ func (d *decoder) scopeTableTarget(shouldAppend bool, t target, name string) (ta } } -func scopeInterface(shouldAppend bool, t target) (target, error) { - err := initInterface(shouldAppend, t) - if err != nil { - return t, err - } - - return interfaceTarget{t}, nil +func scopeInterface(shouldAppend bool, t target) target { + initInterface(shouldAppend, t) + return interfaceTarget{t} } -func scopePtr(t target) (target, error) { - err := initPtr(t) - if err != nil { - return t, err - } - - return valueTarget(t.get().Elem()), nil +func scopePtr(t target) target { + initPtr(t) + return valueTarget(t.get().Elem()) } -func initPtr(t target) error { +func initPtr(t target) { x := t.get() if !x.IsNil() { - return nil + return } - err := t.set(reflect.New(x.Type().Elem())) - if err != nil { - return fmt.Errorf("initPtr: %w", err) - } - - return nil + t.set(reflect.New(x.Type().Elem())) } // initInterface makes sure that the interface pointed at by the target is not // nil. // Returns the target to the initialized value of the target. -func initInterface(shouldAppend bool, t target) error { +func initInterface(shouldAppend bool, t target) { x := t.get() if x.Kind() != reflect.Interface { @@ -631,7 +396,7 @@ func initInterface(shouldAppend bool, t target) error { } if !x.IsNil() && (x.Elem().Type() == sliceInterfaceType || x.Elem().Type() == mapStringInterfaceType) { - return nil + return } var newElement reflect.Value @@ -641,55 +406,43 @@ func initInterface(shouldAppend bool, t target) error { newElement = reflect.MakeMap(mapStringInterfaceType) } - err := t.set(newElement) - if err != nil { - return fmt.Errorf("initInterface: %w", err) - } - - return nil + t.set(newElement) } -func scopeSlice(shouldAppend bool, t target) (target, error) { +func scopeSlice(shouldAppend bool, t target) target { v := t.get() if shouldAppend { newElem := reflect.New(v.Type().Elem()) newSlice := reflect.Append(v, newElem.Elem()) - err := t.set(newSlice) - if err != nil { - return t, fmt.Errorf("scopeSlice: %w", err) - } + t.set(newSlice) v = t.get() } - return valueTarget(v.Index(v.Len() - 1)), nil + return valueTarget(v.Index(v.Len() - 1)) } -var errScopeArrayNotEnoughSpace = errors.New("not enough space in the array") - func (d *decoder) scopeArray(shouldAppend bool, t target) (target, error) { v := t.get() idx := d.arrayIndex(shouldAppend, v) if idx >= v.Len() { - return nil, errScopeArrayNotEnoughSpace + return nil, fmt.Errorf("toml: impossible to insert element beyond array's size: %d", v.Len()) } return valueTarget(v.Index(idx)), nil } -var errScopeMapCannotConvertStringToKey = errors.New("cannot convert string into map key type") - func scopeMap(v reflect.Value, name string) (target, bool, error) { k := reflect.ValueOf(name) keyType := v.Type().Key() if !k.Type().AssignableTo(keyType) { if !k.Type().ConvertibleTo(keyType) { - return nil, false, fmt.Errorf("scopeMap: %w %s", errScopeMapCannotConvertStringToKey, keyType) + return nil, false, fmt.Errorf("toml: cannot convert map key of type %s to expected type %s", k.Type(), keyType) } k = k.Convert(keyType) diff --git a/targets_test.go b/targets_test.go index 7b57fe06..c895ad5a 100644 --- a/targets_test.go +++ b/targets_test.go @@ -128,14 +128,12 @@ func TestPushNew(t *testing.T) { x, _, err := dec.scopeTableTarget(false, valueTarget(reflect.ValueOf(&d).Elem()), "A") require.NoError(t, err) - n, err := elementAt(x, 0) - require.NoError(t, err) - require.NoError(t, n.setString("hello")) + n := elementAt(x, 0) + n.setString("hello") require.Equal(t, []string{"hello"}, d.A) - n, err = elementAt(x, 1) - require.NoError(t, err) - require.NoError(t, n.setString("world")) + n = elementAt(x, 1) + n.setString("world") require.Equal(t, []string{"hello", "world"}, d.A) }) @@ -151,13 +149,11 @@ func TestPushNew(t *testing.T) { x, _, err := dec.scopeTableTarget(false, valueTarget(reflect.ValueOf(&d).Elem()), "A") require.NoError(t, err) - n, err := elementAt(x, 0) - require.NoError(t, err) + n := elementAt(x, 0) require.NoError(t, setString(n, "hello")) require.Equal(t, []interface{}{"hello"}, d.A) - n, err = elementAt(x, 1) - require.NoError(t, err) + n = elementAt(x, 1) require.NoError(t, setString(n, "world")) require.Equal(t, []interface{}{"hello", "world"}, d.A) }) diff --git a/toml_testgen_support_test.go b/toml_testgen_support_test.go index e2617e64..071ea6da 100644 --- a/toml_testgen_support_test.go +++ b/toml_testgen_support_test.go @@ -94,12 +94,7 @@ func testGenTranslateDesc(input interface{}) interface{} { if ok { dvalue, ok = d["value"] if ok { - var okdt bool - - dtype, okdt = dtypeiface.(string) - if !okdt { - panic(fmt.Sprintf("dtypeiface should be valid string: %v", dtypeiface)) - } + dtype = dtypeiface.(string) switch dtype { case "string": @@ -132,10 +127,7 @@ func testGenTranslateDesc(input interface{}) interface{} { return nil } - a, oka := dvalue.([]interface{}) - if !oka { - panic(fmt.Sprintf("a should be valid []interface{}: %v", a)) - } + a := dvalue.([]interface{}) xs := make([]interface{}, len(a)) diff --git a/unmarshaler.go b/unmarshaler.go index 47a9aeca..3864be65 100644 --- a/unmarshaler.go +++ b/unmarshaler.go @@ -56,7 +56,7 @@ func (d *Decoder) SetStrict(strict bool) { func (d *Decoder) Decode(v interface{}) error { b, err := ioutil.ReadAll(d.r) if err != nil { - return fmt.Errorf("Decode: %w", err) + return fmt.Errorf("toml: %w", err) } p := parser{} @@ -130,20 +130,15 @@ func keyLocation(node ast.Node) []byte { return unsafe.BytesRange(start, end) } -var ( - errFromParserExpectingPointer = errors.New("expecting a pointer as target") - errFromParserExpectingNonNilPointer = errors.New("expecting non nil pointer as target") -) - //nolint:funlen,cyclop func (d *decoder) fromParser(p *parser, v interface{}) error { r := reflect.ValueOf(v) if r.Kind() != reflect.Ptr { - return fmt.Errorf("fromParser: %w, not %s", errFromParserExpectingPointer, r.Kind()) + return fmt.Errorf("toml: decoding can only be performed into a pointer, not %s", r.Kind()) } if r.IsNil() { - return errFromParserExpectingNonNilPointer + return fmt.Errorf("toml: decoding pointer target cannot be nil") } var ( @@ -162,7 +157,7 @@ func (d *decoder) fromParser(p *parser, v interface{}) error { err := d.seen.CheckExpression(node) if err != nil { - return fmt.Errorf("fromParser: %w", err) + return err } var found bool @@ -181,16 +176,13 @@ func (d *decoder) fromParser(p *parser, v interface{}) error { // looks like a table. Otherwise the information // of a table is lost, and marshal cannot do the // round trip. - err := ensureMapIfInterface(current) - if err != nil { - panic(fmt.Sprintf("ensureMapIfInterface: %s", err)) - } + ensureMapIfInterface(current) } case ast.ArrayTable: d.strict.EnterArrayTable(node) current, found, err = d.scopeWithArrayTable(root, node.Key()) default: - panic(fmt.Sprintf("fromParser: this should not be a top level node type: %s", node.Kind)) + panic(fmt.Sprintf("this should not be a top level node type: %s", node.Kind)) } if err != nil { @@ -267,26 +259,18 @@ func (d *decoder) scopeWithArrayTable(x target, key ast.Iterator) (target, bool, v := x.get() if v.Kind() == reflect.Ptr { - x, err = scopePtr(x) - if err != nil { - return x, false, err - } - + x = scopePtr(x) v = x.get() } if v.Kind() == reflect.Interface { - x, err = scopeInterface(true, x) - if err != nil { - return x, found, err - } - + x = scopeInterface(true, x) v = x.get() } switch v.Kind() { case reflect.Slice: - x, err = scopeSlice(true, x) + x = scopeSlice(true, x) case reflect.Array: x, err = d.scopeArray(true, x) default: @@ -334,7 +318,7 @@ func tryTextUnmarshaler(x target, node ast.Node) (bool, error) { if v.Type().Implements(textUnmarshalerType) { err := v.Interface().(encoding.TextUnmarshaler).UnmarshalText(node.Data) if err != nil { - return false, fmt.Errorf("tryTextUnmarshaler: %w", err) + return false, newDecodeError(node.Data, "error calling UnmarshalText: %w", err) } return true, nil @@ -343,7 +327,7 @@ func tryTextUnmarshaler(x target, node ast.Node) (bool, error) { if v.CanAddr() && v.Addr().Type().Implements(textUnmarshalerType) { err := v.Addr().Interface().(encoding.TextUnmarshaler).UnmarshalText(node.Data) if err != nil { - return false, fmt.Errorf("tryTextUnmarshaler: %w", err) + return false, newDecodeError(node.Data, "error calling UnmarshalText: %w", err) } return true, nil @@ -358,11 +342,7 @@ func (d *decoder) unmarshalValue(x target, node ast.Node) error { if v.Kind() == reflect.Ptr { if !v.Elem().IsValid() { - err := x.set(reflect.New(v.Type().Elem())) - if err != nil { - return fmt.Errorf("unmarshalValue: %w", err) - } - + x.set(reflect.New(v.Type().Elem())) v = x.get() } @@ -394,7 +374,7 @@ func (d *decoder) unmarshalValue(x target, node ast.Node) error { case ast.LocalDate: return unmarshalLocalDate(x, node) default: - panic(fmt.Sprintf("unmarshalValue: unhandled unmarshalValue kind %s", node.Kind)) + panic(fmt.Sprintf("unhandled node kind %s", node.Kind)) } } @@ -406,7 +386,9 @@ func unmarshalLocalDate(x target, node ast.Node) error { return err } - return setDate(x, v) + setDate(x, v) + + return nil } func unmarshalLocalDateTime(x target, node ast.Node) error { @@ -421,7 +403,9 @@ func unmarshalLocalDateTime(x target, node ast.Node) error { return newDecodeError(rest, "extra characters at the end of a local date time") } - return setLocalDateTime(x, v) + setLocalDateTime(x, v) + + return nil } func unmarshalDateTime(x target, node ast.Node) error { @@ -432,48 +416,37 @@ func unmarshalDateTime(x target, node ast.Node) error { return err } - return setDateTime(x, v) + setDateTime(x, v) + + return nil } -func setLocalDateTime(x target, v LocalDateTime) error { +func setLocalDateTime(x target, v LocalDateTime) { if x.get().Type() == timeType { cast := v.In(time.Local) - return setDateTime(x, cast) + setDateTime(x, cast) + return } - err := x.set(reflect.ValueOf(v)) - if err != nil { - return fmt.Errorf("setLocalDateTime: %w", err) - } - - return nil + x.set(reflect.ValueOf(v)) } -func setDateTime(x target, v time.Time) error { - err := x.set(reflect.ValueOf(v)) - if err != nil { - return fmt.Errorf("setDateTime: %w", err) - } - - return nil +func setDateTime(x target, v time.Time) { + x.set(reflect.ValueOf(v)) } var timeType = reflect.TypeOf(time.Time{}) -func setDate(x target, v LocalDate) error { +func setDate(x target, v LocalDate) { if x.get().Type() == timeType { cast := v.In(time.Local) - return setDateTime(x, cast) + setDateTime(x, cast) + return } - err := x.set(reflect.ValueOf(v)) - if err != nil { - return fmt.Errorf("setDate: %w", err) - } - - return nil + x.set(reflect.ValueOf(v)) } func unmarshalString(x target, node ast.Node) error { @@ -514,10 +487,7 @@ func unmarshalFloat(x target, node ast.Node) error { func (d *decoder) unmarshalInlineTable(x target, node ast.Node) error { assertNode(ast.InlineTable, node) - err := ensureMapIfInterface(x) - if err != nil { - return fmt.Errorf("unmarshalInlineTable: %w", err) - } + ensureMapIfInterface(x) it := node.Children() for it.Next() { @@ -546,10 +516,7 @@ func (d *decoder) unmarshalArray(x target, node ast.Node) error { for it.Next() { n := it.Node() - v, err := elementAt(x, idx) - if err != nil { - return err - } + v := elementAt(x, idx) if v == nil { // when we go out of bound for an array just stop processing it to diff --git a/unmarshaler_test.go b/unmarshaler_test.go index 6caaadb1..9814cd18 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -38,6 +38,11 @@ func TestUnmarshal_Integers(t *testing.T) { input: `+99`, expected: 99, }, + { + desc: "integer decimal underscore", + input: `123_456`, + expected: 123456, + }, { desc: "integer hex uppercase", input: `0xDEADBEEF`, @@ -58,6 +63,21 @@ func TestUnmarshal_Integers(t *testing.T) { input: `0b11010110`, expected: 0b11010110, }, + { + desc: "double underscore", + input: "12__3", + err: true, + }, + { + desc: "starts with underscore", + input: "_1", + err: true, + }, + { + desc: "ends with underscore", + input: "1_", + err: true, + }, } type doc struct { @@ -71,8 +91,12 @@ func TestUnmarshal_Integers(t *testing.T) { doc := doc{} err := toml.Unmarshal([]byte(`A = `+e.input), &doc) - require.NoError(t, err) - assert.Equal(t, e.expected, doc.A) + if e.err { + require.Error(t, err) + } else { + require.NoError(t, err) + assert.Equal(t, e.expected, doc.A) + } }) } } @@ -799,6 +823,33 @@ B = "data"`, } }, }, + { + desc: "mismatch types int to string", + input: `A = 42`, + gen: func() test { + type S struct { + A string + } + return test{ + target: &S{}, + err: true, + } + }, + }, + { + desc: "mismatch types array of int to interface with non-slice", + input: `A = [[42]]`, + skip: true, + gen: func() test { + type S struct { + A *string + } + return test{ + target: &S{}, + expected: &S{}, + } + }, + }, } for _, e := range examples { @@ -815,6 +866,9 @@ B = "data"`, } err := toml.Unmarshal([]byte(e.input), test.target) if test.err { + if err == nil { + t.Log("=>", test.target) + } require.Error(t, err) } else { require.NoError(t, err) @@ -1030,7 +1084,7 @@ world'`, if e.msg != "" { t.Log("\n" + de.String()) - require.Equal(t, e.msg, de.Error()) + require.Equal(t, "toml: "+e.msg, de.Error()) } }) } From 45ea20024b1affdaff94b4f41c30520787a26baf Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Sat, 8 May 2021 17:03:51 -0400 Subject: [PATCH 193/228] Readme (#535) --- README.md | 374 +++++++++++++++++++++++++++++++++++++++----- doc.go | 4 + errors.go | 5 +- errors_test.go | 21 +++ marshaler.go | 42 +++-- marshaler_test.go | 25 +++ unmarshaler.go | 35 ++++- unmarshaler_test.go | 28 ++++ 8 files changed, 476 insertions(+), 58 deletions(-) create mode 100644 doc.go diff --git a/README.md b/README.md index ee6a3eae..876f31e5 100644 --- a/README.md +++ b/README.md @@ -1,57 +1,351 @@ -# go-toml V2 +# go-toml v2 -Development branch. Use at your own risk. +Go library for the [TOML](https://toml.io/en/) format. -[👉 Discussion on github](https://github.com/pelletier/go-toml/discussions/471). +This library supports [TOML v1.0.0](https://toml.io/en/v1.0.0). -* `toml.Unmarshal()` should work as well as v1. -## Must do +## Development status -### Unmarshal +This is the upcoming major version of go-toml. It is currently in active +development. As of release v2.0.0-beta.1, the library has reached feature parity +with v1, and fixes a lot known bugs and performance issues along the way. -- [x] Unmarshal into maps. -- [x] Support Array Tables. -- [x] Unmarshal into pointers. -- [x] Support Date / times. -- [x] Support struct tags annotations. -- [x] Support Arrays. -- [x] Support Unmarshaler interface. -- [x] Original go-toml unmarshal tests pass. -- [x] Benchmark! -- [x] Abstract AST. -- [x] Original go-toml testgen tests pass. -- [x] Track file position (line, column) for errors. -- [x] Strict mode. -- [ ] Document Unmarshal / Decode +If you do not need the advanced document editing features of v1, you are +encouraged to try out this version. -### Marshal +👉 [Roadmap for v2](https://github.com/pelletier/go-toml/discussions/506). -- [x] Minimal implementation -- [x] Multiline strings -- [ ] Multiline arrays -- [ ] `inline` tag for tables -- [ ] Optional indentation -- [ ] Option to pick default quotes -### Document +## Documentation -- [ ] Gather requirements and design API. +Full API, examples, and implementation notes are available in the Go documentation. -## Ideas +[![Go Reference](https://pkg.go.dev/badge/github.com/pelletier/go-toml/v2.svg)](https://pkg.go.dev/github.com/pelletier/go-toml/v2) -- [ ] Allow types to implement a `ASTUnmarshaler` interface to unmarshal - straight from the AST? -- [x] Rewrite AST to use a single array as storage instead of one allocation per - node. -- [ ] Provide "minimal allocations" option that uses `unsafe` to reuse the input - byte array as storage for strings. -- [x] Cache reflection operations per type. -- [ ] Optimize tracker pass. -## Differences with v1 +## Import -* [unmarshal](https://github.com/pelletier/go-toml/discussions/488) +```go +import "github.com/pelletier/go-toml/v2" +``` + +## Features + +### Stdlib behavior + +As much as possible, this library is designed to behave similarly as the +standard library's `encoding/json`. + +### Performance + +While go-toml favors usability, it is written with performance in mind. Most +operations should not be shockingly slow. + +### Strict mode + +`Decoder` can be set to "strict mode", which makes it error when some parts of +the TOML document was not prevent in the target structure. This is a great way +to check for typos. [See example in the documentation][strict]. + +[strict]: https://pkg.go.dev/github.com/pelletier/go-toml/v2#example-Decoder.SetStrict + +### Contextualized errors + +When decoding errors occur, go-toml returns [`DecodeError`][decode-err]), which +contains a human readable contextualized version of the error. For example: + +``` +2| key1 = "value1" +3| key2 = "missing2" + | ~~~~ missing field +4| key3 = "missing3" +5| key4 = "value4" +``` + +[decode-err]: https://pkg.go.dev/github.com/pelletier/go-toml/v2#DecodeError + +### Local date and time support + +TOML supports native [local date/times][ldt]. It allows to represent a given +date, time, or date-time without relation to a timezone or offset. To support +this use-case, go-toml provides [`LocalDate`][tld], [`LocalTime`][tlt], and +[`LocalDateTime`][tldt]. Those types can be transformed to and from `time.Time`, +making them convenient yet unambiguous structures for their respective TOML +representation. + +[ldt]: https://toml.io/en/v1.0.0#local-date-time +[tld]: https://pkg.go.dev/github.com/pelletier/go-toml/v2#LocalDate +[tlt]: https://pkg.go.dev/github.com/pelletier/go-toml/v2#LocalTime +[tldt]: https://pkg.go.dev/github.com/pelletier/go-toml/v2#LocalDateTime + +## Getting started + +Given the following struct, let's see how to read it and write it as TOML: + +```go +type MyConfig struct { + Version int + Name string + Tags []string +} +``` + +### Unmarshaling + +[`Unmarshal`][unmarshal] reads a TOML document and fills a Go structure with its +content. For example: + +```go +doc := ` +version = 2 +name = "go-toml" +tags = ["go", "toml"] +` + +var cfg MyConfig +err := toml.Unmarshal([]byte(doc), &cfg) +if err != nil { + panic(err) +} +fmt.Println("version:", cfg.Version) +fmt.Println("name:", cfg.Name) +fmt.Println("tags:", cfg.Tags) + +// Output: +// version: 2 +// name: go-toml +// tags: [go toml] +``` + +[unmarshal]: https://pkg.go.dev/github.com/pelletier/go-toml/v2#Unmarshal + +### Marshaling + +[`Marshal`][marshal] is the opposite of Unmarshal: it represents a Go structure +as a TOML document: + +```go +cfg := MyConfig{ + Version: 2, + Name: "go-toml", + Tags: []string{"go", "toml"}, +} + +b, err := toml.Marshal(cfg) +if err != nil { + panic(err) +} +fmt.Println(string(b)) + +// Output: +// Version = 2 +// Name = 'go-toml' +// Tags = ['go', 'toml'] +``` + +[marshal]: https://pkg.go.dev/github.com/pelletier/go-toml/v2#Marshal + +## Migrating from v1 + +This section describes the differences between v1 and v2, with some pointers on +how to get the original behavior when possible. + +### Decoding / Unmarshal + +#### Automatic field name guessing + +When unmarshaling to a struct, if a key in the TOML document does not exactly +match the name of a struct field or any of the `toml`-tagged field, v1 tries +multiple variations of the key ([code][v1-keys]). + +V2 instead does a case-insensitive matching, like `encoding/json`. + +This could impact you if you are relying on casing to differentiate two fields, +and one of them is a not using the `toml` struct tag. The recommended solution +is to be specific about tag names for those fields using the `toml` struct tag. + +[v1-keys]: https://github.com/pelletier/go-toml/blob/a2e52561804c6cd9392ebf0048ca64fe4af67a43/marshal.go#L775-L781 + +#### Ignore preexisting value in interface + +When decoding into a non-nil `interface{}`, go-toml v1 uses the type of the +element in the interface to decode the object. For example: + +```go +type inner struct { + B interface{} +} +type doc struct { + A interface{} +} + +d := doc{ + A: inner{ + B: "Before", + }, +} + +data := ` +[A] +B = "After" +` + +toml.Unmarshal([]byte(data), &d) +fmt.Printf("toml v1: %#v\n", d) + +// toml v1: main.doc{A:main.inner{B:"After"}} +``` + +In this case, field `A` is of type `interface{}`, containing a `inner` struct. +V1 sees that type and uses it when decoding the object. + +When decoding an object into an `interface{}`, V2 instead disregards whatever +value the `interface{}` may contain and replaces it with a +`map[string]interface{}`. With the same data structure as above, here is what +the result looks like: + +```go +toml.Unmarshal([]byte(data), &d) +fmt.Printf("toml v2: %#v\n", d) + +// toml v2: main.doc{A:map[string]interface {}{"B":"After"}} +``` + +This is to match `encoding/json`'s behavior. There is no way to make the v2 +decoder behave like v1. + +#### Values out of array bounds ignored + +When decoding into an array, v1 returns an error when the number of elements +contained in the doc is superior to the capacity of the array. For example: + +```go +type doc struct { + A [2]string +} +d := doc{} +err := toml.Unmarshal([]byte(`A = ["one", "two", "many"]`), &d) +fmt.Println(err) + +// (1, 1): unmarshal: TOML array length (3) exceeds destination array length (2) +``` + +In the same situation, v2 ignores the last value: + +```go +err := toml.Unmarshal([]byte(`A = ["one", "two", "many"]`), &d) +fmt.Println("err:", err, "d:", d) +// err: d: {[one two]} +``` + +This is to match `encoding/json`'s behavior. There is no way to make the v2 +decoder behave like v1. + +#### Support for `toml.Unmarshaler` has been dropped + +This method was not widely used, poorly defined, and added a lot of complexity. +A similar effect can be achieved by implementing the `encoding.TextUnmarshaler` +interface and use strings. + +### Encoding / Marshal + +#### Default struct fields order + +V1 emits struct fields order alphabetically by default. V2 struct fields are +emitted in order they are defined. For example: + +```go +type S struct { + B string + A string +} + +data := S{ + B: "B", + A: "A", +} + +b, _ := tomlv1.Marshal(data) +fmt.Println("v1:\n" + string(b)) + +b, _ = tomlv2.Marshal(data) +fmt.Println("v2:\n" + string(b)) + +// Output: +// v1: +// A = "A" +// B = "B" + +// v2: +// B = 'B' +// A = 'A' +``` + +There is no way to make v2 encoder behave like v1. A workaround could be to +manually sort the fields alphabetically in the struct definition. + +#### No indentation by default + +V1 automatically indents content of tables by default. V2 does not. However the +same behavior can be obtained using [`Encoder.SetIndentTables`][sit]. For example: + + +```go +data := map[string]interface{}{ + "table": map[string]string{ + "key": "value", + }, +} + +b, _ := tomlv1.Marshal(data) +fmt.Println("v1:\n" + string(b)) + +b, _ = tomlv2.Marshal(data) +fmt.Println("v2:\n" + string(b)) + +buf := bytes.Buffer{} +enc := tomlv2.NewEncoder(&buf) +enc.SetIndentTables(true) +enc.Encode(data) +fmt.Println("v2 Encoder:\n" + string(buf.Bytes())) + +// Output: +// v1: +// +// [table] +// key = "value" +// +// v2: +// [table] +// key = 'value' +// +// +// v2 Encoder: +// [table] +// key = 'value' +``` + +[sit]: https://pkg.go.dev/github.com/pelletier/go-toml/v2#Encoder.SetIndentTables + +#### Keys and strings are single quoted + +V1 always uses double quotes (`"`) around strings and keys that cannot be +represented bare (unquoted). V2 uses single quotes instead by default (`'`), +unless a character cannot be represented, then falls back to double quotes. + +There is no way to make v2 encoder behave like v1. + +#### `TextMarshaler` emits as a string, not TOML + +Types that implement [`encoding.TextMarshaler`][tm] can emit arbitrary TOML in +v1. The encoder would append the result to the output directly. In v2 the result +is wrapped in a string. As a result, this interface cannot be implemented by the +root object. + +There is no way to make v2 encoder behave like v1. + +[tm]: https://golang.org/pkg/encoding/#TextMarshaler ## License diff --git a/doc.go b/doc.go new file mode 100644 index 00000000..d541e4b9 --- /dev/null +++ b/doc.go @@ -0,0 +1,4 @@ +/* + Package toml is a library to read and write TOML documents. +*/ +package toml diff --git a/errors.go b/errors.go index 82c21ef4..44865057 100644 --- a/errors.go +++ b/errors.go @@ -90,12 +90,13 @@ func (e *DecodeError) Position() (row int, column int) { return e.line, e.column } -// Key that was being processed when the error occurred. +// Key that was being processed when the error occurred. The key is present only +// if this DecodeError is part of a StrictMissingError. func (e *DecodeError) Key() Key { return e.key } -// decodeErrorFromHighlight creates a DecodeError referencing to a highlighted +// decodeErrorFromHighlight creates a DecodeError referencing a highlighted // range of bytes from document. // // highlight needs to be a sub-slice of document, or this function panics. diff --git a/errors_test.go b/errors_test.go index efbb59a3..893dc9c9 100644 --- a/errors_test.go +++ b/errors_test.go @@ -3,6 +3,7 @@ package toml import ( "bytes" "errors" + "fmt" "strings" "testing" @@ -179,3 +180,23 @@ line 5`, }) } } + +func ExampleDecodeError() { + doc := `name = 123__456` + + s := map[string]interface{}{} + err := Unmarshal([]byte(doc), &s) + + fmt.Println(err) + + de := err.(*DecodeError) + fmt.Println(de.String()) + + row, col := de.Position() + fmt.Println("error occured at row", row, "column", col) + // Output: + // toml: number must have at least one digit between underscores + // 1| name = 123__456 + // | ~~ number must have at least one digit between underscores + // error occured at row 1 column 11 +} diff --git a/marshaler.go b/marshaler.go index aa8783ac..713d8af0 100644 --- a/marshaler.go +++ b/marshaler.go @@ -50,7 +50,9 @@ func NewEncoder(w io.Writer) *Encoder { // SetTablesInline forces the encoder to emit all tables inline. // // This behavior can be controlled on an individual struct field basis with the -// `inline="true"` tag. +// inline tag: +// +// MyField `inline:"true"` func (enc *Encoder) SetTablesInline(inline bool) { enc.tablesInline = inline } @@ -58,8 +60,9 @@ func (enc *Encoder) SetTablesInline(inline bool) { // SetArraysMultiline forces the encoder to emit all arrays with one element per // line. // -// This behavior can be controlled on an individual struct field basis with the -// `multiline="true"` tag. +// This behavior can be controlled on an individual struct field basis with the multiline tag: +// +// MyField `multiline:"true"` func (enc *Encoder) SetArraysMultiline(multiline bool) { enc.arraysMultiline = multiline } @@ -80,31 +83,42 @@ func (enc *Encoder) SetIndentTables(indent bool) { // // If v cannot be represented to TOML it returns an error. // -// Encoding rules: +// Encoding rules // -// 1. A top level slice containing only maps or structs is encoded as [[table +// A top level slice containing only maps or structs is encoded as [[table // array]]. // -// 2. All slices not matching rule 1 are encoded as [array]. As a result, any -// map or struct they contain is encoded as an {inline table}. +// All slices not matching rule 1 are encoded as [array]. As a result, any map +// or struct they contain is encoded as an {inline table}. // -// 3. Nil interfaces and nil pointers are not supported. +// Nil interfaces and nil pointers are not supported. // -// 4. Keys in key-values always have one part. +// Keys in key-values always have one part. // -// 5. Intermediate tables are always printed. +// Intermediate tables are always printed. // // By default, strings are encoded as literal string, unless they contain either // a newline character or a single quote. In that case they are emitted as quoted // strings. // // When encoding structs, fields are encoded in order of definition, with their -// exact name. The following struct tags are available: +// exact name. +// +// Struct tags +// +// The following struct tags are available to tweak encoding on a per-field +// basis: +// +// toml:"foo" +// Changes the name of the key to use for the field to foo. // -// `toml:"foo"`: changes the name of the key to use for the field to foo. +// multiline:"true" +// When the field contains a string, it will be emitted as a quoted +// multi-line TOML string. // -// `multiline:"true"`: when the field contains a string, it will be emitted as -// a quoted multi-line TOML string. +// inline:"true" +// When the field would normally be encoded as a table, it is instead +// encoded as an inline table. func (enc *Encoder) Encode(v interface{}) error { var ( b []byte diff --git a/marshaler_test.go b/marshaler_test.go index 470bead0..d7194068 100644 --- a/marshaler_test.go +++ b/marshaler_test.go @@ -511,3 +511,28 @@ func TestIssue424(t *testing.T) { require.NoError(t, err) require.Equal(t, msg2, msg2parsed) } + +func ExampleMarshal() { + type MyConfig struct { + Version int + Name string + Tags []string + } + + cfg := MyConfig{ + Version: 2, + Name: "go-toml", + Tags: []string{"go", "toml"}, + } + + b, err := toml.Marshal(cfg) + if err != nil { + panic(err) + } + fmt.Println(string(b)) + + // Output: + // Version = 2 + // Name = 'go-toml' + // Tags = ['go', 'toml'] +} diff --git a/unmarshaler.go b/unmarshaler.go index 3864be65..3d25a862 100644 --- a/unmarshaler.go +++ b/unmarshaler.go @@ -14,6 +14,9 @@ import ( "github.com/pelletier/go-toml/v2/internal/unsafe" ) +// Unmarshal deserializes a TOML document into a Go value. +// +// It is a shortcut for Decoder.Decode() with the default options. func Unmarshal(data []byte, v interface{}) error { p := parser{} p.Reset(data) @@ -48,11 +51,39 @@ func (d *Decoder) SetStrict(strict bool) { // Decode the whole content of r into v. // -// When a TOML local date is decoded into a time.Time, its value is represented -// in time.Local timezone. +// By default, values in the document that don't exist in the target Go value +// are ignored. See Decoder.SetStrict() to change this behavior. +// +// When a TOML local date, time, or date-time is decoded into a time.Time, its +// value is represented in time.Local timezone. Otherwise the approriate Local* +// structure is used. // // Empty tables decoded in an interface{} create an empty initialized // map[string]interface{}. +// +// Types implementing the encoding.TextUnmarshaler interface are decoded from a +// TOML string. +// +// When decoding a number, go-toml will return an error if the number is out of +// bounds for the target type (which includes negative numbers when decoding +// into an unsigned int). +// +// Type mapping +// +// List of supported TOML types and their associated accepted Go types: +// +// String -> string +// Integer -> uint*, int*, depending on size +// Float -> float*, depending on size +// Boolean -> bool +// Offset Date-Time -> time.Time +// Local Date-time -> LocalDateTime, time.Time +// Local Date -> LocalDate, time.Time +// Local Time -> LocalTime, time.Time +// Array -> slice and array, depending on elements types +// Table -> map and struct +// Inline Table -> same as Table +// Array of Tables -> same as Array and Table func (d *Decoder) Decode(v interface{}) error { b, err := ioutil.ReadAll(d.r) if err != nil { diff --git a/unmarshaler_test.go b/unmarshaler_test.go index 9814cd18..62357270 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -1321,3 +1321,31 @@ key3 = "value3" // | ~~~~ missing field // 4| key3 = "value3" } + +func ExampleUnmarshal() { + type MyConfig struct { + Version int + Name string + Tags []string + } + + doc := ` + version = 2 + name = "go-toml" + tags = ["go", "toml"] + ` + + var cfg MyConfig + err := toml.Unmarshal([]byte(doc), &cfg) + if err != nil { + panic(err) + } + fmt.Println("version:", cfg.Version) + fmt.Println("name:", cfg.Name) + fmt.Println("tags:", cfg.Tags) + + // Output: + // version: 2 + // name: go-toml + // tags: [go toml] +} From 3db329a5123a23cff929f3a275710b216ce5887f Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Sun, 9 May 2021 17:37:03 -0400 Subject: [PATCH 194/228] ci: basic github action for coverage (#537) --- .github/PULL_REQUEST_TEMPLATE.md | 18 +++- .github/workflows/coverage.yml | 23 +++++ CONTRIBUTING.md | 164 ++++++++++++++++++++----------- ci.sh | 107 ++++++++++++++++++++ unmarshaler_test.go | 9 +- 5 files changed, 256 insertions(+), 65 deletions(-) create mode 100644 .github/workflows/coverage.yml create mode 100755 ci.sh diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 041cdc4a..1e54e074 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,5 +1,19 @@ -**Issue:** add link to pelletier/go-toml issue here + Explanation of what this pull request does. -More detailed description of the decisions being made and the reasons why (if the patch is non-trivial). +More detailed description of the decisions being made and the reasons why (if +the patch is non-trivial). + +--- + +Paste `benchstat` results here diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml new file mode 100644 index 00000000..0a5e46d5 --- /dev/null +++ b/.github/workflows/coverage.yml @@ -0,0 +1,23 @@ +name: coverage +on: + push: + branches: + - v2 + pull_request: + branches: + - v2 + +jobs: + report: + runs-on: 'ubuntu-latest' + name: report + steps: + - uses: actions/checkout@master + with: + fetch-depth: 0 + - name: Setup go + uses: actions/setup-go@master + with: + go-version: 1.16 + - name: Run tests with coverage + run: ./ci.sh coverage -d "${GITHUB_BASE_REF-HEAD}" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 98b9893d..e62df92c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,74 +1,74 @@ -## Contributing +# Contributing Thank you for your interest in go-toml! We appreciate you considering contributing to go-toml! -The main goal is the project is to provide an easy-to-use TOML -implementation for Go that gets the job done and gets out of your way – -dealing with TOML is probably not the central piece of your project. +The main goal is the project is to provide an easy-to-use and efficient TOML +implementation for Go that gets the job done and gets out of your way – dealing +with TOML is probably not the central piece of your project. -As the single maintainer of go-toml, time is scarce. All help, big or -small, is more than welcomed! +As the single maintainer of go-toml, time is scarce. All help, big or small, is +more than welcomed! -### Ask questions +## Ask questions -Any question you may have, somebody else might have it too. Always feel -free to ask them on the [issues tracker][issues-tracker]. We will try to -answer them as clearly and quickly as possible, time permitting. +Any question you may have, somebody else might have it too. Always feel free to +ask them on the [discussion board][discussions]. We will try to answer them as +clearly and quickly as possible, time permitting. Asking questions also helps us identify areas where the documentation needs improvement, or new features that weren't envisioned before. Sometimes, a -seemingly innocent question leads to the fix of a bug. Don't hesitate and -ask away! +seemingly innocent question leads to the fix of a bug. Don't hesitate and ask +away! -### Improve the documentation +[discussions]: https://github.com/pelletier/go-toml/discussions -The best way to share your knowledge and experience with go-toml is to -improve the documentation. Fix a typo, clarify an interface, add an -example, anything goes! +## Improve the documentation -The documentation is present in the [README][readme] and thorough the -source code. On release, it gets updated on [pkg.go.dev][pkg.go.dev]. To make a -change to the documentation, create a pull request with your proposed -changes. For simple changes like that, the easiest way to go is probably -the "Fork this project and edit the file" button on Github, displayed at -the top right of the file. Unless it's a trivial change (for example a -typo), provide a little bit of context in your pull request description or -commit message. +The best way to share your knowledge and experience with go-toml is to improve +the documentation. Fix a typo, clarify an interface, add an example, anything +goes! -### Report a bug +The documentation is present in the [README][readme] and thorough the source +code. On release, it gets updated on [pkg.go.dev][pkg.go.dev]. To make a change +to the documentation, create a pull request with your proposed changes. For +simple changes like that, the easiest way to go is probably the "Fork this +project and edit the file" button on Github, displayed at the top right of the +file. Unless it's a trivial change (for example a typo), provide a little bit of +context in your pull request description or commit message. -Found a bug! Sorry to hear that :(. Help us and other track them down and -fix by reporting it. [File a new bug report][bug-report] on the [issues -tracker][issues-tracker]. The template should provide enough guidance on -what to include. When in doubt: add more details! By reducing ambiguity and -providing more information, it decreases back and forth and saves everyone -time. +## Report a bug -### Code changes +Found a bug! Sorry to hear that :(. Help us and other track them down and fix by +reporting it. [File a new bug report][bug-report] on the [issues +tracker][issues-tracker]. The template should provide enough guidance on what to +include. When in doubt: add more details! By reducing ambiguity and providing +more information, it decreases back and forth and saves everyone time. + +## Code changes Want to contribute a patch? Very happy to hear that! First, some high-level rules: -* A short proposal with some POC code is better than a lengthy piece of - text with no code. Code speaks louder than words. -* No backward-incompatible patch will be accepted unless discussed. - Sometimes it's hard, and Go's lack of versioning by default does not - help, but we try not to break people's programs unless we absolutely have +* A short proposal with some POC code is better than a lengthy piece of text + with no code. Code speaks louder than words. That being said, bigger changes + should probably start with a [discussion][discussions]. +* No backward-incompatible patch will be accepted unless discussed. Sometimes + it's hard, but we try not to break people's programs unless we absolutely have to. -* If you are writing a new feature or extending an existing one, make sure - to write some documentation. +* If you are writing a new feature or extending an existing one, make sure to + write some documentation. * Bug fixes need to be accompanied with regression tests. * New code needs to be tested. -* Your commit messages need to explain why the change is needed, even if - already included in the PR description. +* Your commit messages need to explain why the change is needed, even if already + included in the PR description. -It does sound like a lot, but those best practices are here to save time -overall and continuously improve the quality of the project, which is -something everyone benefits from. +It does sound like a lot, but those best practices are here to save time overall +and continuously improve the quality of the project, which is something everyone +benefits from. -#### Get started +### Get started The fairly standard code contribution process looks like that: @@ -76,42 +76,90 @@ The fairly standard code contribution process looks like that: 2. Make your changes, commit on any branch you like. 3. [Open up a pull request][pull-request] 4. Review, potential ask for changes. -5. Merge. You're in! +5. Merge. Feel free to ask for help! You can create draft pull requests to gather some early feedback! -#### Run the tests +### Run the tests + +You can run tests for go-toml using Go's test tool: `go test -race ./...`. + +During the pull request process, all tests will be ran on Linux, Windows, and +MacOS on the last two versions of Go. + +However, given GitHub's new policy to _not_ run Actions on pull requests until a +maintainer clicks on button, it is highly recommended that you run them locally +as you make changes. + +### Check coverage + +We use `go tool cover` to compute test coverage. Most code editors have a way to +run and display code coverage, but at the end of the day, we do this: + +``` +go test -covermode=atomic -coverprofile=coverage.out +go tool cover -func=coverage.out +``` + +and verify that the overall percentage of tested code does not go down. This is +a requirement. As a rule of thumb, all lines of code touched by your changes +should be covered. On Unix you can use `./ci.sh coverage -d v2` to check if your +code lowers the coverage. + +### Verify performance + +Go-toml aims to stay efficient. We rely on a set of scenarios executed with Go's +builtin benchmark systems. Because of their noisy nature, containers provided by +Github Actions cannot be reliably used for benchmarking. As a result, you are +responsible for checking that your changes do not incur a performance penalty. +You can run their following to execute benchmarks: + +``` +go test ./... -bench=. -count=10 +``` + +Benchmark results should be compared against each other with +[benchstat][benchstat]. Typical flow looks like this: + +1. On the `v2` branch, run `go test ./... -bench=. -count 10` and save output to + a file (for example `old.txt`). +2. Make some code changes. +3. Run `go test ....` again, and save the output to an other file (for example + `new.txt`). +4. Run `benchstat old.txt new.txt` to check that time/op does not go up in any + test. + +It is highly encouraged to add the benchstat results to your pull request +description. Pull requests that lower performance will receive more scrutiny. + +[benchstat]: https://pkg.go.dev/golang.org/x/perf/cmd/benchstat -You can run tests for go-toml using Go's test tool: `go test ./...`. -When creating a pull requests, all tests will be ran on Linux on a few Go -versions (Travis CI), and on Windows using the latest Go version -(AppVeyor). -#### Style +### Style -Try to look around and follow the same format and structure as the rest of -the code. We enforce using `go fmt` on the whole code base. +Try to look around and follow the same format and structure as the rest of the +code. We enforce using `go fmt` on the whole code base. --- -### Maintainers-only +## Maintainers-only -#### Merge pull request +### Merge pull request Checklist: * Passing CI. * Does not introduce backward-incompatible changes (unless discussed). * Has relevant doc changes. -* Has relevant unit tests. +* Benchstat does not show performance regression. 1. Merge using "squash and merge". 2. Make sure to edit the commit message to keep all the useful information nice and clean. 3. Make sure the commit title is clear and contains the PR number (#123). -#### New release +### New release 1. Go to [releases][releases]. Click on "X commits to master since this release". diff --git a/ci.sh b/ci.sh new file mode 100755 index 00000000..0e3314c1 --- /dev/null +++ b/ci.sh @@ -0,0 +1,107 @@ +#!/usr/bin/env bash + + +stderr() { + echo "$@" 1>&2 +} + +usage() { + b=$(basename "$0") + echo $b: ERROR: "$@" 1>&2 + + cat 1>&2 < "${target_out}" + cover "HEAD" > "${head_out}" + + cat "${target_out}" + cat "${head_out}" + + echo "" + + target_pct="$(cat ${target_out} |sed -E 's/.*total.*\t([0-9.]+)%/\1/;t;d')" + head_pct="$(cat ${head_out} |sed -E 's/.*total.*\t([0-9.]+)%/\1/;t;d')" + echo "Results: ${target} ${target_pct}% HEAD ${head_pct}%" + + delta_pct=$(echo "$head_pct - $target_pct" | bc -l) + echo "Delta: ${delta_pct}" + + if [[ $delta_pct = \-* ]]; then + echo "Regression!"; + return 1 + fi + return 0 + ;; + esac + + cover "${1-HEAD}" +} + +case "$1" in + coverage) shift; coverage $@;; + *) usage "bad argument $1";; +esac diff --git a/unmarshaler_test.go b/unmarshaler_test.go index 62357270..338c0f50 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -838,15 +838,14 @@ B = "data"`, }, { desc: "mismatch types array of int to interface with non-slice", - input: `A = [[42]]`, - skip: true, + input: `A = [42]`, gen: func() test { type S struct { - A *string + A string } return test{ - target: &S{}, - expected: &S{}, + target: &S{}, + err: true, } }, }, From 95c701b25360da1ce8cf2bb82d3387d130ca0cfb Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Mon, 10 May 2021 20:17:05 -0400 Subject: [PATCH 195/228] Increase test coverage (#538) Also fix array in map bug. --- .golangci.toml | 8 +- CONTRIBUTING.md | 26 ++- ci.sh | 61 ++++- decode.go | 87 ++----- doc.go | 4 +- errors.go | 6 +- errors_test.go | 23 +- marshaler.go | 90 ++----- marshaler_test.go | 292 +++++++++++++++++++++++ parser.go | 59 ++--- scanner.go | 6 +- targets.go | 14 +- unmarshaler.go | 24 +- unmarshaler_test.go | 557 ++++++++++++++++++++++++++++++++++++++++++-- 14 files changed, 1028 insertions(+), 229 deletions(-) diff --git a/.golangci.toml b/.golangci.toml index 0e71b204..fdf167b4 100644 --- a/.golangci.toml +++ b/.golangci.toml @@ -24,7 +24,7 @@ enable = [ # "exhaustivestruct", "exportloopref", "forbidigo", - "forcetypeassert", + # "forcetypeassert", "funlen", "gci", # "gochecknoglobals", @@ -35,7 +35,7 @@ enable = [ "gocyclo", "godot", "godox", - "goerr113", + # "goerr113", "gofmt", "gofumpt", "goheader", @@ -57,7 +57,7 @@ enable = [ "nakedret", "nestif", "nilerr", - "nlreturn", + # "nlreturn", "noctx", "nolintlint", "paralleltest", @@ -80,5 +80,5 @@ enable = [ "wastedassign", "whitespace", # "wrapcheck", - "wsl" + # "wsl" ] diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e62df92c..59658d39 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -51,17 +51,17 @@ Want to contribute a patch? Very happy to hear that! First, some high-level rules: -* A short proposal with some POC code is better than a lengthy piece of text +- A short proposal with some POC code is better than a lengthy piece of text with no code. Code speaks louder than words. That being said, bigger changes should probably start with a [discussion][discussions]. -* No backward-incompatible patch will be accepted unless discussed. Sometimes +- No backward-incompatible patch will be accepted unless discussed. Sometimes it's hard, but we try not to break people's programs unless we absolutely have to. -* If you are writing a new feature or extending an existing one, make sure to +- If you are writing a new feature or extending an existing one, make sure to write some documentation. -* Bug fixes need to be accompanied with regression tests. -* New code needs to be tested. -* Your commit messages need to explain why the change is needed, even if already +- Bug fixes need to be accompanied with regression tests. +- New code needs to be tested. +- Your commit messages need to explain why the change is needed, even if already included in the PR description. It does sound like a lot, but those best practices are here to save time overall @@ -129,13 +129,15 @@ Benchmark results should be compared against each other with `new.txt`). 4. Run `benchstat old.txt new.txt` to check that time/op does not go up in any test. - + +On Unix you can use `./ci.sh benchmark -d v2` to verify how your code impacts +performance. + It is highly encouraged to add the benchstat results to your pull request description. Pull requests that lower performance will receive more scrutiny. [benchstat]: https://pkg.go.dev/golang.org/x/perf/cmd/benchstat - ### Style Try to look around and follow the same format and structure as the rest of the @@ -149,10 +151,10 @@ code. We enforce using `go fmt` on the whole code base. Checklist: -* Passing CI. -* Does not introduce backward-incompatible changes (unless discussed). -* Has relevant doc changes. -* Benchstat does not show performance regression. +- Passing CI. +- Does not introduce backward-incompatible changes (unless discussed). +- Has relevant doc changes. +- Benchstat does not show performance regression. 1. Merge using "squash and merge". 2. Make sure to edit the commit message to keep all the useful information diff --git a/ci.sh b/ci.sh index 0e3314c1..75d7008b 100755 --- a/ci.sh +++ b/ci.sh @@ -25,6 +25,20 @@ USAGE COMMANDS +benchmark [OPTIONS...] [BRANCH] + + Run benchmarks. + + ARGUMENTS + + BRANCH Optional. Defines which Git branch to use when running + benchmarks. + + OPTIONS + + -d Compare benchmarks of HEAD with BRANCH using benchstats. In + this form the BRANCH argument is required. + coverage [OPTIONS...] [BRANCH] Generates code coverage. @@ -50,9 +64,9 @@ cover() { stderr "Executing coverage for ${branch} at ${dir}" if [ "${branch}" = "HEAD" ]; then - cp -r . "${dir}/" + cp -r . "${dir}/" else - git worktree add "$dir" "$branch" + git worktree add "$dir" "$branch" fi pushd "$dir" @@ -61,7 +75,7 @@ cover() { popd if [ "${branch}" != "HEAD" ]; then - git worktree remove --force "$dir" + git worktree remove --force "$dir" fi } @@ -101,7 +115,48 @@ coverage() { cover "${1-HEAD}" } +bench() { + branch="${1}" + out="${2}" + dir="$(mktemp -d)" + + stderr "Executing benchmark for ${branch} at ${dir}" + + if [ "${branch}" = "HEAD" ]; then + cp -r . "${dir}/" + else + git worktree add "$dir" "$branch" + fi + + pushd "$dir" + go test -bench=. -count=10 ./... | tee "${out}" + popd + + if [ "${branch}" != "HEAD" ]; then + git worktree remove --force "$dir" + fi +} + +benchmark() { + case "$1" in + -d) + shift + target="${1?Need to provide a target branch argument}" + old=`mktemp` + bench "${target}" "${old}" + + new=`mktemp` + bench HEAD "${new}" + benchstat "${old}" "${new}" + return 0 + ;; + esac + + bench "${1-HEAD}" `mktemp` +} + case "$1" in coverage) shift; coverage $@;; + benchmark) shift; benchmark $@;; *) usage "bad argument $1";; esac diff --git a/decode.go b/decode.go index afa6db35..33ac2a96 100644 --- a/decode.go +++ b/decode.go @@ -1,6 +1,7 @@ package toml import ( + "fmt" "math" "strconv" "time" @@ -16,7 +17,7 @@ func parseInteger(b []byte) (int64, error) { case 'o': return parseIntOct(b) default: - return 0, newDecodeError(b[1:2], "invalid base: '%c'", b[1]) + panic(fmt.Errorf("invalid base '%c', should have been checked by scanIntOrFloat", b[1])) } } @@ -34,41 +35,26 @@ func parseLocalDate(b []byte) (LocalDate, error) { return date, newDecodeError(b, "dates are expected to have the format YYYY-MM-DD") } - var err error + date.Year = parseDecimalDigits(b[0:4]) - date.Year, err = parseDecimalDigits(b[0:4]) - if err != nil { - return date, err - } - - v, err := parseDecimalDigits(b[5:7]) - if err != nil { - return date, err - } + v := parseDecimalDigits(b[5:7]) date.Month = time.Month(v) - date.Day, err = parseDecimalDigits(b[8:10]) - if err != nil { - return date, err - } + date.Day = parseDecimalDigits(b[8:10]) return date, nil } -func parseDecimalDigits(b []byte) (int, error) { +func parseDecimalDigits(b []byte) int { v := 0 - for i, c := range b { - if !isDigit(c) { - return 0, newDecodeError(b[i:i+1], "should be a digit (0-9)") - } - + for _, c := range b { v *= 10 v += int(c - '0') } - return v, nil + return v } func parseDateTime(b []byte) (time.Time, error) { @@ -77,8 +63,6 @@ func parseDateTime(b []byte) (time.Time, error) { // time-offset = "Z" / time-numoffset // time-numoffset = ( "+" / "-" ) time-hour ":" time-minute - originalBytes := b - dt, b, err := parseLocalDateTime(b) if err != nil { return time.Time{}, err @@ -87,7 +71,8 @@ func parseDateTime(b []byte) (time.Time, error) { var zone *time.Location if len(b) == 0 { - return time.Time{}, newDecodeError(originalBytes, "date-time is missing timezone") + // parser should have checked that when assigning the date time node + panic("date time should have a timezone") } if b[0] == 'Z' { @@ -99,18 +84,15 @@ func parseDateTime(b []byte) (time.Time, error) { return time.Time{}, newDecodeError(b, "invalid date-time timezone") } direction := 1 - switch b[0] { - case '+': - case '-': + if b[0] == '-' { direction = -1 - default: - return time.Time{}, newDecodeError(b[0:1], "invalid timezone offset character") } hours := digitsToInt(b[1:3]) minutes := digitsToInt(b[4:6]) seconds := direction * (hours*3600 + minutes*60) zone = time.FixedZone("", seconds) + b = b[dateTimeByteLen:] } if len(b) > 0 { @@ -161,7 +143,6 @@ func parseLocalDateTime(b []byte) (LocalDateTime, []byte, error) { // parseLocalTime is a bit different because it also returns the remaining // []byte that is didn't need. This is to allow parseDateTime to parse those // remaining bytes as a timezone. -//nolint:cyclop,funlen func parseLocalTime(b []byte) (LocalTime, []byte, error) { var ( nspow = [10]int{0, 1e8, 1e7, 1e6, 1e5, 1e4, 1e3, 1e2, 1e1, 1e0} @@ -173,46 +154,26 @@ func parseLocalTime(b []byte) (LocalTime, []byte, error) { return t, nil, newDecodeError(b, "times are expected to have the format HH:MM:SS[.NNNNNN]") } - var err error - - t.Hour, err = parseDecimalDigits(b[0:2]) - if err != nil { - return t, nil, err - } - + t.Hour = parseDecimalDigits(b[0:2]) if b[2] != ':' { return t, nil, newDecodeError(b[2:3], "expecting colon between hours and minutes") } - t.Minute, err = parseDecimalDigits(b[3:5]) - if err != nil { - return t, nil, err - } - + t.Minute = parseDecimalDigits(b[3:5]) if b[5] != ':' { return t, nil, newDecodeError(b[5:6], "expecting colon between minutes and seconds") } - t.Second, err = parseDecimalDigits(b[6:8]) - if err != nil { - return t, nil, err - } + t.Second = parseDecimalDigits(b[6:8]) - if len(b) >= 9 && b[8] == '.' { + const minLengthWithFrac = 9 + if len(b) >= minLengthWithFrac && b[minLengthWithFrac-1] == '.' { frac := 0 digits := 0 - for i, c := range b[9:] { - if !isDigit(c) { - if i == 0 { - return t, nil, newDecodeError(b[i:i+1], "need at least one digit after fraction point") - } - - break - } - - //nolint:gomnd - if i >= 9 { + for i, c := range b[minLengthWithFrac:] { + const maxFracPrecision = 9 + if i >= maxFracPrecision { return t, nil, newDecodeError(b[i:i+1], "maximum precision for date time is nanosecond") } @@ -231,8 +192,6 @@ func parseLocalTime(b []byte) (LocalTime, []byte, error) { //nolint:cyclop func parseFloat(b []byte) (float64, error) { - //nolint:godox - // TODO: inefficient if len(b) == 4 && (b[0] == '+' || b[0] == '-') && b[1] == 'n' && b[2] == 'a' && b[3] == 'n' { return math.NaN(), nil } @@ -252,7 +211,7 @@ func parseFloat(b []byte) (float64, error) { f, err := strconv.ParseFloat(string(cleaned), 64) if err != nil { - return 0, newDecodeError(b, "coudn't parse float: %w", err) + return 0, newDecodeError(b, "unable to parse float: %w", err) } return f, nil @@ -315,10 +274,6 @@ func parseIntDec(b []byte) (int64, error) { } func checkAndRemoveUnderscores(b []byte) ([]byte, error) { - if len(b) == 0 { - return b, nil - } - if b[0] == '_' { return nil, newDecodeError(b[0:1], "number cannot start with underscore") } diff --git a/doc.go b/doc.go index d541e4b9..b7bc599b 100644 --- a/doc.go +++ b/doc.go @@ -1,4 +1,2 @@ -/* - Package toml is a library to read and write TOML documents. -*/ +// Package toml is a library to read and write TOML documents. package toml diff --git a/errors.go b/errors.go index 44865057..712765bb 100644 --- a/errors.go +++ b/errors.go @@ -105,13 +105,9 @@ func (e *DecodeError) Key() Key { // highlight can be freely deallocated. //nolint:funlen func wrapDecodeError(document []byte, de *decodeError) *DecodeError { - if de == nil { - return nil - } - offset := unsafe.SubsliceOffset(document, de.highlight) - errMessage := de.message + errMessage := de.Error() errLine, errColumn := positionAtEnd(document[:offset]) before, after := linesOfContext(document, de.highlight, offset, 3) diff --git a/errors_test.go b/errors_test.go index 893dc9c9..d6af314e 100644 --- a/errors_test.go +++ b/errors_test.go @@ -181,6 +181,24 @@ line 5`, } } +func TestDecodeError_Accessors(t *testing.T) { + t.Parallel() + + e := DecodeError{ + message: "foo", + line: 1, + column: 2, + key: []string{"one", "two"}, + human: "bar", + } + assert.Equal(t, "toml: foo", e.Error()) + r, c := e.Position() + assert.Equal(t, 1, r) + assert.Equal(t, 2, c) + assert.Equal(t, Key{"one", "two"}, e.Key()) + assert.Equal(t, "bar", e.String()) +} + func ExampleDecodeError() { doc := `name = 123__456` @@ -189,14 +207,15 @@ func ExampleDecodeError() { fmt.Println(err) + //nolint:errorlint de := err.(*DecodeError) fmt.Println(de.String()) row, col := de.Position() - fmt.Println("error occured at row", row, "column", col) + fmt.Println("error occurred at row", row, "column", col) // Output: // toml: number must have at least one digit between underscores // 1| name = 123__456 // | ~~ number must have at least one digit between underscores - // error occured at row 1 column 11 + // error occurred at row 1 column 11 } diff --git a/marshaler.go b/marshaler.go index 713d8af0..ce9972aa 100644 --- a/marshaler.go +++ b/marshaler.go @@ -127,6 +127,10 @@ func (enc *Encoder) Encode(v interface{}) error { ctx.inline = enc.tablesInline + if v == nil { + return fmt.Errorf("toml: cannot encode a nil interface") + } + b, err := enc.encode(b, ctx, reflect.ValueOf(v)) if err != nil { return err @@ -193,9 +197,11 @@ func (ctx *encoderCtx) isRoot() bool { //nolint:cyclop,funlen func (enc *Encoder) encode(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) { - i, ok := v.Interface().(time.Time) - if ok { - return i.AppendFormat(b, time.RFC3339), nil + if !v.IsZero() { + i, ok := v.Interface().(time.Time) + if ok { + return i.AppendFormat(b, time.RFC3339), nil + } } if v.Type().Implements(textMarshalerType) { @@ -273,11 +279,6 @@ func (enc *Encoder) encodeKv(b []byte, ctx encoderCtx, options valueOptions, v r if !ctx.hasKey { panic("caller of encodeKv should have set the key in the context") } - - if isNil(v) { - return b, nil - } - b = enc.indent(ctx.indent, b) b, err = enc.encodeKey(b, ctx.key) @@ -470,12 +471,7 @@ func (enc *Encoder) encodeMap(b []byte, ctx encoderCtx, v reflect.Value) ([]byte continue } - table, err := willConvertToTableOrArrayTable(ctx, v) - if err != nil { - return nil, err - } - - if table { + if willConvertToTableOrArrayTable(ctx, v) { t.pushTable(k, v, emptyValueOptions) } else { t.pushKV(k, v, emptyValueOptions) @@ -543,18 +539,13 @@ func (enc *Encoder) encodeStruct(b []byte, ctx encoderCtx, v reflect.Value) ([]b continue } - willConvert, err := willConvertToTableOrArrayTable(ctx, f) - if err != nil { - return nil, err - } - options := valueOptions{ multiline: fieldBoolTag(fieldType, "multiline"), } inline := fieldBoolTag(fieldType, "inline") - if inline || !willConvert { + if inline || !willConvertToTableOrArrayTable(ctx, f) { t.pushKV(k, f, options) } else { t.pushTable(k, f, options) @@ -640,21 +631,8 @@ func (enc *Encoder) encodeTableInline(b []byte, ctx encoderCtx, t table) ([]byte } } - for _, table := range t.tables { - if first { - first = false - } else { - b = append(b, `, `...) - } - - ctx.setKey(table.Key) - - b, err = enc.encode(b, ctx, table.Value) - if err != nil { - return nil, err - } - - b = append(b, '\n') + if len(t.tables) > 0 { + panic("inline table cannot contain nested tables, online key-values") } b = append(b, "}"...) @@ -664,61 +642,50 @@ func (enc *Encoder) encodeTableInline(b []byte, ctx encoderCtx, t table) ([]byte var textMarshalerType = reflect.TypeOf(new(encoding.TextMarshaler)).Elem() -func willConvertToTable(ctx encoderCtx, v reflect.Value) (bool, error) { +func willConvertToTable(ctx encoderCtx, v reflect.Value) bool { if v.Type() == timeType || v.Type().Implements(textMarshalerType) { - return false, nil + return false } t := v.Type() switch t.Kind() { case reflect.Map, reflect.Struct: - return !ctx.inline, nil + return !ctx.inline case reflect.Interface: - if v.IsNil() { - return false, fmt.Errorf("toml: encoding a nil interface is not supported") - } - return willConvertToTable(ctx, v.Elem()) case reflect.Ptr: if v.IsNil() { - return false, nil + return false } return willConvertToTable(ctx, v.Elem()) default: - return false, nil + return false } } -func willConvertToTableOrArrayTable(ctx encoderCtx, v reflect.Value) (bool, error) { +func willConvertToTableOrArrayTable(ctx encoderCtx, v reflect.Value) bool { t := v.Type() if t.Kind() == reflect.Interface { - if v.IsNil() { - return false, fmt.Errorf("toml: encoding a nil interface is not supported") - } - return willConvertToTableOrArrayTable(ctx, v.Elem()) } if t.Kind() == reflect.Slice { if v.Len() == 0 { // An empty slice should be a kv = []. - return false, nil + return false } for i := 0; i < v.Len(); i++ { - t, err := willConvertToTable(ctx, v.Index(i)) - if err != nil { - return false, err - } + t := willConvertToTable(ctx, v.Index(i)) if !t { - return false, nil + return false } } - return true, nil + return true } return willConvertToTable(ctx, v) @@ -731,12 +698,7 @@ func (enc *Encoder) encodeSlice(b []byte, ctx encoderCtx, v reflect.Value) ([]by return b, nil } - allTables, err := willConvertToTableOrArrayTable(ctx, v) - if err != nil { - return nil, err - } - - if allTables { + if willConvertToTableOrArrayTable(ctx, v) { return enc.encodeSliceAsArrayTable(b, ctx, v) } @@ -746,10 +708,6 @@ func (enc *Encoder) encodeSlice(b []byte, ctx encoderCtx, v reflect.Value) ([]by // caller should have checked that v is a slice that only contains values that // encode into tables. func (enc *Encoder) encodeSliceAsArrayTable(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) { - if v.Len() == 0 { - return b, nil - } - ctx.shiftKey() var err error diff --git a/marshaler_test.go b/marshaler_test.go index d7194068..5c946c32 100644 --- a/marshaler_test.go +++ b/marshaler_test.go @@ -16,6 +16,12 @@ import ( func TestMarshal(t *testing.T) { t.Parallel() + someInt := 42 + + type structInline struct { + A interface{} `inline:"true"` + } + examples := []struct { desc string v interface{} @@ -298,6 +304,213 @@ A = [ ] `, }, + { + desc: "nil interface not supported at root", + v: nil, + err: true, + }, + { + desc: "nil interface not supported in slice", + v: map[string]interface{}{ + "a": []interface{}{"a", nil, 2}, + }, + err: true, + }, + { + desc: "nil pointer in slice uses zero value", + v: struct { + A []*int + }{ + A: []*int{nil}, + }, + expected: `A = [0]`, + }, + { + desc: "nil pointer in slice uses zero value", + v: struct { + A []*int + }{ + A: []*int{nil}, + }, + expected: `A = [0]`, + }, + { + desc: "pointer in slice", + v: struct { + A []*int + }{ + A: []*int{&someInt}, + }, + expected: `A = [42]`, + }, + { + desc: "inline table in inline table", + v: structInline{ + A: structInline{ + A: structInline{ + A: "hello", + }, + }, + }, + expected: `A = {A = {A = 'hello'}}`, + }, + { + desc: "empty slice in map", + v: map[string][]string{ + "a": {}, + }, + expected: `a = []`, + }, + { + desc: "map in slice", + v: map[string][]map[string]string{ + "a": {{"hello": "world"}}, + }, + expected: ` +[[a]] +hello = 'world'`, + }, + { + desc: "newline in map in slice", + v: map[string][]map[string]string{ + "a\n": {{"hello": "world"}}, + }, + err: true, + }, + { + desc: "newline in map in slice", + v: map[string][]map[string]*customTextMarshaler{ + "a": {{"hello": &customTextMarshaler{1}}}, + }, + err: true, + }, + { + desc: "empty slice of empty struct", + v: struct { + A []struct{} + }{ + A: []struct{}{}, + }, + expected: `A = []`, + }, + { + desc: "nil field is ignored", + v: struct { + A interface{} + }{ + A: nil, + }, + expected: ``, + }, + { + desc: "private fields are ignored", + v: struct { + Public string + private string + }{ + Public: "shown", + private: "hidden", + }, + expected: `Public = 'shown'`, + }, + { + desc: "fields tagged - are ignored", + v: struct { + Public string `toml:"-"` + private string + }{ + Public: "hidden", + }, + expected: ``, + }, + { + desc: "nil value in map is ignored", + v: map[string]interface{}{ + "A": nil, + }, + expected: ``, + }, + { + desc: "new line in table key", + v: map[string]interface{}{ + "hello\nworld": 42, + }, + err: true, + }, + { + desc: "new line in parent of nested table key", + v: map[string]interface{}{ + "hello\nworld": map[string]interface{}{ + "inner": 42, + }, + }, + err: true, + }, + { + desc: "new line in nested table key", + v: map[string]interface{}{ + "parent": map[string]interface{}{ + "in\ner": map[string]interface{}{ + "foo": 42, + }, + }, + }, + err: true, + }, + { + desc: "invalid map key", + v: map[int]interface{}{}, + err: true, + }, + { + desc: "unhandled type", + v: struct { + A chan int + }{ + A: make(chan int), + }, + err: true, + }, + { + desc: "numbers", + v: struct { + A float32 + B uint64 + C uint32 + D uint16 + E uint8 + F uint + G int64 + H int32 + I int16 + J int8 + K int + }{ + A: 1.1, + B: 42, + C: 42, + D: 42, + E: 42, + F: 42, + G: 42, + H: 42, + I: 42, + J: 42, + K: 42, + }, + expected: ` +A = 1.1 +B = 42 +C = 42 +D = 42 +E = 42 +F = 42 +G = 42 +H = 42 +I = 42 +J = 42 +K = 42`, + }, } for _, e := range examples { @@ -460,6 +673,85 @@ root = 'value0' } } +type customTextMarshaler struct { + value int64 +} + +func (c *customTextMarshaler) MarshalText() ([]byte, error) { + if c.value == 1 { + return nil, fmt.Errorf("cannot represent 1 because this is a silly test") + } + return []byte(fmt.Sprintf("::%d", c.value)), nil +} + +func TestMarshalTextMarshaler_NoRoot(t *testing.T) { + t.Parallel() + + c := customTextMarshaler{} + _, err := toml.Marshal(&c) + require.Error(t, err) +} + +func TestMarshalTextMarshaler_Error(t *testing.T) { + t.Parallel() + + m := map[string]interface{}{"a": &customTextMarshaler{value: 1}} + _, err := toml.Marshal(m) + require.Error(t, err) +} + +func TestMarshalTextMarshaler_ErrorInline(t *testing.T) { + t.Parallel() + + type s struct { + A map[string]interface{} `inline:"true"` + } + + d := s{ + A: map[string]interface{}{"a": &customTextMarshaler{value: 1}}, + } + + _, err := toml.Marshal(d) + require.Error(t, err) +} + +func TestMarshalTextMarshaler(t *testing.T) { + t.Parallel() + + m := map[string]interface{}{"a": &customTextMarshaler{value: 2}} + r, err := toml.Marshal(m) + require.NoError(t, err) + equalStringsIgnoreNewlines(t, "a = '::2'", string(r)) +} + +type brokenWriter struct{} + +func (b *brokenWriter) Write([]byte) (int, error) { + return 0, fmt.Errorf("dead") +} + +func TestEncodeToBrokenWriter(t *testing.T) { + t.Parallel() + w := brokenWriter{} + enc := toml.NewEncoder(&w) + err := enc.Encode(map[string]string{"hello": "world"}) + require.Error(t, err) +} + +func TestEncoderSetIndentSymbol(t *testing.T) { + t.Parallel() + var w strings.Builder + enc := toml.NewEncoder(&w) + enc.SetIndentTables(true) + enc.SetIndentSymbol(">>>") + err := enc.Encode(map[string]map[string]string{"parent": {"hello": "world"}}) + require.NoError(t, err) + expected := ` +[parent] +>>>hello = 'world'` + equalStringsIgnoreNewlines(t, expected, w.String()) +} + func TestIssue436(t *testing.T) { t.Parallel() diff --git a/parser.go b/parser.go index 5b6e3bac..eefbf032 100644 --- a/parser.go +++ b/parser.go @@ -2,7 +2,6 @@ package toml import ( "bytes" - "fmt" "strconv" "github.com/pelletier/go-toml/v2/internal/ast" @@ -77,7 +76,6 @@ func (p *parser) parseNewline(b []byte) ([]byte, error) { if b[0] == '\r' { _, rest, err := scanWindowsNewline(b) - return rest, err } @@ -206,6 +204,10 @@ func (p *parser) parseKeyval(b []byte) (ast.Reference, []byte, error) { b = p.parseWhitespace(b) + if len(b) == 0 { + return ast.Reference{}, nil, newDecodeError(b, "expected = after a key, but the document ends there") + } + b, err = expect('=', b) if err != nil { return ast.Reference{}, nil, err @@ -304,6 +306,7 @@ func atmost(b []byte, n int) []byte { if n >= len(b) { return b } + return b[:n] } @@ -397,8 +400,7 @@ func (p *parser) parseValArray(b []byte) (ast.Reference, []byte, error) { } if len(b) == 0 { - //nolint:godox - return parent, nil, unexpectedCharacter{b: b} // TODO: should be unexpected EOF + return parent, nil, newDecodeError(b, "array is incomplete") } if b[0] == ']' { @@ -562,7 +564,7 @@ func (p *parser) parseMultilineBasicString(b []byte) ([]byte, []byte, error) { case 't': builder.WriteByte('\t') case 'u': - x, err := hexToString(token[i+3:len(token)-3], 4) + x, err := hexToString(atmost(token[i+1:], 4), 4) if err != nil { return nil, nil, err } @@ -570,7 +572,7 @@ func (p *parser) parseMultilineBasicString(b []byte) ([]byte, []byte, error) { builder.WriteString(x) i += 4 case 'U': - x, err := hexToString(token[i+3:len(token)-3], 8) + x, err := hexToString(atmost(token[i+1:], 8), 8) if err != nil { return nil, nil, err } @@ -610,12 +612,7 @@ func (p *parser) parseKey(b []byte) (ast.Reference, []byte, error) { for { b = p.parseWhitespace(b) if len(b) > 0 && b[0] == '.' { - b, err = expect('.', b) - if err != nil { - return ref, nil, err - } - - b = p.parseWhitespace(b) + b = p.parseWhitespace(b[1:]) key, b, err = p.parseSimpleKey(b) if err != nil { @@ -639,8 +636,7 @@ func (p *parser) parseSimpleKey(b []byte) (key, rest []byte, err error) { // unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _ // quoted-key = basic-string / literal-string if len(b) == 0 { - //nolint:godox - return nil, nil, unexpectedCharacter{b: b} // TODO: should be unexpected EOF + return nil, nil, newDecodeError(b, "key is incomplete") } switch { @@ -649,10 +645,10 @@ func (p *parser) parseSimpleKey(b []byte) (key, rest []byte, err error) { case b[0] == '"': return p.parseBasicString(b) case isUnquotedKeyChar(b[0]): - return scanUnquotedKey(b) + key, rest = scanUnquotedKey(b) + return key, rest, nil default: - //nolint:godox - return nil, nil, unexpectedCharacter{b: b} // TODO: should be unexpected EOF + return nil, nil, newDecodeError(b[0:1], "invalid character at start of key: %c", b[0]) } } @@ -825,11 +821,14 @@ byteLoop: c := b[i] switch { - case isDigit(c) || c == '-': + case isDigit(c): + case c == '-': + const offsetOfTz = 19 + if i == offsetOfTz { + hasTz = true + } case c == 'T' || c == ':' || c == '.': hasTime = true - - continue byteLoop case c == '+' || c == '-' || c == 'Z': hasTz = true case c == ' ': @@ -854,9 +853,6 @@ byteLoop: kind = ast.LocalDateTime } } else { - if hasTz { - return ast.Reference{}, nil, newDecodeError(b, "date-time has timezone but not time component") - } kind = ast.LocalDate } @@ -977,26 +973,9 @@ func isValidBinaryRune(r byte) bool { } func expect(x byte, b []byte) ([]byte, error) { - if len(b) == 0 { - return nil, newDecodeError(b[:0], "expecting %#U", x) - } - if b[0] != x { return nil, newDecodeError(b[0:1], "expected character %U", x) } return b[1:], nil } - -type unexpectedCharacter struct { - r byte - b []byte -} - -func (u unexpectedCharacter) Error() string { - if len(u.b) == 0 { - return fmt.Sprintf("expected %#U, not EOF", u.r) - } - - return fmt.Sprintf("expected %#U, not %#U", u.r, u.b[0]) -} diff --git a/scanner.go b/scanner.go index 047aef53..b2037022 100644 --- a/scanner.go +++ b/scanner.go @@ -30,15 +30,15 @@ func scanFollowsNan(b []byte) bool { return scanFollows(b, `nan`) } -func scanUnquotedKey(b []byte) ([]byte, []byte, error) { +func scanUnquotedKey(b []byte) ([]byte, []byte) { // unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _ for i := 0; i < len(b); i++ { if !isUnquotedKeyChar(b[i]) { - return b[:i], b[i:], nil + return b[:i], b[i:] } } - return b, b[len(b):], nil + return b, b[len(b):] } func isUnquotedKeyChar(r byte) bool { diff --git a/targets.go b/targets.go index fd3eaec8..370892f0 100644 --- a/targets.go +++ b/targets.go @@ -70,19 +70,19 @@ func (t interfaceTarget) set(v reflect.Value) { } func (t interfaceTarget) setString(v string) { - t.x.setString(v) + panic("interface targets should always go through set") } func (t interfaceTarget) setBool(v bool) { - t.x.setBool(v) + panic("interface targets should always go through set") } func (t interfaceTarget) setInt64(v int64) { - t.x.setInt64(v) + panic("interface targets should always go through set") } func (t interfaceTarget) setFloat64(v float64) { - t.x.setFloat64(v) + panic("interface targets should always go through set") } // mapTarget targets a specific key of a map. @@ -115,7 +115,6 @@ func (t mapTarget) setFloat64(v float64) { t.set(reflect.ValueOf(v)) } -//nolint:cyclop // makes sure that the value pointed at by t is indexable (Slice, Array), or // dereferences to an indexable (Ptr, Interface). func ensureValueIndexable(t target) error { @@ -193,7 +192,7 @@ const ( minInt = -maxInt - 1 ) -//nolint:funlen,gocognit,cyclop,gocyclo +//nolint:funlen,gocognit,cyclop func setInt64(t target, v int64) error { f := t.get() @@ -285,7 +284,6 @@ func setFloat64(t target, v float64) error { return nil } -//nolint:cyclop // Returns the element at idx of the value pointed at by target, or an error if // t does not point to an indexable. // If the target points to an Array and idx is out of bounds, it returns @@ -311,7 +309,6 @@ func elementAt(t target, idx int) target { case reflect.Interface: // This function is called after ensureValueIndexable, so it's // guaranteed that f contains an initialized slice. - ifaceElem := f.Elem() idx := ifaceElem.Len() newElem := reflect.New(ifaceElem.Type().Elem()).Elem() @@ -326,7 +323,6 @@ func elementAt(t target, idx int) target { } } -//nolint:cyclop func (d *decoder) scopeTableTarget(shouldAppend bool, t target, name string) (target, bool, error) { x := t.get() diff --git a/unmarshaler.go b/unmarshaler.go index 3d25a862..6a156ae6 100644 --- a/unmarshaler.go +++ b/unmarshaler.go @@ -541,6 +541,27 @@ func (d *decoder) unmarshalArray(x target, node ast.Node) error { return err } + // Special work around when unmarshaling into an array. + // If the array is not addressable, for example when stored as a value in a + // map, calling elementAt in the inner function would fail. + // Instead, we allocate a new array that will be filled then inserted into + // the container. + // This problem does not exist with slices because they are addressable. + // There may be a better way of doing this, but it is not obvious to me + // with the target system. + if x.get().Kind() == reflect.Array { + container := x + newArrayPtr := reflect.New(x.get().Type()) + x = valueTarget(newArrayPtr.Elem()) + defer func() { + container.set(newArrayPtr.Elem()) + }() + } + + return d.unmarshalArrayInner(x, node) +} + +func (d *decoder) unmarshalArrayInner(x target, node ast.Node) error { idx := 0 it := node.Children() @@ -555,14 +576,13 @@ func (d *decoder) unmarshalArray(x target, node ast.Node) error { break } - err = d.unmarshalValue(v, n) + err := d.unmarshalValue(v, n) if err != nil { return err } idx++ } - return nil } diff --git a/unmarshaler_test.go b/unmarshaler_test.go index 338c0f50..6346b8e8 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/require" ) +// nolint:funlen func TestUnmarshal_Integers(t *testing.T) { t.Parallel() @@ -239,6 +240,34 @@ func TestUnmarshal(t *testing.T) { } }, }, + { + desc: "time.time with negative zone", + input: `a = 1979-05-27T00:32:00-07:00 `, // space intentional + gen: func() test { + var v map[string]time.Time + + return test{ + target: &v, + expected: &map[string]time.Time{ + "a": time.Date(1979, 5, 27, 0, 32, 0, 0, time.FixedZone("", -7*3600)), + }, + } + }, + }, + { + desc: "time.time with positive zone", + input: `a = 1979-05-27T00:32:00+07:00`, + gen: func() test { + var v map[string]time.Time + + return test{ + target: &v, + expected: &map[string]time.Time{ + "a": time.Date(1979, 5, 27, 0, 32, 0, 0, time.FixedZone("", 7*3600)), + }, + } + }, + }, { desc: "issue 475 - space between dots in key", input: `fruit. color = "yellow" @@ -288,6 +317,73 @@ func TestUnmarshal(t *testing.T) { } }, }, + { + desc: "multiline literal string with windows newline", + input: "A = '''\r\nTest'''", + gen: func() test { + type doc struct { + A string + } + + return test{ + target: &doc{}, + expected: &doc{A: "Test"}, + } + }, + }, + { + desc: "multiline basic string with windows newline", + input: "A = \"\"\"\r\nTest\"\"\"", + gen: func() test { + type doc struct { + A string + } + + return test{ + target: &doc{}, + expected: &doc{A: "Test"}, + } + }, + }, + { + desc: "multiline basic string escapes", + input: `A = """ +\\\b\f\n\r\t\uffff\U0001D11E"""`, + gen: func() test { + type doc struct { + A string + } + + return test{ + target: &doc{}, + expected: &doc{A: "\\\b\f\n\r\t\uffff\U0001D11E"}, + } + }, + }, + { + desc: "basic string escapes", + input: `A = "\\\b\f\n\r\t\uffff\U0001D11E"`, + gen: func() test { + type doc struct { + A string + } + + return test{ + target: &doc{}, + expected: &doc{A: "\\\b\f\n\r\t\uffff\U0001D11E"}, + } + }, + }, + { + desc: "spaces around dotted keys", + input: "a . b = 1", + gen: func() test { + return test{ + target: &map[string]map[string]interface{}{}, + expected: &map[string]map[string]interface{}{"a": {"b": int64(1)}}, + } + }, + }, { desc: "kv bool true", input: `A = true`, @@ -721,6 +817,197 @@ B = "data"`, } }, }, + { + desc: "interface holding a string", + input: `A = "Hello"`, + gen: func() test { + type doc struct { + A interface{} + } + return test{ + target: &doc{}, + expected: &doc{ + A: "Hello", + }, + } + }, + }, + { + desc: "map of bools", + input: `A = true`, + gen: func() test { + return test{ + target: &map[string]bool{}, + expected: &map[string]bool{"A": true}, + } + }, + }, + { + desc: "map of int64", + input: `A = 42`, + gen: func() test { + return test{ + target: &map[string]int64{}, + expected: &map[string]int64{"A": 42}, + } + }, + }, + { + desc: "map of float64", + input: `A = 4.2`, + gen: func() test { + return test{ + target: &map[string]float64{}, + expected: &map[string]float64{"A": 4.2}, + } + }, + }, + { + desc: "array of int in map", + input: `A = [1,2,3]`, + gen: func() test { + return test{ + target: &map[string][3]int{}, + expected: &map[string][3]int{"A": {1, 2, 3}}, + } + }, + }, + { + desc: "array of int in map with too many elements", + input: `A = [1,2,3,4,5]`, + gen: func() test { + return test{ + target: &map[string][3]int{}, + expected: &map[string][3]int{"A": {1, 2, 3}}, + } + }, + }, + { + desc: "array of int in map with invalid element", + input: `A = [1,2,false]`, + gen: func() test { + return test{ + target: &map[string][3]int{}, + err: true, + } + }, + }, + { + desc: "nested arrays", + input: ` + [[A]] + [[A.B]] + C = 1 + [[A]] + [[A.B]] + C = 2`, + gen: func() test { + type leaf struct { + C int + } + type inner struct { + B [2]leaf + } + type s struct { + A [2]inner + } + return test{ + target: &s{}, + expected: &s{A: [2]inner{ + {B: [2]leaf{ + {C: 1}, + }}, + {B: [2]leaf{ + {C: 2}, + }}, + }}, + } + }, + }, + { + desc: "nested arrays too many", + input: ` + [[A]] + [[A.B]] + C = 1 + [[A.B]] + C = 2`, + gen: func() test { + type leaf struct { + C int + } + type inner struct { + B [1]leaf + } + type s struct { + A [1]inner + } + return test{ + target: &s{}, + err: true, + } + }, + }, + { + desc: "into map with invalid key type", + input: `A = "hello"`, + gen: func() test { + return test{ + target: &map[int]string{}, + err: true, + } + }, + }, + { + desc: "into map with convertible key type", + input: `A = "hello"`, + gen: func() test { + type foo string + return test{ + target: &map[foo]string{}, + expected: &map[foo]string{ + "A": "hello", + }, + } + }, + }, + { + desc: "array of int in struct", + input: `A = [1,2,3]`, + gen: func() test { + type s struct { + A [3]int + } + return test{ + target: &s{}, + expected: &s{A: [3]int{1, 2, 3}}, + } + }, + }, + { + desc: "array of int in struct", + input: `[A] + b = 42`, + gen: func() test { + type s struct { + A *map[string]interface{} + } + return test{ + target: &s{}, + expected: &s{A: &map[string]interface{}{"b": int64(42)}}, + } + }, + }, + { + desc: "assign bool to float", + input: `A = true`, + gen: func() test { + return test{ + target: &map[string]float64{}, + err: true, + } + }, + }, { desc: "interface holding a struct", input: `[A] @@ -877,6 +1164,82 @@ B = "data"`, } } +func TestUnmarshalOverflows(t *testing.T) { + examples := []struct { + t interface{} + errors []string + }{ + { + t: &map[string]int32{}, + errors: []string{`-2147483649`, `2147483649`}, + }, + { + t: &map[string]int16{}, + errors: []string{`-2147483649`, `2147483649`}, + }, + { + t: &map[string]int8{}, + errors: []string{`-2147483649`, `2147483649`}, + }, + { + t: &map[string]int{}, + errors: []string{`-19223372036854775808`, `9223372036854775808`}, + }, + { + t: &map[string]uint64{}, + errors: []string{`-1`, `18446744073709551616`}, + }, + { + t: &map[string]uint32{}, + errors: []string{`-1`, `18446744073709551616`}, + }, + { + t: &map[string]uint16{}, + errors: []string{`-1`, `18446744073709551616`}, + }, + { + t: &map[string]uint8{}, + errors: []string{`-1`, `18446744073709551616`}, + }, + { + t: &map[string]uint{}, + errors: []string{`-1`, `18446744073709551616`}, + }, + } + + for _, e := range examples { + e := e + for _, v := range e.errors { + v := v + t.Run(fmt.Sprintf("%T %s", e.t, v), func(t *testing.T) { + doc := "A = " + v + err := toml.Unmarshal([]byte(doc), e.t) + t.Log("input:", doc) + require.Error(t, err) + }) + } + t.Run(fmt.Sprintf("%T ok", e.t), func(t *testing.T) { + doc := "A = 1" + err := toml.Unmarshal([]byte(doc), e.t) + t.Log("input:", doc) + require.NoError(t, err) + }) + } +} + +func TestUnmarshalFloat32(t *testing.T) { + t.Run("fits", func(t *testing.T) { + doc := "A = 1.2" + err := toml.Unmarshal([]byte(doc), &map[string]float32{}) + require.NoError(t, err) + }) + t.Run("overflows", func(t *testing.T) { + doc := "A = 4.40282346638528859811704183484516925440e+38" + err := toml.Unmarshal([]byte(doc), &map[string]float32{}) + require.Error(t, err) + }) +} + type Integer484 struct { Value int } @@ -999,10 +1362,66 @@ func TestUnmarshalDecodeErrors(t *testing.T) { data string msg string }{ + { + desc: "local date with invalid digit", + data: `a = 20x1-05-21`, + }, + { + desc: "local time with fractional", + data: `a = 11:22:33.x`, + }, + { + desc: "local time frac precision too large", + data: `a = 2021-05-09T11:22:33.99999999999`, + }, + { + desc: "wrong time offset separator", + data: `a = 1979-05-27T00:32:00T07:00`, + }, + { + desc: "wrong time offset separator", + data: `a = 1979-05-27T00:32:00Z07:00`, + }, + { + desc: "float with double _", + data: `flt8 = 224_617.445_991__228`, + }, + { + desc: "float with double _", + data: `flt8 = 1..2`, + }, { desc: "int with wrong base", data: `a = 0f2`, }, + { + desc: "int hex with double underscore", + data: `a = 0xFFF__FFF`, + }, + { + desc: "int hex very large", + data: `a = 0xFFFFFFFFFFFFFFFFF`, + }, + { + desc: "int oct with double underscore", + data: `a = 0o777__77`, + }, + { + desc: "int oct very large", + data: `a = 0o77777777777777777777777`, + }, + { + desc: "int bin with double underscore", + data: `a = 0b111__111`, + }, + { + desc: "int bin very large", + data: `a = 0b11111111111111111111111111111111111111111111111111111111111111111111111111111`, + }, + { + desc: "int dec very large", + data: `a = 999999999999999999999999`, + }, { desc: "literal string with new lines", data: `a = 'hello @@ -1065,6 +1484,102 @@ world'`, data: `a = 2021-03-30 21:312:0`, msg: `expecting colon between minutes and seconds`, }, + { + desc: `binary with invalid digit`, + data: `a = 0bf`, + }, + { + desc: `invalid i in dec`, + data: `a = 0i`, + }, + { + desc: `invalid n in dec`, + data: `a = 0n`, + }, + { + desc: `invalid unquoted key`, + data: `a`, + }, + { + desc: "dt with tz has no time", + data: `a = 2021-03-30TZ`, + }, + { + desc: "invalid end of array table", + data: `[[a}`, + }, + { + desc: "invalid end of array table two", + data: `[[a]}`, + }, + { + desc: "eof after equal", + data: `a =`, + }, + { + desc: "invalid true boolean", + data: `a = trois`, + }, + { + desc: "invalid false boolean", + data: `a = faux`, + }, + { + desc: "inline table with incorrect separator", + data: `a = {b=1;}`, + }, + { + desc: "inline table with invalid value", + data: `a = {b=faux}`, + }, + { + desc: `incomplete array after whitespace`, + data: `a = [ `, + }, + { + desc: `array with comma first`, + data: `a = [ ,]`, + }, + { + desc: `array staring with incomplete newline`, + data: "a = [\r]", + }, + { + desc: `array with incomplete newline after comma`, + data: "a = [1,\r]", + }, + { + desc: `array with incomplete newline after value`, + data: "a = [1\r]", + }, + { + desc: `invalid unicode in basic multiline string`, + data: `A = """\u123"""`, + }, + { + desc: `invalid long unicode in basic multiline string`, + data: `A = """\U0001D11"""`, + }, + { + desc: `invalid unicode in basic string`, + data: `A = "\u123"`, + }, + { + desc: `invalid long unicode in basic string`, + data: `A = "\U0001D11"`, + }, + { + desc: `invalid escape char basic multiline string`, + data: `A = """\z"""`, + }, + { + desc: `invalid inf`, + data: `A = ick`, + }, + { + desc: `invalid nan`, + data: `A = non`, + }, } for _, e := range examples { @@ -1270,21 +1785,35 @@ bar = 42 t.Run(e.desc, func(t *testing.T) { t.Parallel() - r := strings.NewReader(e.input) - d := toml.NewDecoder(r) - d.SetStrict(true) - x := e.target - if x == nil { - x = &struct{}{} - } - err := d.Decode(x) + t.Run("strict", func(t *testing.T) { + r := strings.NewReader(e.input) + d := toml.NewDecoder(r) + d.SetStrict(true) + x := e.target + if x == nil { + x = &struct{}{} + } + err := d.Decode(x) - var tsm *toml.StrictMissingError - if errors.As(err, &tsm) { - equalStringsIgnoreNewlines(t, e.expected, tsm.String()) - } else { - t.Fatalf("err should have been a *toml.StrictMissingError, but got %s (%T)", err, err) - } + var tsm *toml.StrictMissingError + if errors.As(err, &tsm) { + equalStringsIgnoreNewlines(t, e.expected, tsm.String()) + } else { + t.Fatalf("err should have been a *toml.StrictMissingError, but got %s (%T)", err, err) + } + }) + + t.Run("default", func(t *testing.T) { + r := strings.NewReader(e.input) + d := toml.NewDecoder(r) + d.SetStrict(false) + x := e.target + if x == nil { + x = &struct{}{} + } + err := d.Decode(x) + require.NoError(t, err) + }) }) } } From d276c42adc0da811c8fbb831a740b60432a5b1a7 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Mon, 10 May 2021 20:22:12 -0400 Subject: [PATCH 196/228] Run coverage test on branches only --- .github/workflows/coverage.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 0a5e46d5..12b7c095 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -1,15 +1,12 @@ name: coverage on: - push: - branches: - - v2 pull_request: branches: - v2 jobs: report: - runs-on: 'ubuntu-latest' + runs-on: "ubuntu-latest" name: report steps: - uses: actions/checkout@master From 67852cf007d1797518abba47606352b719a771b5 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Sat, 15 May 2021 08:49:15 -0400 Subject: [PATCH 197/228] Clarify default struct tag being unsupported (#543) Follow up to https://github.com/pelletier/go-toml/issues/542 --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index 876f31e5..7d262303 100644 --- a/README.md +++ b/README.md @@ -248,6 +248,23 @@ This method was not widely used, poorly defined, and added a lot of complexity. A similar effect can be achieved by implementing the `encoding.TextUnmarshaler` interface and use strings. + +#### Support for `default` struct tag has been dropped + +This feature adds complexity and a poorly defined API for an effect that can be +accomplished outside of the library. + +It does not seem like other format parsers in Go support that feature (the +project referenced in the original ticket #202 has not been updated since 2017). +Given that go-toml v2 should not touch values not in the document, the same +effect can be achieved by pre-filling the struct with defaults (libraries like +[go-defaults][go-defaults] can help). Also, string representation is not well +defined for all types: it creates issues like #278. + +The recommended replacement is pre-filling the struct before unmarshaling. + +[go-defaults]: https://github.com/mcuadros/go-defaults + ### Encoding / Marshal #### Default struct fields order From 238a6fef7dc8492f242222b42e939993acdbcae9 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Sat, 15 May 2021 08:55:07 -0400 Subject: [PATCH 198/228] Add links to proper feedback channels --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7d262303..521ed749 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,11 @@ with v1, and fixes a lot known bugs and performance issues along the way. If you do not need the advanced document editing features of v1, you are encouraged to try out this version. -👉 [Roadmap for v2](https://github.com/pelletier/go-toml/discussions/506). +[👉 Roadmap for v2](https://github.com/pelletier/go-toml/discussions/506) + +[🐞 Bug Reports](https://github.com/pelletier/go-toml/issues) + +[💬 Anything else](https://github.com/pelletier/go-toml/discussions) ## Documentation From c2d1fd86e50635ace935d3dbe198afd86e87ea8f Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Fri, 21 May 2021 09:37:43 -0400 Subject: [PATCH 199/228] Fix timezone detection when time has fractional component (#544) --- benchmark/benchmark_test.go | 17 +++++++++++++++++ decode.go | 8 ++++++++ parser.go | 4 ++-- unmarshaler_test.go | 20 +++++++++++++++++++- 4 files changed, 46 insertions(+), 3 deletions(-) diff --git a/benchmark/benchmark_test.go b/benchmark/benchmark_test.go index 3e6c4857..b4951405 100644 --- a/benchmark/benchmark_test.go +++ b/benchmark/benchmark_test.go @@ -145,6 +145,23 @@ func BenchmarkReferenceFile(b *testing.B) { } } +func BenchmarkReferenceFileMap(b *testing.B) { + bytes, err := ioutil.ReadFile("benchmark.toml") + if err != nil { + b.Fatal(err) + } + b.SetBytes(int64(len(bytes))) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + m := map[string]interface{}{} + err := toml.Unmarshal(bytes, &m) + if err != nil { + panic(err) + } + } +} + func TestReferenceFile(t *testing.T) { bytes, err := ioutil.ReadFile("benchmark.toml") require.NoError(t, err) diff --git a/decode.go b/decode.go index 33ac2a96..88e6a426 100644 --- a/decode.go +++ b/decode.go @@ -172,6 +172,14 @@ func parseLocalTime(b []byte) (LocalTime, []byte, error) { digits := 0 for i, c := range b[minLengthWithFrac:] { + if !isDigit(c) { + if i == 0 { + return t, nil, newDecodeError(b[i:i+1], "need at least one digit after fraction point") + } + + break + } + const maxFracPrecision = 9 if i >= maxFracPrecision { return t, nil, newDecodeError(b[i:i+1], "maximum precision for date time is nanosecond") diff --git a/parser.go b/parser.go index eefbf032..b4caf737 100644 --- a/parser.go +++ b/parser.go @@ -823,8 +823,8 @@ byteLoop: switch { case isDigit(c): case c == '-': - const offsetOfTz = 19 - if i == offsetOfTz { + const minOffsetOfTz = 8 + if i >= minOffsetOfTz { hasTz = true } case c == 'T' || c == ':' || c == '.': diff --git a/unmarshaler_test.go b/unmarshaler_test.go index 6346b8e8..d9203a65 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -268,6 +268,20 @@ func TestUnmarshal(t *testing.T) { } }, }, + { + desc: "time.time with zone and fractional", + input: `a = 1979-05-27T00:32:00.999999-07:00`, + gen: func() test { + var v map[string]time.Time + + return test{ + target: &v, + expected: &map[string]time.Time{ + "a": time.Date(1979, 5, 27, 0, 32, 0, 999999000, time.FixedZone("", -7*3600)), + }, + } + }, + }, { desc: "issue 475 - space between dots in key", input: `fruit. color = "yellow" @@ -1376,7 +1390,11 @@ func TestUnmarshalDecodeErrors(t *testing.T) { }, { desc: "wrong time offset separator", - data: `a = 1979-05-27T00:32:00T07:00`, + data: `a = 1979-05-27T00:32:00.-07:00`, + }, + { + desc: "missing fractional with tz", + data: `a = 2021-05-09T11:22:33.99999999999`, }, { desc: "wrong time offset separator", From 840df4a22978c3ccf214a594e7a012eb44e53947 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Wed, 26 May 2021 18:47:00 -0400 Subject: [PATCH 200/228] seen tracker: use array for storage (#545) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit name old time/op new time/op delta UnmarshalDataset/config-32 81.2ms ± 3% 77.8ms ± 3% -4.25% (p=0.000 n=20+19) UnmarshalDataset/canada-32 104ms ± 5% 105ms ± 4% ~ (p=0.102 n=20+20) UnmarshalDataset/citm_catalog-32 57.5ms ± 5% 59.0ms ± 5% +2.54% (p=0.033 n=20+20) UnmarshalDataset/twitter-32 25.7ms ± 7% 28.1ms ± 5% +9.33% (p=0.000 n=20+20) UnmarshalDataset/code-32 305ms ± 6% 292ms ± 5% -4.29% (p=0.000 n=20+19) UnmarshalDataset/example-32 519µs ± 6% 522µs ± 5% ~ (p=0.659 n=20+20) UnmarshalSimple/struct-32 1.44µs ± 1% 1.17µs ± 6% -18.78% (p=0.000 n=14+20) UnmarshalSimple/map-32 2.30µs ± 4% 1.99µs ± 4% -13.65% (p=0.000 n=20+19) ReferenceFile/struct-32 44.1µs ± 4% 38.1µs ± 5% -13.61% (p=0.000 n=18+20) ReferenceFile/map-32 197µs ± 7% 189µs ± 5% -3.91% (p=0.000 n=20+20) HugoFrontMatter-32 45.9µs ± 6% 39.3µs ± 6% -14.46% (p=0.000 n=19+20) name old speed new speed delta UnmarshalDataset/config-32 12.9MB/s ± 3% 13.5MB/s ± 3% +4.42% (p=0.000 n=20+19) UnmarshalDataset/canada-32 21.1MB/s ± 5% 20.9MB/s ± 4% ~ (p=0.101 n=20+20) UnmarshalDataset/citm_catalog-32 9.72MB/s ± 6% 9.47MB/s ± 5% -2.53% (p=0.031 n=20+20) UnmarshalDataset/twitter-32 17.2MB/s ± 7% 15.8MB/s ± 5% -8.57% (p=0.000 n=20+20) UnmarshalDataset/code-32 8.81MB/s ± 7% 9.20MB/s ± 5% +4.47% (p=0.000 n=20+19) UnmarshalDataset/example-32 15.6MB/s ± 6% 15.5MB/s ± 5% ~ (p=0.644 n=20+20) UnmarshalSimple/struct-32 7.61MB/s ± 1% 9.39MB/s ± 7% +23.33% (p=0.000 n=15+20) UnmarshalSimple/map-32 4.78MB/s ± 4% 5.54MB/s ± 5% +15.85% (p=0.000 n=20+19) ReferenceFile/struct-32 119MB/s ± 4% 138MB/s ± 5% +15.79% (p=0.000 n=18+20) ReferenceFile/map-32 26.6MB/s ± 7% 27.7MB/s ± 5% +4.06% (p=0.000 n=20+20) HugoFrontMatter-32 11.9MB/s ± 6% 13.9MB/s ± 6% +16.91% (p=0.000 n=19+20) name old alloc/op new alloc/op delta UnmarshalDataset/config-32 16.9MB ± 0% 13.9MB ± 0% -17.48% (p=0.000 n=18+18) UnmarshalDataset/canada-32 74.3MB ± 0% 74.3MB ± 0% -0.00% (p=0.000 n=20+20) UnmarshalDataset/citm_catalog-32 37.3MB ± 0% 37.3MB ± 0% +0.11% (p=0.000 n=20+20) UnmarshalDataset/twitter-32 15.6MB ± 0% 15.6MB ± 0% ~ (p=0.211 n=19+20) UnmarshalDataset/code-32 59.5MB ± 0% 52.4MB ± 0% -11.96% (p=0.000 n=19+20) UnmarshalDataset/example-32 238kB ± 0% 239kB ± 0% +0.02% (p=0.000 n=18+20) UnmarshalSimple/struct-32 981B ± 0% 709B ± 0% -27.73% (p=0.000 n=20+20) UnmarshalSimple/map-32 1.45kB ± 0% 1.17kB ± 0% -18.82% (p=0.000 n=20+20) ReferenceFile/struct-32 11.8kB ± 0% 9.7kB ± 0% -17.64% (p=0.000 n=20+20) ReferenceFile/map-32 51.5kB ± 0% 52.2kB ± 0% +1.30% (p=0.000 n=20+17) HugoFrontMatter-32 12.1kB ± 0% 11.1kB ± 0% -7.97% (p=0.000 n=20+19) name old allocs/op new allocs/op delta UnmarshalDataset/config-32 645k ± 0% 557k ± 0% -13.76% (p=0.000 n=20+16) UnmarshalDataset/canada-32 896k ± 0% 896k ± 0% -0.00% (p=0.000 n=20+20) UnmarshalDataset/citm_catalog-32 380k ± 0% 377k ± 0% -0.75% (p=0.000 n=19+20) UnmarshalDataset/twitter-32 158k ± 0% 158k ± 0% -0.01% (p=0.000 n=18+18) UnmarshalDataset/code-32 2.92M ± 0% 2.57M ± 0% -11.87% (p=0.000 n=20+20) UnmarshalDataset/example-32 3.66k ± 0% 3.64k ± 0% -0.63% (p=0.000 n=20+20) UnmarshalSimple/struct-32 13.0 ± 0% 10.0 ± 0% -23.08% (p=0.000 n=20+20) UnmarshalSimple/map-32 22.0 ± 0% 19.0 ± 0% -13.64% (p=0.000 n=20+20) ReferenceFile/struct-32 253 ± 0% 155 ± 0% -38.74% (p=0.000 n=20+20) ReferenceFile/map-32 1.67k ± 0% 1.44k ± 0% -14.23% (p=0.000 n=20+20) HugoFrontMatter-32 357 ± 0% 313 ± 0% -12.32% (p=0.000 n=20+20) --- benchmark/benchmark_test.go | 118 +++++++++++++----- internal/tracker/seen.go | 233 ++++++++++++++++++++++-------------- 2 files changed, 232 insertions(+), 119 deletions(-) diff --git a/benchmark/benchmark_test.go b/benchmark/benchmark_test.go index b4951405..13378959 100644 --- a/benchmark/benchmark_test.go +++ b/benchmark/benchmark_test.go @@ -10,16 +10,38 @@ import ( ) func BenchmarkUnmarshalSimple(b *testing.B) { - d := struct { - A string - }{} doc := []byte(`A = "hello"`) - for i := 0; i < b.N; i++ { - err := toml.Unmarshal(doc, &d) - if err != nil { - panic(err) + + b.Run("struct", func(b *testing.B) { + b.SetBytes(int64(len(doc))) + b.ReportAllocs() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + d := struct { + A string + }{} + + err := toml.Unmarshal(doc, &d) + if err != nil { + panic(err) + } } - } + }) + + b.Run("map", func(b *testing.B) { + b.SetBytes(int64(len(doc))) + b.ReportAllocs() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + d := map[string]interface{}{} + err := toml.Unmarshal(doc, &d) + if err != nil { + panic(err) + } + } + }) } type benchmarkDoc struct { @@ -133,39 +155,73 @@ func BenchmarkReferenceFile(b *testing.B) { if err != nil { b.Fatal(err) } - b.SetBytes(int64(len(bytes))) - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - d := benchmarkDoc{} - err := toml.Unmarshal(bytes, &d) - if err != nil { - panic(err) + + b.Run("struct", func(b *testing.B) { + b.SetBytes(int64(len(bytes))) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + d := benchmarkDoc{} + err := toml.Unmarshal(bytes, &d) + if err != nil { + panic(err) + } } - } + }) + + b.Run("map", func(b *testing.B) { + b.SetBytes(int64(len(bytes))) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + d := map[string]interface{}{} + err := toml.Unmarshal(bytes, &d) + if err != nil { + panic(err) + } + } + }) } -func BenchmarkReferenceFileMap(b *testing.B) { +func TestReferenceFile(t *testing.T) { bytes, err := ioutil.ReadFile("benchmark.toml") - if err != nil { - b.Fatal(err) - } + require.NoError(t, err) + d := benchmarkDoc{} + err = toml.Unmarshal(bytes, &d) + require.NoError(t, err) +} + +func BenchmarkHugoFrontMatter(b *testing.B) { + bytes := []byte(` +categories = ["Development", "VIM"] +date = "2012-04-06" +description = "spf13-vim is a cross platform distribution of vim plugins and resources for Vim." +slug = "spf13-vim-3-0-release-and-new-website" +tags = [".vimrc", "plugins", "spf13-vim", "vim"] +title = "spf13-vim 3.0 release and new website" +include_toc = true +show_comments = false + +[[cascade]] + background = "yosemite.jpg" + [cascade._target] + kind = "page" + lang = "en" + path = "/blog/**" + +[[cascade]] + background = "goldenbridge.jpg" + [cascade._target] + kind = "section" +`) b.SetBytes(int64(len(bytes))) b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { - m := map[string]interface{}{} - err := toml.Unmarshal(bytes, &m) + d := map[string]interface{}{} + err := toml.Unmarshal(bytes, &d) if err != nil { panic(err) } } } - -func TestReferenceFile(t *testing.T) { - bytes, err := ioutil.ReadFile("benchmark.toml") - require.NoError(t, err) - d := benchmarkDoc{} - err = toml.Unmarshal(bytes, &d) - require.NoError(t, err) -} diff --git a/internal/tracker/seen.go b/internal/tracker/seen.go index e245aace..4b5d3924 100644 --- a/internal/tracker/seen.go +++ b/internal/tracker/seen.go @@ -1,6 +1,7 @@ package tracker import ( + "bytes" "fmt" "github.com/pelletier/go-toml/v2/internal/ast" @@ -29,67 +30,92 @@ func (k keyKind) String() string { panic("missing keyKind string mapping") } -// SeenTracker tracks which keys have been seen with which TOML type to flag duplicates -// and mismatches according to the spec. +// SeenTracker tracks which keys have been seen with which TOML type to flag +// duplicates and mismatches according to the spec. +// +// Each node in the visited tree is represented by an entry. Each entry has an +// identifier, which is provided by a counter. Entries are stored in the array +// entries. As new nodes are discovered (referenced for the first time in the +// TOML document), entries are created and appended to the array. An entry +// points to its parent using its id. +// +// To find whether a given key (sequence of []byte) has already been visited, +// the entries are linearly searched, looking for one with the right name and +// parent id. +// +// Given that all keys appear in the document after their parent, it is +// guaranteed that all descendants of a node are stored after the node, this +// speeds up the search process. +// +// When encountering [[array tables]], the descendants of that node are removed +// to allow that branch of the tree to be "rediscovered". To maintain the +// invariant above, the deletion process needs to keep the order of entries. +// This results in more copies in that case. type SeenTracker struct { - root *info - current *info + entries []entry + currentIdx int + nextID int } -type info struct { - parent *info +type entry struct { + id int + parent int + name []byte kind keyKind - children map[string]*info explicit bool } -func (i *info) Clear() { - i.children = nil +// Remove all descendent of node at position idx. +func (s *SeenTracker) clear(idx int) { + p := s.entries[idx].id + rest := clear(p, s.entries[idx+1:]) + s.entries = s.entries[:idx+1+len(rest)] } -func (i *info) Has(k string) (*info, bool) { - c, ok := i.children[k] - return c, ok -} - -func (i *info) SetKind(kind keyKind) { - i.kind = kind -} - -func (i *info) CreateTable(k string, explicit bool) *info { - return i.createChild(k, tableKind, explicit) -} - -func (i *info) CreateArrayTable(k string, explicit bool) *info { - return i.createChild(k, arrayTableKind, explicit) +func clear(parentID int, entries []entry) []entry { + for i := 0; i < len(entries); { + if entries[i].parent == parentID { + id := entries[i].id + copy(entries[i:], entries[i+1:]) + entries = entries[:len(entries)-1] + rest := clear(id, entries[i:]) + entries = entries[:i+len(rest)] + } else { + i++ + } + } + return entries } -func (i *info) createChild(k string, kind keyKind, explicit bool) *info { - if i.children == nil { - i.children = make(map[string]*info, 1) - } +func (s *SeenTracker) create(parentIdx int, name []byte, kind keyKind, explicit bool) int { + parentID := s.id(parentIdx) - x := &info{ - parent: i, + idx := len(s.entries) + s.entries = append(s.entries, entry{ + id: s.nextID, + parent: parentID, + name: name, kind: kind, explicit: explicit, - } - i.children[k] = x - return x + }) + s.nextID++ + return idx } // CheckExpression takes a top-level node and checks that it does not contain keys // that have been seen in previous calls, and validates that types are consistent. func (s *SeenTracker) CheckExpression(node ast.Node) error { - if s.root == nil { - s.root = &info{ - kind: tableKind, - } - s.current = s.root + if s.entries == nil { + //s.entries = make([]entry, 0, 8) + // Skip ID = 0 to remove the confusion between nodes whose parent has + // id 0 and root nodes (parent id is 0 because it's the zero value). + s.nextID = 1 + // Start unscoped, so idx is negative. + s.currentIdx = -1 } switch node.Kind { case ast.KeyValue: - return s.checkKeyValue(s.current, node) + return s.checkKeyValue(node) case ast.Table: return s.checkTable(node) case ast.ArrayTable: @@ -97,104 +123,135 @@ func (s *SeenTracker) CheckExpression(node ast.Node) error { default: panic(fmt.Errorf("this should not be a top level node type: %s", node.Kind)) } - } -func (s *SeenTracker) checkTable(node ast.Node) error { - s.current = s.root +func (s *SeenTracker) checkTable(node ast.Node) error { it := node.Key() - // handle the first parts of the key, excluding the last one + + parentIdx := -1 + + // This code is duplicated in checkArrayTable. This is because factoring + // it in a function requires to copy the iterator, or allocate it to the + // heap, which is not cheap. for it.Next() { if !it.Node().Next().Valid() { break } - k := string(it.Node().Data) - child, found := s.current.Has(k) - if !found { - child = s.current.CreateTable(k, false) + k := it.Node().Data + + idx := s.find(parentIdx, k) + + if idx < 0 { + idx = s.create(parentIdx, k, tableKind, false) } - s.current = child + parentIdx = idx } - // handle the last part of the key - k := string(it.Node().Data) + k := it.Node().Data + idx := s.find(parentIdx, k) - i, found := s.current.Has(k) - if found { - if i.kind != tableKind { - return fmt.Errorf("toml: key %s should be a table, not a %s", k, i.kind) + if idx >= 0 { + kind := s.entries[idx].kind + if kind != tableKind { + return fmt.Errorf("toml: key %s should be a table, not a %s", string(k), kind) } - if i.explicit { - return fmt.Errorf("toml: table %s already exists", k) + if s.entries[idx].explicit { + return fmt.Errorf("toml: table %s already exists", string(k)) } - i.explicit = true - s.current = i + s.entries[idx].explicit = true } else { - s.current = s.current.CreateTable(k, true) + idx = s.create(parentIdx, k, tableKind, true) } + s.currentIdx = idx + return nil } func (s *SeenTracker) checkArrayTable(node ast.Node) error { - s.current = s.root - it := node.Key() - // handle the first parts of the key, excluding the last one + parentIdx := -1 + for it.Next() { if !it.Node().Next().Valid() { break } - k := string(it.Node().Data) - child, found := s.current.Has(k) - if !found { - child = s.current.CreateTable(k, false) + k := it.Node().Data + + idx := s.find(parentIdx, k) + + if idx < 0 { + idx = s.create(parentIdx, k, tableKind, false) } - s.current = child + parentIdx = idx } - // handle the last part of the key - k := string(it.Node().Data) + k := it.Node().Data + idx := s.find(parentIdx, k) - info, found := s.current.Has(k) - if found { - if info.kind != arrayTableKind { - return fmt.Errorf("toml: key %s already exists as a %s, but should be an array table", info.kind, k) + if idx >= 0 { + kind := s.entries[idx].kind + if kind != arrayTableKind { + return fmt.Errorf("toml: key %s already exists as a %s, but should be an array table", kind, string(k)) } - info.Clear() + s.clear(idx) } else { - info = s.current.CreateArrayTable(k, true) + idx = s.create(parentIdx, k, arrayTableKind, true) } - s.current = info + s.currentIdx = idx + return nil } -func (s *SeenTracker) checkKeyValue(context *info, node ast.Node) error { +func (s *SeenTracker) checkKeyValue(node ast.Node) error { it := node.Key() - // handle the first parts of the key, excluding the last one + parentIdx := s.currentIdx + for it.Next() { - k := string(it.Node().Data) - child, found := context.Has(k) - if found { - if child.kind != tableKind { - return fmt.Errorf("toml: expected %s to be a table, not a %s", k, child.kind) + k := it.Node().Data + + idx := s.find(parentIdx, k) + + if idx >= 0 { + if s.entries[idx].kind != tableKind { + return fmt.Errorf("toml: expected %s to be a table, not a %s", string(k), s.entries[idx].kind) } } else { - child = context.CreateTable(k, false) + idx = s.create(parentIdx, k, tableKind, false) } - context = child + parentIdx = idx } + kind := valueKind + if node.Value().Kind == ast.InlineTable { - context.SetKind(tableKind) - } else { - context.SetKind(valueKind) + kind = tableKind } + s.entries[parentIdx].kind = kind return nil } + +func (s *SeenTracker) id(idx int) int { + if idx >= 0 { + return s.entries[idx].id + } + return 0 +} + +func (s *SeenTracker) find(parentIdx int, k []byte) int { + parentID := s.id(parentIdx) + + for i := parentIdx + 1; i < len(s.entries); i++ { + if s.entries[i].parent == parentID && bytes.Equal(s.entries[i].name, k) { + return i + } + } + + return -1 +} From 11f022ab09f392f9cb6a40bcd7996152a9da0be1 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Sun, 30 May 2021 18:53:07 -0400 Subject: [PATCH 201/228] Fix parser skipping over the whole file (#547) * Add test for ReferenceFile/struct * Stop skipping after table * Add .gitattributes to force LF encoding * Fix the reference file --- .gitattributes | 3 + benchmark/benchmark.toml | 2 +- benchmark/benchmark_test.go | 292 +++++++++++++++++++++++++++++++++++- go.mod | 3 +- go.sum | 4 +- unmarshaler.go | 2 + 6 files changed, 296 insertions(+), 10 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..39d488ba --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +* text=auto + +benchmark/benchmark.toml text eol=lf diff --git a/benchmark/benchmark.toml b/benchmark/benchmark.toml index dfd77e09..ec119752 100644 --- a/benchmark/benchmark.toml +++ b/benchmark/benchmark.toml @@ -186,7 +186,7 @@ key3 = 1979-05-27T00:32:00.999999-07:00 key1 = [ 1, 2, 3 ] key2 = [ "red", "yellow", "green" ] key3 = [ [ 1, 2 ], [3, 4, 5] ] -#key4 = [ [ 1, 2 ], ["a", "b", "c"] ] # this is ok +key4 = [ [ 1, 2 ], ["a", "b", "c"] ] # this is ok # Arrays can also be multiline. So in addition to ignoring whitespace, arrays # also ignore newlines between the brackets. Terminating commas are ok before diff --git a/benchmark/benchmark_test.go b/benchmark/benchmark_test.go index 13378959..da56f06a 100644 --- a/benchmark/benchmark_test.go +++ b/benchmark/benchmark_test.go @@ -57,7 +57,7 @@ type benchmarkDoc struct { } Point struct { X int64 - U int64 + Y int64 } } } @@ -130,6 +130,7 @@ type benchmarkDoc struct { Key2 []string Key3 [][]int64 // TODO: Key4 not supported by go-toml's Unmarshal + Key4 []interface{} Key5 []int64 Key6 []int64 } @@ -141,11 +142,11 @@ type benchmarkDoc struct { Fruit []struct { Name string Physical struct { - Color string - Shape string - Variety []struct { - Name string - } + Color string + Shape string + } + Variety []struct { + Name string } } } @@ -189,6 +190,285 @@ func TestReferenceFile(t *testing.T) { d := benchmarkDoc{} err = toml.Unmarshal(bytes, &d) require.NoError(t, err) + + expected := benchmarkDoc{ + Table: struct { + Key string + Subtable struct{ Key string } + Inline struct { + Name struct { + First string + Last string + } + Point struct { + X int64 + Y int64 + } + } + }{ + Key: "value", + Subtable: struct{ Key string }{ + Key: "another value", + }, + // note: x.y.z.w is purposefully missing + Inline: struct { + Name struct { + First string + Last string + } + Point struct { + X int64 + Y int64 + } + }{ + Name: struct { + First string + Last string + }{ + First: "Tom", + Last: "Preston-Werner", + }, + Point: struct { + X int64 + Y int64 + }{ + X: 1, + Y: 2, + }, + }, + }, + String: struct { + Basic struct{ Basic string } + Multiline struct { + Key1 string + Key2 string + Key3 string + Continued struct { + Key1 string + Key2 string + Key3 string + } + } + Literal struct { + Winpath string + Winpath2 string + Quoted string + Regex string + Multiline struct { + Regex2 string + Lines string + } + } + }{ + Basic: struct{ Basic string }{ + Basic: "I'm a string. \"You can quote me\". Name\tJos\u00E9\nLocation\tSF.", + }, + Multiline: struct { + Key1 string + Key2 string + Key3 string + Continued struct { + Key1 string + Key2 string + Key3 string + } + }{ + Key1: "One\nTwo", + Key2: "One\nTwo", + Key3: "One\nTwo", + + Continued: struct { + Key1 string + Key2 string + Key3 string + }{ + Key1: `The quick brown fox jumps over the lazy dog.`, + Key2: `The quick brown fox jumps over the lazy dog.`, + Key3: `The quick brown fox jumps over the lazy dog.`, + }, + }, + Literal: struct { + Winpath string + Winpath2 string + Quoted string + Regex string + Multiline struct { + Regex2 string + Lines string + } + }{ + Winpath: `C:\Users\nodejs\templates`, + Winpath2: `\\ServerX\admin$\system32\`, + Quoted: `Tom "Dubs" Preston-Werner`, + Regex: `<\i\c*\s*>`, + + Multiline: struct { + Regex2 string + Lines string + }{ + Regex2: `I [dw]on't need \d{2} apples`, + Lines: `The first newline is +trimmed in raw strings. + All other whitespace + is preserved. +`, + }, + }, + }, + Integer: struct { + Key1 int64 + Key2 int64 + Key3 int64 + Key4 int64 + Underscores struct { + Key1 int64 + Key2 int64 + Key3 int64 + } + }{ + Key1: 99, + Key2: 42, + Key3: 0, + Key4: -17, + + Underscores: struct { + Key1 int64 + Key2 int64 + Key3 int64 + }{ + Key1: 1000, + Key2: 5349221, + Key3: 12345, + }, + }, + Float: struct { + Fractional struct { + Key1 float64 + Key2 float64 + Key3 float64 + } + Exponent struct { + Key1 float64 + Key2 float64 + Key3 float64 + } + Both struct{ Key float64 } + Underscores struct { + Key1 float64 + Key2 float64 + } + }{ + Fractional: struct { + Key1 float64 + Key2 float64 + Key3 float64 + }{ + Key1: 1.0, + Key2: 3.1415, + Key3: -0.01, + }, + Exponent: struct { + Key1 float64 + Key2 float64 + Key3 float64 + }{ + Key1: 5e+22, + Key2: 1e6, + Key3: -2e-2, + }, + Both: struct{ Key float64 }{ + Key: 6.626e-34, + }, + Underscores: struct { + Key1 float64 + Key2 float64 + }{ + Key1: 9224617.445991228313, + Key2: 1e100, + }, + }, + Boolean: struct { + True bool + False bool + }{ + True: true, + False: false, + }, + Datetime: struct { + Key1 time.Time + Key2 time.Time + Key3 time.Time + }{ + Key1: time.Date(1979, 5, 27, 7, 32, 0, 0, time.UTC), + Key2: time.Date(1979, 5, 27, 0, 32, 0, 0, time.FixedZone("", -7*3600)), + Key3: time.Date(1979, 5, 27, 0, 32, 0, 999999000, time.FixedZone("", -7*3600)), + }, + Array: struct { + Key1 []int64 + Key2 []string + Key3 [][]int64 + Key4 []interface{} + Key5 []int64 + Key6 []int64 + }{ + Key1: []int64{1, 2, 3}, + Key2: []string{"red", "yellow", "green"}, + Key3: [][]int64{{1, 2}, {3, 4, 5}}, + Key4: []interface{}{ + []interface{}{int64(1), int64(2)}, + []interface{}{"a", "b", "c"}, + }, + Key5: []int64{1, 2, 3}, + Key6: []int64{1, 2}, + }, + Products: []struct { + Name string + Sku int64 + Color string + }{ + { + Name: "Hammer", + Sku: 738594937, + }, + {}, + { + Name: "Nail", + Sku: 284758393, + Color: "gray", + }, + }, + Fruit: []struct { + Name string + Physical struct { + Color string + Shape string + } + Variety []struct{ Name string } + }{ + { + Name: "apple", + Physical: struct { + Color string + Shape string + }{ + Color: "red", + Shape: "round", + }, + Variety: []struct{ Name string }{ + {Name: "red delicious"}, + {Name: "granny smith"}, + }, + }, + { + Name: "banana", + Variety: []struct{ Name string }{ + {Name: "plantain"}, + }, + }, + }, + } + + require.Equal(t, expected, d) } func BenchmarkHugoFrontMatter(b *testing.B) { diff --git a/go.mod b/go.mod index 6745f0a5..8fed3421 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,5 @@ module github.com/pelletier/go-toml/v2 go 1.15 -require github.com/stretchr/testify v1.7.0 +// latest (v1.7.0) doesn't have the fix for time.Time +require github.com/stretchr/testify v1.7.1-0.20210427113832-6241f9ab9942 diff --git a/go.sum b/go.sum index acb88a48..6fe6dfa1 100644 --- a/go.sum +++ b/go.sum @@ -3,8 +3,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1-0.20210427113832-6241f9ab9942 h1:t0lM6y/M5IiUZyvbBTcngso8SZEZICH7is9B6g/obVU= +github.com/stretchr/testify v1.7.1-0.20210427113832-6241f9ab9942/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= diff --git a/unmarshaler.go b/unmarshaler.go index 6a156ae6..fda94386 100644 --- a/unmarshaler.go +++ b/unmarshaler.go @@ -198,6 +198,7 @@ func (d *decoder) fromParser(p *parser, v interface{}) error { err = d.unmarshalKeyValue(current, node) found = true case ast.Table: + skipUntilTable = false d.strict.EnterTable(node) current, found, err = d.scopeWithKey(root, node.Key()) @@ -210,6 +211,7 @@ func (d *decoder) fromParser(p *parser, v interface{}) error { ensureMapIfInterface(current) } case ast.ArrayTable: + skipUntilTable = false d.strict.EnterArrayTable(node) current, found, err = d.scopeWithArrayTable(root, node.Key()) default: From 250e073408fa9921f651b466e05d6e45f2ad7b80 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Mon, 31 May 2021 12:14:13 -0400 Subject: [PATCH 202/228] Stack-based unmarshaler (#546) * Benchmark script * Rewrite unmarshaler using the stack Instead of tracking the build chain using `target`s, use the stack instead. Working and most benchmarks look good, but regression on structs unmarshalling. ~60% slower on ReferenceFile/struct. * Shortcut to check if last node of iterator * Remove unecessary pointer allocation * Skip over unused keys without marking them as seen * Add some tests * Fix mktemp on macos --- .golangci.toml | 2 +- benchmark/bench_datasets_test.go | 33 +- benchmark/benchmark_test.go | 12 + ci.sh | 37 +- errors_test.go | 4 +- fast_test.go | 100 ++ internal/ast/ast.go | 6 + .../imported_tests/unmarshal_imported_test.go | 78 +- internal/tracker/seen.go | 6 +- localtime_test.go | 18 - marshaler.go | 2 - marshaler_test.go | 22 - parser_test.go | 6 +- strict.go | 19 + targets.go | 536 -------- targets_test.go | 207 ---- toml_testgen_test.go | 74 -- types.go | 13 + unmarshaler.go | 1083 ++++++++++++----- unmarshaler_test.go | 296 ++++- 20 files changed, 1299 insertions(+), 1255 deletions(-) create mode 100644 fast_test.go delete mode 100644 targets.go delete mode 100644 targets_test.go create mode 100644 types.go diff --git a/.golangci.toml b/.golangci.toml index fdf167b4..067db551 100644 --- a/.golangci.toml +++ b/.golangci.toml @@ -60,7 +60,7 @@ enable = [ # "nlreturn", "noctx", "nolintlint", - "paralleltest", + #"paralleltest", "prealloc", "predeclared", "revive", diff --git a/benchmark/bench_datasets_test.go b/benchmark/bench_datasets_test.go index 1d668d90..ca974fd6 100644 --- a/benchmark/bench_datasets_test.go +++ b/benchmark/bench_datasets_test.go @@ -31,13 +31,14 @@ var bench_inputs = []struct { func TestUnmarshalDatasetCode(t *testing.T) { for _, tc := range bench_inputs { - buf := fixture(t, tc.name) t.Run(tc.name, func(t *testing.T) { + buf := fixture(t, tc.name) + var v interface{} - check(t, toml.Unmarshal(buf, &v)) + require.NoError(t, toml.Unmarshal(buf, &v)) b, err := json.Marshal(v) - check(t, err) + require.NoError(t, err) require.Equal(t, len(b), tc.jsonLen) }) } @@ -45,14 +46,14 @@ func TestUnmarshalDatasetCode(t *testing.T) { func BenchmarkUnmarshalDataset(b *testing.B) { for _, tc := range bench_inputs { - buf := fixture(b, tc.name) b.Run(tc.name, func(b *testing.B) { + buf := fixture(b, tc.name) b.SetBytes(int64(len(buf))) b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { var v interface{} - check(b, toml.Unmarshal(buf, &v)) + require.NoError(b, toml.Unmarshal(buf, &v)) } }) } @@ -60,22 +61,20 @@ func BenchmarkUnmarshalDataset(b *testing.B) { // fixture returns the uncompressed contents of path. func fixture(tb testing.TB, path string) []byte { - f, err := os.Open(filepath.Join("testdata", path+".toml.gz")) - check(tb, err) + tb.Helper() + + file := path + ".toml.gz" + f, err := os.Open(filepath.Join("testdata", file)) + if os.IsNotExist(err) { + tb.Skip("benchmark fixture not found:", file) + } + require.NoError(tb, err) defer f.Close() gz, err := gzip.NewReader(f) - check(tb, err) + require.NoError(tb, err) buf, err := ioutil.ReadAll(gz) - check(tb, err) - + require.NoError(tb, err) return buf } - -func check(tb testing.TB, err error) { - if err != nil { - tb.Helper() - tb.Fatal(err) - } -} diff --git a/benchmark/benchmark_test.go b/benchmark/benchmark_test.go index da56f06a..6d90571b 100644 --- a/benchmark/benchmark_test.go +++ b/benchmark/benchmark_test.go @@ -9,6 +9,18 @@ import ( "github.com/stretchr/testify/require" ) +func TestUnmarshalSimple(t *testing.T) { + doc := []byte(`A = "hello"`) + d := struct { + A string + }{} + + err := toml.Unmarshal(doc, &d) + if err != nil { + panic(err) + } +} + func BenchmarkUnmarshalSimple(b *testing.B) { doc := []byte(`A = "hello"`) diff --git a/ci.sh b/ci.sh index 75d7008b..d7e10cde 100755 --- a/ci.sh +++ b/ci.sh @@ -39,6 +39,9 @@ benchmark [OPTIONS...] [BRANCH] -d Compare benchmarks of HEAD with BRANCH using benchstats. In this form the BRANCH argument is required. + -a Compare benchmarks of HEAD against go-toml v1 and + BurntSushi/toml. + coverage [OPTIONS...] [BRANCH] Generates code coverage. @@ -118,6 +121,7 @@ coverage() { bench() { branch="${1}" out="${2}" + replace="${3}" dir="$(mktemp -d)" stderr "Executing benchmark for ${branch} at ${dir}" @@ -129,6 +133,15 @@ bench() { fi pushd "$dir" + + if [ "${replace}" != "" ]; then + find ./benchmark/ -iname '*.go' -exec sed -i -E "s|github.com/pelletier/go-toml/v2|${replace}|g" {} \; + go get "${replace}" + # hack: remove canada.toml.gz because it is not supported by + # burntsushi, and replace is only used for benchmark -a + rm -f benchmark/testdata/canada.toml.gz + fi + go test -bench=. -count=10 ./... | tee "${out}" popd @@ -142,14 +155,34 @@ benchmark() { -d) shift target="${1?Need to provide a target branch argument}" - old=`mktemp` + + old=`mktemp --suffix=-${target}` bench "${target}" "${old}" - new=`mktemp` + new=`mktemp --suffix=-HEAD` bench HEAD "${new}" + benchstat "${old}" "${new}" return 0 ;; + -a) + shift + + v2stats=`mktemp -t go-toml-v2` + bench HEAD "${v2stats}" "github.com/pelletier/go-toml/v2" + v1stats=`mktemp -t go-toml-v1` + bench HEAD "${v1stats}" "github.com/pelletier/go-toml" + bsstats=`mktemp -t bs-toml` + bench HEAD "${bsstats}" "github.com/BurntSushi/toml" + + cp "${v2stats}" go-toml-v2.txt + cp "${v1stats}" go-toml-v1.txt + cp "${bsstats}" bs-toml.txt + + benchstat -geomean go-toml-v2.txt go-toml-v1.txt bs-toml.txt + + rm -f go-toml-v2.txt go-toml-v1.txt bs-toml.txt + return $? esac bench "${1-HEAD}" `mktemp` diff --git a/errors_test.go b/errors_test.go index d6af314e..d0986478 100644 --- a/errors_test.go +++ b/errors_test.go @@ -12,7 +12,6 @@ import ( //nolint:funlen func TestDecodeError(t *testing.T) { - t.Parallel() examples := []struct { desc string @@ -154,7 +153,7 @@ line 5`, for _, e := range examples { e := e t.Run(e.desc, func(t *testing.T) { - t.Parallel() + b := bytes.Buffer{} b.Write([]byte(e.doc[0])) start := b.Len() @@ -182,7 +181,6 @@ line 5`, } func TestDecodeError_Accessors(t *testing.T) { - t.Parallel() e := DecodeError{ message: "foo", diff --git a/fast_test.go b/fast_test.go new file mode 100644 index 00000000..02910bb2 --- /dev/null +++ b/fast_test.go @@ -0,0 +1,100 @@ +package toml_test + +import ( + "testing" + + "github.com/pelletier/go-toml/v2" + "github.com/stretchr/testify/require" +) + +func TestFastSimple(t *testing.T) { + m := map[string]int64{} + err := toml.Unmarshal([]byte(`a = 42`), &m) + require.NoError(t, err) + require.Equal(t, map[string]int64{"a": 42}, m) +} + +func TestFastSimpleString(t *testing.T) { + m := map[string]string{} + err := toml.Unmarshal([]byte(`a = "hello"`), &m) + require.NoError(t, err) + require.Equal(t, map[string]string{"a": "hello"}, m) +} + +func TestFastSimpleInterface(t *testing.T) { + m := map[string]interface{}{} + err := toml.Unmarshal([]byte(` + a = "hello" + b = 42`), &m) + require.NoError(t, err) + require.Equal(t, map[string]interface{}{ + "a": "hello", + "b": int64(42), + }, m) +} + +func TestFastMultipartKeyInterface(t *testing.T) { + m := map[string]interface{}{} + err := toml.Unmarshal([]byte(` + a.interim = "test" + a.b.c = "hello" + b = 42`), &m) + require.NoError(t, err) + require.Equal(t, map[string]interface{}{ + "a": map[string]interface{}{ + "interim": "test", + "b": map[string]interface{}{ + "c": "hello", + }, + }, + "b": int64(42), + }, m) +} + +func TestFastExistingMap(t *testing.T) { + m := map[string]interface{}{ + "ints": map[string]int{}, + } + err := toml.Unmarshal([]byte(` + ints.one = 1 + ints.two = 2 + strings.yo = "hello"`), &m) + require.NoError(t, err) + require.Equal(t, map[string]interface{}{ + "ints": map[string]interface{}{ + "one": int64(1), + "two": int64(2), + }, + "strings": map[string]interface{}{ + "yo": "hello", + }, + }, m) +} + +func TestFastArrayTable(t *testing.T) { + b := []byte(` + [root] + [[root.nested]] + name = 'Bob' + [[root.nested]] + name = 'Alice' + `) + + m := map[string]interface{}{} + + err := toml.Unmarshal(b, &m) + require.NoError(t, err) + + require.Equal(t, map[string]interface{}{ + "root": map[string]interface{}{ + "nested": []interface{}{ + map[string]interface{}{ + "name": "Bob", + }, + map[string]interface{}{ + "name": "Alice", + }, + }, + }, + }, m) +} diff --git a/internal/ast/ast.go b/internal/ast/ast.go index ba2729e0..f9059d88 100644 --- a/internal/ast/ast.go +++ b/internal/ast/ast.go @@ -28,6 +28,12 @@ func (c *Iterator) Next() bool { return c.node.Valid() } +// IsLast returns true if the current node of the iterator is the last one. +// Subsequent call to Next() will return false. +func (c *Iterator) IsLast() bool { + return c.node.next <= 0 +} + // Node returns a copy of the node pointed at by the iterator. func (c *Iterator) Node() Node { return c.node diff --git a/internal/imported_tests/unmarshal_imported_test.go b/internal/imported_tests/unmarshal_imported_test.go index d3f54d87..2345445d 100644 --- a/internal/imported_tests/unmarshal_imported_test.go +++ b/internal/imported_tests/unmarshal_imported_test.go @@ -223,11 +223,13 @@ type testSubDoc struct { unexported int `toml:"shouldntBeHere"` } -var biteMe = "Bite me" -var float1 float32 = 12.3 -var float2 float32 = 45.6 -var float3 float32 = 78.9 -var subdoc = testSubDoc{"Second", 0} +var ( + biteMe = "Bite me" + float1 float32 = 12.3 + float2 float32 = 45.6 + float3 float32 = 78.9 + subdoc = testSubDoc{"Second", 0} +) var docData = testDoc{ Title: "TOML Marshal Testing", @@ -382,7 +384,7 @@ var intErrTomls = []string{ } func TestErrUnmarshal(t *testing.T) { - var errTomls = []string{ + errTomls := []string{ "bool = truly\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = 5000\nstring = \"Bite me\"", "bool = true\ndate = 1979-05-27T07:3200Z\nfloat = 123.4\nint = 5000\nstring = \"Bite me\"", "bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123a4\nint = 5000\nstring = \"Bite me\"", @@ -468,7 +470,7 @@ func TestEmptyUnmarshalOmit(t *testing.T) { Map map[string]string `toml:"map,omitempty"` } - var emptyTestData2 = emptyMarshalTestStruct2{ + emptyTestData2 := emptyMarshalTestStruct2{ Title: "Placeholder", Bool: false, Int: 0, @@ -496,21 +498,23 @@ type pointerMarshalTestStruct struct { DblPtr *[]*[]*string } -var pointerStr = "Hello" -var pointerList = []string{"Hello back"} -var pointerListPtr = []*string{&pointerStr} -var pointerMap = map[string]string{"response": "Goodbye"} -var pointerMapPtr = map[string]*string{"alternate": &pointerStr} -var pointerTestData = pointerMarshalTestStruct{ - Str: &pointerStr, - List: &pointerList, - ListPtr: &pointerListPtr, - Map: &pointerMap, - MapPtr: &pointerMapPtr, - EmptyStr: nil, - EmptyList: nil, - EmptyMap: nil, -} +var ( + pointerStr = "Hello" + pointerList = []string{"Hello back"} + pointerListPtr = []*string{&pointerStr} + pointerMap = map[string]string{"response": "Goodbye"} + pointerMapPtr = map[string]*string{"alternate": &pointerStr} + pointerTestData = pointerMarshalTestStruct{ + Str: &pointerStr, + List: &pointerList, + ListPtr: &pointerListPtr, + Map: &pointerMap, + MapPtr: &pointerMapPtr, + EmptyStr: nil, + EmptyList: nil, + EmptyMap: nil, + } +) var pointerTestToml = []byte(`List = ["Hello back"] ListPtr = ["Hello"] @@ -538,15 +542,17 @@ func TestUnmarshalTypeMismatch(t *testing.T) { type nestedMarshalTestStruct struct { String [][]string - //Struct [][]basicMarshalTestSubStruct + // Struct [][]basicMarshalTestSubStruct StringPtr *[]*[]*string // StructPtr *[]*[]*basicMarshalTestSubStruct } -var str1 = "Three" -var str2 = "Four" -var strPtr = []*string{&str1, &str2} -var strPtr2 = []*[]*string{&strPtr} +var ( + str1 = "Three" + str2 = "Four" + strPtr = []*string{&str1, &str2} + strPtr2 = []*[]*string{&strPtr} +) var nestedTestData = nestedMarshalTestStruct{ String: [][]string{{"Five", "Six"}, {"One", "Two"}}, @@ -597,6 +603,7 @@ var nestedCustomMarshalerData = customMarshalerParent{ var nestedCustomMarshalerToml = []byte(`friends = ["Sally Fields"] me = "Maiku Suteda" `) + var nestedCustomMarshalerTomlForUnmarshal = []byte(`[friends] FirstName = "Sally" LastName = "Fields"`) @@ -613,11 +620,11 @@ func (x *IntOrString) MarshalTOML() ([]byte, error) { } func TestUnmarshalTextMarshaler(t *testing.T) { - var nested = struct { + nested := struct { Friends textMarshaler `toml:"friends"` }{} - var expected = struct { + expected := struct { Friends textMarshaler `toml:"friends"` }{ Friends: textMarshaler{FirstName: "Sally", LastName: "Fields"}, @@ -1360,7 +1367,6 @@ func TestUnmarshalPreservesUnexportedFields(t *testing.T) { t.Run("unexported field should not be set from toml", func(t *testing.T) { var actual unexportedFieldPreservationTest err := toml.Unmarshal([]byte(doc), &actual) - if err != nil { t.Fatal("did not expect an error") } @@ -1394,7 +1400,6 @@ func TestUnmarshalPreservesUnexportedFields(t *testing.T) { Nested3: &unexportedFieldPreservationTestNested{"baz", "bax"}, } err := toml.Unmarshal([]byte(doc), &actual) - if err != nil { t.Fatal("did not expect an error") } @@ -1431,7 +1436,6 @@ func TestUnmarshalLocalDate(t *testing.T) { var obj dateStruct err := toml.Unmarshal([]byte(doc), &obj) - if err != nil { t.Fatal(err) } @@ -1457,7 +1461,6 @@ func TestUnmarshalLocalDate(t *testing.T) { var obj dateStruct err := toml.Unmarshal([]byte(doc), &obj) - if err != nil { t.Fatal(err) } @@ -1495,7 +1498,8 @@ func TestUnmarshalLocalDateTime(t *testing.T) { Second: 0, Nanosecond: 0, }, - }}, + }, + }, { name: "with nanoseconds", in: "1979-05-27T00:32:00.999999", @@ -1526,7 +1530,6 @@ func TestUnmarshalLocalDateTime(t *testing.T) { var obj dateStruct err := toml.Unmarshal([]byte(doc), &obj) - if err != nil { t.Fatal(err) } @@ -1544,7 +1547,6 @@ func TestUnmarshalLocalDateTime(t *testing.T) { var obj dateStruct err := toml.Unmarshal([]byte(doc), &obj) - if err != nil { t.Fatal(err) } @@ -1613,7 +1615,6 @@ func TestUnmarshalLocalTime(t *testing.T) { var obj dateStruct err := toml.Unmarshal([]byte(doc), &obj) - if err != nil { t.Fatal(err) } @@ -2283,8 +2284,7 @@ func (d *durationString) UnmarshalTOML(v interface{}) error { return nil } -type config437Error struct { -} +type config437Error struct{} func (e *config437Error) UnmarshalTOML(v interface{}) error { return errors.New("expected") diff --git a/internal/tracker/seen.go b/internal/tracker/seen.go index 4b5d3924..0f6bd01b 100644 --- a/internal/tracker/seen.go +++ b/internal/tracker/seen.go @@ -106,7 +106,7 @@ func (s *SeenTracker) create(parentIdx int, name []byte, kind keyKind, explicit // that have been seen in previous calls, and validates that types are consistent. func (s *SeenTracker) CheckExpression(node ast.Node) error { if s.entries == nil { - //s.entries = make([]entry, 0, 8) + // s.entries = make([]entry, 0, 8) // Skip ID = 0 to remove the confusion between nodes whose parent has // id 0 and root nodes (parent id is 0 because it's the zero value). s.nextID = 1 @@ -134,7 +134,7 @@ func (s *SeenTracker) checkTable(node ast.Node) error { // it in a function requires to copy the iterator, or allocate it to the // heap, which is not cheap. for it.Next() { - if !it.Node().Next().Valid() { + if it.IsLast() { break } @@ -175,7 +175,7 @@ func (s *SeenTracker) checkArrayTable(node ast.Node) error { parentIdx := -1 for it.Next() { - if !it.Node().Next().Valid() { + if it.IsLast() { break } diff --git a/localtime_test.go b/localtime_test.go index 67415041..646a3dbc 100644 --- a/localtime_test.go +++ b/localtime_test.go @@ -26,7 +26,6 @@ func cmpEqual(x, y interface{}) bool { } func TestDates(t *testing.T) { - t.Parallel() for _, test := range []struct { date LocalDate @@ -64,7 +63,6 @@ func TestDates(t *testing.T) { } func TestDateIsValid(t *testing.T) { - t.Parallel() for _, test := range []struct { date LocalDate @@ -91,7 +89,6 @@ func TestDateIsValid(t *testing.T) { } func TestParseDate(t *testing.T) { - t.Parallel() var emptyDate LocalDate @@ -118,7 +115,6 @@ func TestParseDate(t *testing.T) { } func TestDateArithmetic(t *testing.T) { - t.Parallel() for _, test := range []struct { desc string @@ -180,7 +176,6 @@ func TestDateArithmetic(t *testing.T) { } func TestDateBefore(t *testing.T) { - t.Parallel() for _, test := range []struct { d1, d2 LocalDate @@ -198,7 +193,6 @@ func TestDateBefore(t *testing.T) { } func TestDateAfter(t *testing.T) { - t.Parallel() for _, test := range []struct { d1, d2 LocalDate @@ -215,7 +209,6 @@ func TestDateAfter(t *testing.T) { } func TestTimeToString(t *testing.T) { - t.Parallel() for _, test := range []struct { str string @@ -249,7 +242,6 @@ func TestTimeToString(t *testing.T) { } func TestTimeOf(t *testing.T) { - t.Parallel() for _, test := range []struct { time time.Time @@ -265,7 +257,6 @@ func TestTimeOf(t *testing.T) { } func TestTimeIsValid(t *testing.T) { - t.Parallel() for _, test := range []struct { time LocalTime @@ -291,7 +282,6 @@ func TestTimeIsValid(t *testing.T) { } func TestDateTimeToString(t *testing.T) { - t.Parallel() for _, test := range []struct { str string @@ -323,7 +313,6 @@ func TestDateTimeToString(t *testing.T) { } func TestParseDateTimeErrors(t *testing.T) { - t.Parallel() for _, str := range []string{ "", @@ -339,7 +328,6 @@ func TestParseDateTimeErrors(t *testing.T) { } func TestDateTimeOf(t *testing.T) { - t.Parallel() for _, test := range []struct { time time.Time @@ -361,7 +349,6 @@ func TestDateTimeOf(t *testing.T) { } func TestDateTimeIsValid(t *testing.T) { - t.Parallel() // No need to be exhaustive here; it's just LocalDate.IsValid && LocalTime.IsValid. for _, test := range []struct { @@ -380,7 +367,6 @@ func TestDateTimeIsValid(t *testing.T) { } func TestDateTimeIn(t *testing.T) { - t.Parallel() dt := LocalDateTime{LocalDate{2016, 1, 2}, LocalTime{3, 4, 5, 6}} @@ -391,7 +377,6 @@ func TestDateTimeIn(t *testing.T) { } func TestDateTimeBefore(t *testing.T) { - t.Parallel() d1 := LocalDate{2016, 12, 31} d2 := LocalDate{2017, 1, 1} @@ -414,7 +399,6 @@ func TestDateTimeBefore(t *testing.T) { } func TestDateTimeAfter(t *testing.T) { - t.Parallel() d1 := LocalDate{2016, 12, 31} d2 := LocalDate{2017, 1, 1} @@ -437,7 +421,6 @@ func TestDateTimeAfter(t *testing.T) { } func TestMarshalJSON(t *testing.T) { - t.Parallel() for _, test := range []struct { value interface{} @@ -459,7 +442,6 @@ func TestMarshalJSON(t *testing.T) { } func TestUnmarshalJSON(t *testing.T) { - t.Parallel() var ( d LocalDate diff --git a/marshaler.go b/marshaler.go index ce9972aa..0d8ed9c5 100644 --- a/marshaler.go +++ b/marshaler.go @@ -640,8 +640,6 @@ func (enc *Encoder) encodeTableInline(b []byte, ctx encoderCtx, t table) ([]byte return b, nil } -var textMarshalerType = reflect.TypeOf(new(encoding.TextMarshaler)).Elem() - func willConvertToTable(ctx encoderCtx, v reflect.Value) bool { if v.Type() == timeType || v.Type().Implements(textMarshalerType) { return false diff --git a/marshaler_test.go b/marshaler_test.go index 5c946c32..333c93c1 100644 --- a/marshaler_test.go +++ b/marshaler_test.go @@ -14,8 +14,6 @@ import ( //nolint:funlen func TestMarshal(t *testing.T) { - t.Parallel() - someInt := 42 type structInline struct { @@ -516,8 +514,6 @@ K = 42`, for _, e := range examples { e := e t.Run(e.desc, func(t *testing.T) { - t.Parallel() - b, err := toml.Marshal(e.v) if e.err { require.Error(t, err) @@ -609,8 +605,6 @@ func equalStringsIgnoreNewlines(t *testing.T, expected string, actual string) { //nolint:funlen func TestMarshalIndentTables(t *testing.T) { - t.Parallel() - examples := []struct { desc string v interface{} @@ -661,8 +655,6 @@ root = 'value0' for _, e := range examples { e := e t.Run(e.desc, func(t *testing.T) { - t.Parallel() - var buf strings.Builder enc := toml.NewEncoder(&buf) enc.SetIndentTables(true) @@ -685,24 +677,18 @@ func (c *customTextMarshaler) MarshalText() ([]byte, error) { } func TestMarshalTextMarshaler_NoRoot(t *testing.T) { - t.Parallel() - c := customTextMarshaler{} _, err := toml.Marshal(&c) require.Error(t, err) } func TestMarshalTextMarshaler_Error(t *testing.T) { - t.Parallel() - m := map[string]interface{}{"a": &customTextMarshaler{value: 1}} _, err := toml.Marshal(m) require.Error(t, err) } func TestMarshalTextMarshaler_ErrorInline(t *testing.T) { - t.Parallel() - type s struct { A map[string]interface{} `inline:"true"` } @@ -716,8 +702,6 @@ func TestMarshalTextMarshaler_ErrorInline(t *testing.T) { } func TestMarshalTextMarshaler(t *testing.T) { - t.Parallel() - m := map[string]interface{}{"a": &customTextMarshaler{value: 2}} r, err := toml.Marshal(m) require.NoError(t, err) @@ -731,7 +715,6 @@ func (b *brokenWriter) Write([]byte) (int, error) { } func TestEncodeToBrokenWriter(t *testing.T) { - t.Parallel() w := brokenWriter{} enc := toml.NewEncoder(&w) err := enc.Encode(map[string]string{"hello": "world"}) @@ -739,7 +722,6 @@ func TestEncodeToBrokenWriter(t *testing.T) { } func TestEncoderSetIndentSymbol(t *testing.T) { - t.Parallel() var w strings.Builder enc := toml.NewEncoder(&w) enc.SetIndentTables(true) @@ -753,8 +735,6 @@ func TestEncoderSetIndentSymbol(t *testing.T) { } func TestIssue436(t *testing.T) { - t.Parallel() - data := []byte(`{"a": [ { "b": { "c": "d" } } ]}`) var v interface{} @@ -774,8 +754,6 @@ c = 'd' } func TestIssue424(t *testing.T) { - t.Parallel() - type Message1 struct { Text string } diff --git a/parser_test.go b/parser_test.go index bb3d3bd9..fdb4f27e 100644 --- a/parser_test.go +++ b/parser_test.go @@ -9,7 +9,6 @@ import ( //nolint:funlen func TestParser_AST_Numbers(t *testing.T) { - t.Parallel() examples := []struct { desc string @@ -137,7 +136,7 @@ func TestParser_AST_Numbers(t *testing.T) { for _, e := range examples { e := e t.Run(e.desc, func(t *testing.T) { - t.Parallel() + p := parser{} p.Reset([]byte(`A = ` + e.input)) p.NextExpression() @@ -200,7 +199,6 @@ func compareIterator(t *testing.T, expected []astNode, actual ast.Iterator) { //nolint:funlen func TestParser_AST(t *testing.T) { - t.Parallel() examples := []struct { desc string @@ -340,7 +338,7 @@ func TestParser_AST(t *testing.T) { for _, e := range examples { e := e t.Run(e.desc, func(t *testing.T) { - t.Parallel() + p := parser{} p.Reset([]byte(e.input)) p.NextExpression() diff --git a/strict.go b/strict.go index 2b2e7d65..ca482c4e 100644 --- a/strict.go +++ b/strict.go @@ -3,6 +3,7 @@ package toml import ( "github.com/pelletier/go-toml/v2/internal/ast" "github.com/pelletier/go-toml/v2/internal/tracker" + "github.com/pelletier/go-toml/v2/internal/unsafe" ) type strict struct { @@ -86,3 +87,21 @@ func (s *strict) Error(doc []byte) error { return err } + +func keyLocation(node ast.Node) []byte { + k := node.Key() + + hasOne := k.Next() + if !hasOne { + panic("should not be called with empty key") + } + + start := k.Node().Data + end := k.Node().Data + + for k.Next() { + end = k.Node().Data + } + + return unsafe.BytesRange(start, end) +} diff --git a/targets.go b/targets.go deleted file mode 100644 index 370892f0..00000000 --- a/targets.go +++ /dev/null @@ -1,536 +0,0 @@ -package toml - -import ( - "fmt" - "math" - "reflect" - "strings" - "sync" -) - -type target interface { - // Dereferences the target. - get() reflect.Value - - // Store a string at the target. - setString(v string) - - // Store a boolean at the target - setBool(v bool) - - // Store an int64 at the target - setInt64(v int64) - - // Store a float64 at the target - setFloat64(v float64) - - // Stores any value at the target - set(v reflect.Value) -} - -// valueTarget just contains a reflect.Value that can be set. -// It is used for struct fields. -type valueTarget reflect.Value - -func (t valueTarget) get() reflect.Value { - return reflect.Value(t) -} - -func (t valueTarget) set(v reflect.Value) { - reflect.Value(t).Set(v) -} - -func (t valueTarget) setString(v string) { - t.get().SetString(v) -} - -func (t valueTarget) setBool(v bool) { - t.get().SetBool(v) -} - -func (t valueTarget) setInt64(v int64) { - t.get().SetInt(v) -} - -func (t valueTarget) setFloat64(v float64) { - t.get().SetFloat(v) -} - -// interfaceTarget wraps an other target to dereference on get. -type interfaceTarget struct { - x target -} - -func (t interfaceTarget) get() reflect.Value { - return t.x.get().Elem() -} - -func (t interfaceTarget) set(v reflect.Value) { - t.x.set(v) -} - -func (t interfaceTarget) setString(v string) { - panic("interface targets should always go through set") -} - -func (t interfaceTarget) setBool(v bool) { - panic("interface targets should always go through set") -} - -func (t interfaceTarget) setInt64(v int64) { - panic("interface targets should always go through set") -} - -func (t interfaceTarget) setFloat64(v float64) { - panic("interface targets should always go through set") -} - -// mapTarget targets a specific key of a map. -type mapTarget struct { - v reflect.Value - k reflect.Value -} - -func (t mapTarget) get() reflect.Value { - return t.v.MapIndex(t.k) -} - -func (t mapTarget) set(v reflect.Value) { - t.v.SetMapIndex(t.k, v) -} - -func (t mapTarget) setString(v string) { - t.set(reflect.ValueOf(v)) -} - -func (t mapTarget) setBool(v bool) { - t.set(reflect.ValueOf(v)) -} - -func (t mapTarget) setInt64(v int64) { - t.set(reflect.ValueOf(v)) -} - -func (t mapTarget) setFloat64(v float64) { - t.set(reflect.ValueOf(v)) -} - -// makes sure that the value pointed at by t is indexable (Slice, Array), or -// dereferences to an indexable (Ptr, Interface). -func ensureValueIndexable(t target) error { - f := t.get() - - switch f.Type().Kind() { - case reflect.Slice: - if f.IsNil() { - t.set(reflect.MakeSlice(f.Type(), 0, 0)) - return nil - } - case reflect.Interface: - if f.IsNil() || f.Elem().Type() != sliceInterfaceType { - t.set(reflect.MakeSlice(sliceInterfaceType, 0, 0)) - return nil - } - case reflect.Ptr: - panic("pointer should have already been dereferenced") - case reflect.Array: - // arrays are always initialized. - default: - return fmt.Errorf("toml: cannot store array in a %s", f.Kind()) - } - - return nil -} - -var ( - sliceInterfaceType = reflect.TypeOf([]interface{}{}) - mapStringInterfaceType = reflect.TypeOf(map[string]interface{}{}) -) - -func ensureMapIfInterface(x target) { - v := x.get() - - if v.Kind() == reflect.Interface && v.IsNil() { - newElement := reflect.MakeMap(mapStringInterfaceType) - - x.set(newElement) - } -} - -func setString(t target, v string) error { - f := t.get() - - switch f.Kind() { - case reflect.String: - t.setString(v) - case reflect.Interface: - t.set(reflect.ValueOf(v)) - default: - return fmt.Errorf("toml: cannot assign string to a %s", f.Kind()) - } - - return nil -} - -func setBool(t target, v bool) error { - f := t.get() - - switch f.Kind() { - case reflect.Bool: - t.setBool(v) - case reflect.Interface: - t.set(reflect.ValueOf(v)) - default: - return fmt.Errorf("toml: cannot assign boolean to a %s", f.Kind()) - } - - return nil -} - -const ( - maxInt = int64(^uint(0) >> 1) - minInt = -maxInt - 1 -) - -//nolint:funlen,gocognit,cyclop -func setInt64(t target, v int64) error { - f := t.get() - - switch f.Kind() { - case reflect.Int64: - t.setInt64(v) - case reflect.Int32: - if v < math.MinInt32 || v > math.MaxInt32 { - return fmt.Errorf("toml: number %d does not fit in an int32", v) - } - - t.set(reflect.ValueOf(int32(v))) - return nil - case reflect.Int16: - if v < math.MinInt16 || v > math.MaxInt16 { - return fmt.Errorf("toml: number %d does not fit in an int16", v) - } - - t.set(reflect.ValueOf(int16(v))) - case reflect.Int8: - if v < math.MinInt8 || v > math.MaxInt8 { - return fmt.Errorf("toml: number %d does not fit in an int8", v) - } - - t.set(reflect.ValueOf(int8(v))) - case reflect.Int: - if v < minInt || v > maxInt { - return fmt.Errorf("toml: number %d does not fit in an int", v) - } - - t.set(reflect.ValueOf(int(v))) - case reflect.Uint64: - if v < 0 { - return fmt.Errorf("toml: negative number %d does not fit in an uint64", v) - } - - t.set(reflect.ValueOf(uint64(v))) - case reflect.Uint32: - if v < 0 || v > math.MaxUint32 { - return fmt.Errorf("toml: negative number %d does not fit in an uint32", v) - } - - t.set(reflect.ValueOf(uint32(v))) - case reflect.Uint16: - if v < 0 || v > math.MaxUint16 { - return fmt.Errorf("toml: negative number %d does not fit in an uint16", v) - } - - t.set(reflect.ValueOf(uint16(v))) - case reflect.Uint8: - if v < 0 || v > math.MaxUint8 { - return fmt.Errorf("toml: negative number %d does not fit in an uint8", v) - } - - t.set(reflect.ValueOf(uint8(v))) - case reflect.Uint: - if v < 0 { - return fmt.Errorf("toml: negative number %d does not fit in an uint", v) - } - - t.set(reflect.ValueOf(uint(v))) - case reflect.Interface: - t.set(reflect.ValueOf(v)) - default: - return fmt.Errorf("toml: integer cannot be assigned to %s", f.Kind()) - } - - return nil -} - -func setFloat64(t target, v float64) error { - f := t.get() - - switch f.Kind() { - case reflect.Float64: - t.setFloat64(v) - case reflect.Float32: - if v > math.MaxFloat32 { - return fmt.Errorf("toml: number %f does not fit in a float32", v) - } - - t.set(reflect.ValueOf(float32(v))) - case reflect.Interface: - t.set(reflect.ValueOf(v)) - default: - return fmt.Errorf("toml: float cannot be assigned to %s", f.Kind()) - } - - return nil -} - -// Returns the element at idx of the value pointed at by target, or an error if -// t does not point to an indexable. -// If the target points to an Array and idx is out of bounds, it returns -// (nil, nil) as this is not a fatal error (the unmarshaler will skip). -func elementAt(t target, idx int) target { - f := t.get() - - switch f.Kind() { - case reflect.Slice: - //nolint:godox - // TODO: use the idx function argument and avoid alloc if possible. - idx := f.Len() - - t.set(reflect.Append(f, reflect.New(f.Type().Elem()).Elem())) - - return valueTarget(t.get().Index(idx)) - case reflect.Array: - if idx >= f.Len() { - return nil - } - - return valueTarget(f.Index(idx)) - case reflect.Interface: - // This function is called after ensureValueIndexable, so it's - // guaranteed that f contains an initialized slice. - ifaceElem := f.Elem() - idx := ifaceElem.Len() - newElem := reflect.New(ifaceElem.Type().Elem()).Elem() - newSlice := reflect.Append(ifaceElem, newElem) - - t.set(newSlice) - - return valueTarget(t.get().Elem().Index(idx)) - default: - // Why ensureValueIndexable let it go through? - panic(fmt.Errorf("elementAt received unhandled value type: %s", f.Kind())) - } -} - -func (d *decoder) scopeTableTarget(shouldAppend bool, t target, name string) (target, bool, error) { - x := t.get() - - switch x.Kind() { - // Kinds that need to recurse - case reflect.Interface: - t := scopeInterface(shouldAppend, t) - return d.scopeTableTarget(shouldAppend, t, name) - case reflect.Ptr: - t := scopePtr(t) - return d.scopeTableTarget(shouldAppend, t, name) - case reflect.Slice: - t := scopeSlice(shouldAppend, t) - shouldAppend = false - return d.scopeTableTarget(shouldAppend, t, name) - case reflect.Array: - t, err := d.scopeArray(shouldAppend, t) - if err != nil { - return t, false, err - } - shouldAppend = false - - return d.scopeTableTarget(shouldAppend, t, name) - - // Terminal kinds - case reflect.Struct: - return scopeStruct(x, name) - case reflect.Map: - if x.IsNil() { - t.set(reflect.MakeMap(x.Type())) - x = t.get() - } - - return scopeMap(x, name) - default: - panic(fmt.Sprintf("can't scope on a %s", x.Kind())) - } -} - -func scopeInterface(shouldAppend bool, t target) target { - initInterface(shouldAppend, t) - return interfaceTarget{t} -} - -func scopePtr(t target) target { - initPtr(t) - return valueTarget(t.get().Elem()) -} - -func initPtr(t target) { - x := t.get() - if !x.IsNil() { - return - } - - t.set(reflect.New(x.Type().Elem())) -} - -// initInterface makes sure that the interface pointed at by the target is not -// nil. -// Returns the target to the initialized value of the target. -func initInterface(shouldAppend bool, t target) { - x := t.get() - - if x.Kind() != reflect.Interface { - panic("this should only be called on interfaces") - } - - if !x.IsNil() && (x.Elem().Type() == sliceInterfaceType || x.Elem().Type() == mapStringInterfaceType) { - return - } - - var newElement reflect.Value - if shouldAppend { - newElement = reflect.MakeSlice(sliceInterfaceType, 0, 0) - } else { - newElement = reflect.MakeMap(mapStringInterfaceType) - } - - t.set(newElement) -} - -func scopeSlice(shouldAppend bool, t target) target { - v := t.get() - - if shouldAppend { - newElem := reflect.New(v.Type().Elem()) - newSlice := reflect.Append(v, newElem.Elem()) - - t.set(newSlice) - - v = t.get() - } - - return valueTarget(v.Index(v.Len() - 1)) -} - -func (d *decoder) scopeArray(shouldAppend bool, t target) (target, error) { - v := t.get() - - idx := d.arrayIndex(shouldAppend, v) - - if idx >= v.Len() { - return nil, fmt.Errorf("toml: impossible to insert element beyond array's size: %d", v.Len()) - } - - return valueTarget(v.Index(idx)), nil -} - -func scopeMap(v reflect.Value, name string) (target, bool, error) { - k := reflect.ValueOf(name) - - keyType := v.Type().Key() - if !k.Type().AssignableTo(keyType) { - if !k.Type().ConvertibleTo(keyType) { - return nil, false, fmt.Errorf("toml: cannot convert map key of type %s to expected type %s", k.Type(), keyType) - } - - k = k.Convert(keyType) - } - - if !v.MapIndex(k).IsValid() { - newElem := reflect.New(v.Type().Elem()) - v.SetMapIndex(k, newElem.Elem()) - } - - return mapTarget{ - v: v, - k: k, - }, true, nil -} - -type fieldPathsMap = map[string][]int - -type fieldPathsCache struct { - m map[reflect.Type]fieldPathsMap - l sync.RWMutex -} - -func (c *fieldPathsCache) get(t reflect.Type) (fieldPathsMap, bool) { - c.l.RLock() - paths, ok := c.m[t] - c.l.RUnlock() - - return paths, ok -} - -func (c *fieldPathsCache) set(t reflect.Type, m fieldPathsMap) { - c.l.Lock() - c.m[t] = m - c.l.Unlock() -} - -var globalFieldPathsCache = fieldPathsCache{ - m: map[reflect.Type]fieldPathsMap{}, - l: sync.RWMutex{}, -} - -func scopeStruct(v reflect.Value, name string) (target, bool, error) { - //nolint:godox - // TODO: cache this, and reduce allocations - fieldPaths, ok := globalFieldPathsCache.get(v.Type()) - if !ok { - fieldPaths = map[string][]int{} - - path := make([]int, 0, 16) - - var walk func(reflect.Value) - walk = func(v reflect.Value) { - t := v.Type() - for i := 0; i < t.NumField(); i++ { - l := len(path) - path = append(path, i) - f := t.Field(i) - - if f.Anonymous { - walk(v.Field(i)) - } else if f.PkgPath == "" { - // only consider exported fields - fieldName, ok := f.Tag.Lookup("toml") - if !ok { - fieldName = f.Name - } - - pathCopy := make([]int, len(path)) - copy(pathCopy, path) - - fieldPaths[fieldName] = pathCopy - // extra copy for the case-insensitive match - fieldPaths[strings.ToLower(fieldName)] = pathCopy - } - path = path[:l] - } - } - - walk(v) - - globalFieldPathsCache.set(v.Type(), fieldPaths) - } - - path, ok := fieldPaths[name] - if !ok { - path, ok = fieldPaths[strings.ToLower(name)] - } - - if !ok { - return nil, false, nil - } - - return valueTarget(v.FieldByIndex(path)), true, nil -} diff --git a/targets_test.go b/targets_test.go deleted file mode 100644 index c895ad5a..00000000 --- a/targets_test.go +++ /dev/null @@ -1,207 +0,0 @@ -package toml - -import ( - "reflect" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestStructTarget_Ensure(t *testing.T) { - t.Parallel() - - examples := []struct { - desc string - input reflect.Value - name string - test func(v reflect.Value, err error) - }{ - { - desc: "handle a nil slice of string", - input: reflect.ValueOf(&struct{ A []string }{}).Elem(), - name: "A", - test: func(v reflect.Value, err error) { - assert.NoError(t, err) - assert.False(t, v.IsNil()) - }, - }, - { - desc: "handle an existing slice of string", - input: reflect.ValueOf(&struct{ A []string }{A: []string{"foo"}}).Elem(), - name: "A", - test: func(v reflect.Value, err error) { - assert.NoError(t, err) - require.False(t, v.IsNil()) - - s, ok := v.Interface().([]string) - if !ok { - t.Errorf("interface %v should be castable into []string", s) - return - } - - assert.Equal(t, []string{"foo"}, s) - }, - }, - } - - for _, e := range examples { - e := e - t.Run(e.desc, func(t *testing.T) { - t.Parallel() - - d := decoder{} - target, _, err := d.scopeTableTarget(false, valueTarget(e.input), e.name) - require.NoError(t, err) - err = ensureValueIndexable(target) - v := target.get() - e.test(v, err) - }) - } -} - -func TestStructTarget_SetString(t *testing.T) { - t.Parallel() - - str := "value" - - examples := []struct { - desc string - input reflect.Value - name string - test func(v reflect.Value, err error) - }{ - { - desc: "sets a string", - input: reflect.ValueOf(&struct{ A string }{}).Elem(), - name: "A", - test: func(v reflect.Value, err error) { - assert.NoError(t, err) - assert.Equal(t, str, v.String()) - }, - }, - { - desc: "fails on a float", - input: reflect.ValueOf(&struct{ A float64 }{}).Elem(), - name: "A", - test: func(v reflect.Value, err error) { - assert.Error(t, err) - }, - }, - { - desc: "fails on a slice", - input: reflect.ValueOf(&struct{ A []string }{}).Elem(), - name: "A", - test: func(v reflect.Value, err error) { - assert.Error(t, err) - }, - }, - } - - for _, e := range examples { - e := e - t.Run(e.desc, func(t *testing.T) { - t.Parallel() - - d := decoder{} - target, _, err := d.scopeTableTarget(false, valueTarget(e.input), e.name) - require.NoError(t, err) - err = setString(target, str) - v := target.get() - e.test(v, err) - }) - } -} - -func TestPushNew(t *testing.T) { - t.Parallel() - - t.Run("slice of strings", func(t *testing.T) { - t.Parallel() - - type Doc struct { - A []string - } - d := Doc{} - - dec := decoder{} - x, _, err := dec.scopeTableTarget(false, valueTarget(reflect.ValueOf(&d).Elem()), "A") - require.NoError(t, err) - - n := elementAt(x, 0) - n.setString("hello") - require.Equal(t, []string{"hello"}, d.A) - - n = elementAt(x, 1) - n.setString("world") - require.Equal(t, []string{"hello", "world"}, d.A) - }) - - t.Run("slice of interfaces", func(t *testing.T) { - t.Parallel() - - type Doc struct { - A []interface{} - } - d := Doc{} - - dec := decoder{} - x, _, err := dec.scopeTableTarget(false, valueTarget(reflect.ValueOf(&d).Elem()), "A") - require.NoError(t, err) - - n := elementAt(x, 0) - require.NoError(t, setString(n, "hello")) - require.Equal(t, []interface{}{"hello"}, d.A) - - n = elementAt(x, 1) - require.NoError(t, setString(n, "world")) - require.Equal(t, []interface{}{"hello", "world"}, d.A) - }) -} - -func TestScope_Struct(t *testing.T) { - t.Parallel() - - examples := []struct { - desc string - input reflect.Value - name string - err bool - found bool - idx []int - }{ - { - desc: "simple field", - input: reflect.ValueOf(&struct{ A string }{}).Elem(), - name: "A", - idx: []int{0}, - found: true, - }, - { - desc: "fails not-exported field", - input: reflect.ValueOf(&struct{ a string }{}).Elem(), - name: "a", - err: false, - found: false, - }, - } - - for _, e := range examples { - e := e - t.Run(e.desc, func(t *testing.T) { - t.Parallel() - - dec := decoder{} - x, found, err := dec.scopeTableTarget(false, valueTarget(e.input), e.name) - assert.Equal(t, e.found, found) - if e.err { - assert.Error(t, err) - } - if found { - x2, ok := x.(valueTarget) - require.True(t, ok) - x2.get() - } - }) - } -} diff --git a/toml_testgen_test.go b/toml_testgen_test.go index b0d82cda..1be0d14f 100644 --- a/toml_testgen_test.go +++ b/toml_testgen_test.go @@ -6,35 +6,30 @@ import ( ) func TestInvalidDatetimeMalformedNoLeads(t *testing.T) { - t.Parallel() input := `no-leads = 1987-7-05T17:45:00Z` testgenInvalid(t, input) } func TestInvalidDatetimeMalformedNoSecs(t *testing.T) { - t.Parallel() input := `no-secs = 1987-07-05T17:45Z` testgenInvalid(t, input) } func TestInvalidDatetimeMalformedNoT(t *testing.T) { - t.Parallel() input := `no-t = 1987-07-0517:45:00Z` testgenInvalid(t, input) } func TestInvalidDatetimeMalformedWithMilli(t *testing.T) { - t.Parallel() input := `with-milli = 1987-07-5T17:45:00.12Z` testgenInvalid(t, input) } func TestInvalidDuplicateKeyTable(t *testing.T) { - t.Parallel() input := `[fruit] type = "apple" @@ -45,7 +40,6 @@ apple = "yes"` } func TestInvalidDuplicateKeys(t *testing.T) { - t.Parallel() input := `dupe = false dupe = true` @@ -53,7 +47,6 @@ dupe = true` } func TestInvalidDuplicateTables(t *testing.T) { - t.Parallel() input := `[a] [a]` @@ -61,21 +54,18 @@ func TestInvalidDuplicateTables(t *testing.T) { } func TestInvalidEmptyImplicitTable(t *testing.T) { - t.Parallel() input := `[naughty..naughty]` testgenInvalid(t, input) } func TestInvalidEmptyTable(t *testing.T) { - t.Parallel() input := `[]` testgenInvalid(t, input) } func TestInvalidFloatNoLeadingZero(t *testing.T) { - t.Parallel() input := `answer = .12345 neganswer = -.12345` @@ -83,7 +73,6 @@ neganswer = -.12345` } func TestInvalidFloatNoTrailingDigits(t *testing.T) { - t.Parallel() input := `answer = 1. neganswer = -1.` @@ -91,21 +80,18 @@ neganswer = -1.` } func TestInvalidKeyEmpty(t *testing.T) { - t.Parallel() input := ` = 1` testgenInvalid(t, input) } func TestInvalidKeyHash(t *testing.T) { - t.Parallel() input := `a# = 1` testgenInvalid(t, input) } func TestInvalidKeyNewline(t *testing.T) { - t.Parallel() input := `a = 1` @@ -113,28 +99,24 @@ func TestInvalidKeyNewline(t *testing.T) { } func TestInvalidKeyOpenBracket(t *testing.T) { - t.Parallel() input := `[abc = 1` testgenInvalid(t, input) } func TestInvalidKeySingleOpenBracket(t *testing.T) { - t.Parallel() input := `[` testgenInvalid(t, input) } func TestInvalidKeySpace(t *testing.T) { - t.Parallel() input := `a b = 1` testgenInvalid(t, input) } func TestInvalidKeyStartBracket(t *testing.T) { - t.Parallel() input := `[a] [xyz = 5 @@ -143,42 +125,36 @@ func TestInvalidKeyStartBracket(t *testing.T) { } func TestInvalidKeyTwoEquals(t *testing.T) { - t.Parallel() input := `key= = 1` testgenInvalid(t, input) } func TestInvalidStringBadByteEscape(t *testing.T) { - t.Parallel() input := `naughty = "\xAg"` testgenInvalid(t, input) } func TestInvalidStringBadEscape(t *testing.T) { - t.Parallel() input := `invalid-escape = "This string has a bad \a escape character."` testgenInvalid(t, input) } func TestInvalidStringByteEscapes(t *testing.T) { - t.Parallel() input := `answer = "\x33"` testgenInvalid(t, input) } func TestInvalidStringNoClose(t *testing.T) { - t.Parallel() input := `no-ending-quote = "One time, at band camp` testgenInvalid(t, input) } func TestInvalidTableArrayImplicit(t *testing.T) { - t.Parallel() input := "# This test is a bit tricky. It should fail because the first use of\n" + "# `[[albums.songs]]` without first declaring `albums` implies that `albums`\n" + @@ -198,7 +174,6 @@ func TestInvalidTableArrayImplicit(t *testing.T) { } func TestInvalidTableArrayMalformedBracket(t *testing.T) { - t.Parallel() input := `[[albums] name = "Born to Run"` @@ -206,7 +181,6 @@ name = "Born to Run"` } func TestInvalidTableArrayMalformedEmpty(t *testing.T) { - t.Parallel() input := `[[]] name = "Born to Run"` @@ -214,14 +188,12 @@ name = "Born to Run"` } func TestInvalidTableEmpty(t *testing.T) { - t.Parallel() input := `[]` testgenInvalid(t, input) } func TestInvalidTableNestedBracketsClose(t *testing.T) { - t.Parallel() input := `[a]b] zyx = 42` @@ -229,7 +201,6 @@ zyx = 42` } func TestInvalidTableNestedBracketsOpen(t *testing.T) { - t.Parallel() input := `[a[b] zyx = 42` @@ -237,14 +208,12 @@ zyx = 42` } func TestInvalidTableWhitespace(t *testing.T) { - t.Parallel() input := `[invalid key]` testgenInvalid(t, input) } func TestInvalidTableWithPound(t *testing.T) { - t.Parallel() input := `[key#group] answer = 42` @@ -252,7 +221,6 @@ answer = 42` } func TestInvalidTextAfterArrayEntries(t *testing.T) { - t.Parallel() input := `array = [ "Is there life after an array separator?", No @@ -262,28 +230,24 @@ func TestInvalidTextAfterArrayEntries(t *testing.T) { } func TestInvalidTextAfterInteger(t *testing.T) { - t.Parallel() input := `answer = 42 the ultimate answer?` testgenInvalid(t, input) } func TestInvalidTextAfterString(t *testing.T) { - t.Parallel() input := `string = "Is there life after strings?" No.` testgenInvalid(t, input) } func TestInvalidTextAfterTable(t *testing.T) { - t.Parallel() input := `[error] this shouldn't be here` testgenInvalid(t, input) } func TestInvalidTextBeforeArraySeparator(t *testing.T) { - t.Parallel() input := `array = [ "Is there life before an array separator?" No, @@ -293,7 +257,6 @@ func TestInvalidTextBeforeArraySeparator(t *testing.T) { } func TestInvalidTextInArray(t *testing.T) { - t.Parallel() input := `array = [ "Entry 1", @@ -304,7 +267,6 @@ func TestInvalidTextInArray(t *testing.T) { } func TestValidArrayEmpty(t *testing.T) { - t.Parallel() input := `thevoid = [[[[[]]]]]` jsonRef := `{ @@ -322,7 +284,6 @@ func TestValidArrayEmpty(t *testing.T) { } func TestValidArrayNospaces(t *testing.T) { - t.Parallel() input := `ints = [1,2,3]` jsonRef := `{ @@ -339,7 +300,6 @@ func TestValidArrayNospaces(t *testing.T) { } func TestValidArraysHetergeneous(t *testing.T) { - t.Parallel() input := `mixed = [[1, 2], ["a", "b"], [1.1, 2.1]]` jsonRef := `{ @@ -365,7 +325,6 @@ func TestValidArraysHetergeneous(t *testing.T) { } func TestValidArraysNested(t *testing.T) { - t.Parallel() input := `nest = [["a"], ["b"]]` jsonRef := `{ @@ -385,7 +344,6 @@ func TestValidArraysNested(t *testing.T) { } func TestValidArrays(t *testing.T) { - t.Parallel() input := `ints = [1, 2, 3] floats = [1.1, 2.1, 3.1] @@ -433,7 +391,6 @@ dates = [ } func TestValidBool(t *testing.T) { - t.Parallel() input := `t = true f = false` @@ -445,7 +402,6 @@ f = false` } func TestValidCommentsEverywhere(t *testing.T) { - t.Parallel() input := `# Top comment. # Top comment. @@ -487,7 +443,6 @@ more = [ # Comment } func TestValidDatetime(t *testing.T) { - t.Parallel() input := `bestdayever = 1987-07-05T17:45:00Z` jsonRef := `{ @@ -497,7 +452,6 @@ func TestValidDatetime(t *testing.T) { } func TestValidEmpty(t *testing.T) { - t.Parallel() input := `` jsonRef := `{}` @@ -505,7 +459,6 @@ func TestValidEmpty(t *testing.T) { } func TestValidExample(t *testing.T) { - t.Parallel() input := `best-day-ever = 1987-07-05T17:45:00Z @@ -530,7 +483,6 @@ perfection = [6, 28, 496]` } func TestValidFloat(t *testing.T) { - t.Parallel() input := `pi = 3.14 negpi = -3.14` @@ -542,7 +494,6 @@ negpi = -3.14` } func TestValidImplicitAndExplicitAfter(t *testing.T) { - t.Parallel() input := `[a.b.c] answer = 42 @@ -563,7 +514,6 @@ better = 43` } func TestValidImplicitAndExplicitBefore(t *testing.T) { - t.Parallel() input := `[a] better = 43 @@ -584,7 +534,6 @@ answer = 42` } func TestValidImplicitGroups(t *testing.T) { - t.Parallel() input := `[a.b.c] answer = 42` @@ -601,7 +550,6 @@ answer = 42` } func TestValidInteger(t *testing.T) { - t.Parallel() input := `answer = 42 neganswer = -42` @@ -613,7 +561,6 @@ neganswer = -42` } func TestValidKeyEqualsNospace(t *testing.T) { - t.Parallel() input := `answer=42` jsonRef := `{ @@ -623,7 +570,6 @@ func TestValidKeyEqualsNospace(t *testing.T) { } func TestValidKeySpace(t *testing.T) { - t.Parallel() input := `"a b" = 1` jsonRef := `{ @@ -633,7 +579,6 @@ func TestValidKeySpace(t *testing.T) { } func TestValidKeySpecialChars(t *testing.T) { - t.Parallel() input := "\"~!@$^&*()_+-`1234567890[]|/?><.,;:'\" = 1\n" jsonRef := "{\n" + @@ -645,7 +590,6 @@ func TestValidKeySpecialChars(t *testing.T) { } func TestValidLongFloat(t *testing.T) { - t.Parallel() input := `longpi = 3.141592653589793 neglongpi = -3.141592653589793` @@ -657,7 +601,6 @@ neglongpi = -3.141592653589793` } func TestValidLongInteger(t *testing.T) { - t.Parallel() input := `answer = 9223372036854775807 neganswer = -9223372036854775808` @@ -669,7 +612,6 @@ neganswer = -9223372036854775808` } func TestValidMultilineString(t *testing.T) { - t.Parallel() input := `multiline_empty_one = """""" multiline_empty_two = """ @@ -728,7 +670,6 @@ equivalent_three = """\ } func TestValidRawMultilineString(t *testing.T) { - t.Parallel() input := `oneline = '''This string has a ' quote character.''' firstnl = ''' @@ -757,7 +698,6 @@ in it.'''` } func TestValidRawString(t *testing.T) { - t.Parallel() input := `backspace = 'This string has a \b backspace character.' tab = 'This string has a \t tab character.' @@ -800,7 +740,6 @@ backslash = 'This string has a \\ backslash character.'` } func TestValidStringEmpty(t *testing.T) { - t.Parallel() input := `answer = ""` jsonRef := `{ @@ -813,7 +752,6 @@ func TestValidStringEmpty(t *testing.T) { } func TestValidStringEscapes(t *testing.T) { - t.Parallel() input := `backspace = "This string has a \b backspace character." tab = "This string has a \t tab character." @@ -876,7 +814,6 @@ notunicode4 = "This string does not have a unicode \\\u0075 escape."` } func TestValidStringSimple(t *testing.T) { - t.Parallel() input := `answer = "You are not drinking enough whisky."` jsonRef := `{ @@ -889,7 +826,6 @@ func TestValidStringSimple(t *testing.T) { } func TestValidStringWithPound(t *testing.T) { - t.Parallel() input := `pound = "We see no # comments here." poundcomment = "But there are # some comments here." # Did I # mess you up?` @@ -904,7 +840,6 @@ poundcomment = "But there are # some comments here." # Did I # mess you up?` } func TestValidTableArrayImplicit(t *testing.T) { - t.Parallel() input := `[[albums.songs]] name = "Glory Days"` @@ -919,7 +854,6 @@ name = "Glory Days"` } func TestValidTableArrayMany(t *testing.T) { - t.Parallel() input := `[[people]] first_name = "Bruce" @@ -952,7 +886,6 @@ last_name = "Seger"` } func TestValidTableArrayNest(t *testing.T) { - t.Parallel() input := `[[albums]] name = "Born to Run" @@ -993,7 +926,6 @@ name = "Born in the USA" } func TestValidTableArrayOne(t *testing.T) { - t.Parallel() input := `[[people]] first_name = "Bruce" @@ -1010,7 +942,6 @@ last_name = "Springsteen"` } func TestValidTableEmpty(t *testing.T) { - t.Parallel() input := `[a]` jsonRef := `{ @@ -1020,7 +951,6 @@ func TestValidTableEmpty(t *testing.T) { } func TestValidTableSubEmpty(t *testing.T) { - t.Parallel() input := `[a] [a.b]` @@ -1031,7 +961,6 @@ func TestValidTableSubEmpty(t *testing.T) { } func TestValidTableWhitespace(t *testing.T) { - t.Parallel() input := `["valid key"]` jsonRef := `{ @@ -1041,7 +970,6 @@ func TestValidTableWhitespace(t *testing.T) { } func TestValidTableWithPound(t *testing.T) { - t.Parallel() input := `["key#group"] answer = 42` @@ -1054,7 +982,6 @@ answer = 42` } func TestValidUnicodeEscape(t *testing.T) { - t.Parallel() input := `answer4 = "\u03B4" answer8 = "\U000003B4"` @@ -1066,7 +993,6 @@ answer8 = "\U000003B4"` } func TestValidUnicodeLiteral(t *testing.T) { - t.Parallel() input := `answer = "δ"` jsonRef := `{ diff --git a/types.go b/types.go new file mode 100644 index 00000000..b5ec165f --- /dev/null +++ b/types.go @@ -0,0 +1,13 @@ +package toml + +import ( + "encoding" + "reflect" + "time" +) + +var timeType = reflect.TypeOf(time.Time{}) +var textMarshalerType = reflect.TypeOf(new(encoding.TextMarshaler)).Elem() +var textUnmarshalerType = reflect.TypeOf(new(encoding.TextUnmarshaler)).Elem() +var mapStringInterfaceType = reflect.TypeOf(map[string]interface{}{}) +var sliceInterfaceType = reflect.TypeOf([]interface{}{}) diff --git a/unmarshaler.go b/unmarshaler.go index fda94386..6dcb5c5c 100644 --- a/unmarshaler.go +++ b/unmarshaler.go @@ -6,12 +6,14 @@ import ( "fmt" "io" "io/ioutil" + "math" "reflect" + "strings" + "sync" "time" "github.com/pelletier/go-toml/v2/internal/ast" "github.com/pelletier/go-toml/v2/internal/tracker" - "github.com/pelletier/go-toml/v2/internal/unsafe" ) // Unmarshal deserializes a TOML document into a Go value. @@ -20,9 +22,9 @@ import ( func Unmarshal(data []byte, v interface{}) error { p := parser{} p.Reset(data) - d := decoder{} + d := decoder{p: &p} - return d.FromParser(&p, v) + return d.FromParser(v) } // Decoder reads and decode a TOML document from an input stream. @@ -93,16 +95,34 @@ func (d *Decoder) Decode(v interface{}) error { p := parser{} p.Reset(b) dec := decoder{ + p: &p, strict: strict{ Enabled: d.strict, }, } - return dec.FromParser(&p, v) + return dec.FromParser(v) } type decoder struct { + // Which parser instance in use for this decoding session. + // TODO: Think about removing later. + p *parser + + // Flag indicating that the current expression is stashed. + // If set to true, calling nextExpr will not actually pull a new expression + // but turn off the flag instead. + stashedExpr bool + + // Skip expressions until a table is found. This is set to true when a + // table could not be create (missing field in map), so all KV expressions + // need to be skipped. + skipUntilTable bool + // Tracks position in Go arrays. + // This is used when decoding [[array tables]] into Go arrays. Given array + // tables are separate TOML expression, we need to keep track of where we + // are at in the Go array, as we can't just introspect its size. arrayIndexes map[reflect.Value]int // Tracks keys that have been seen, with which type. @@ -112,6 +132,22 @@ type decoder struct { strict strict } +func (d *decoder) expr() ast.Node { + return d.p.Expression() +} + +func (d *decoder) nextExpr() bool { + if d.stashedExpr { + d.stashedExpr = false + return true + } + return d.p.NextExpression() +} + +func (d *decoder) stashExpr() { + d.stashedExpr = true +} + func (d *decoder) arrayIndex(shouldAppend bool, v reflect.Value) int { if d.arrayIndexes == nil { d.arrayIndexes = make(map[reflect.Value]int, 1) @@ -129,215 +165,371 @@ func (d *decoder) arrayIndex(shouldAppend bool, v reflect.Value) int { return idx } -func (d *decoder) FromParser(p *parser, v interface{}) error { - err := d.fromParser(p, v) +func (d *decoder) FromParser(v interface{}) error { + r := reflect.ValueOf(v) + if r.Kind() != reflect.Ptr { + return fmt.Errorf("toml: decoding can only be performed into a pointer, not %s", r.Kind()) + } + + if r.IsNil() { + return fmt.Errorf("toml: decoding pointer target cannot be nil") + } + + err := d.fromParser(r.Elem()) if err == nil { - return d.strict.Error(p.data) + return d.strict.Error(d.p.data) } var e *decodeError if errors.As(err, &e) { - return wrapDecodeError(p.data, e) + return wrapDecodeError(d.p.data, e) } return err } -func keyLocation(node ast.Node) []byte { - k := node.Key() - - hasOne := k.Next() - if !hasOne { - panic("should not be called with empty key") +func (d *decoder) fromParser(root reflect.Value) error { + for d.nextExpr() { + err := d.handleRootExpression(d.expr(), root) + if err != nil { + return err + } } - start := k.Node().Data - end := k.Node().Data + return d.p.Error() +} - for k.Next() { - end = k.Node().Data - } +/* +Rules for the unmarshal code: - return unsafe.BytesRange(start, end) -} +- The stack is used to keep track of which values need to be set where. +- handle* functions <=> switch on a given ast.Kind. +- unmarshalX* functions need to unmarshal a node of kind X. +- An "object" is either a struct or a map. +*/ -//nolint:funlen,cyclop -func (d *decoder) fromParser(p *parser, v interface{}) error { - r := reflect.ValueOf(v) - if r.Kind() != reflect.Ptr { - return fmt.Errorf("toml: decoding can only be performed into a pointer, not %s", r.Kind()) +func (d *decoder) handleRootExpression(expr ast.Node, v reflect.Value) error { + var x reflect.Value + var err error + + if !(d.skipUntilTable && expr.Kind == ast.KeyValue) { + err = d.seen.CheckExpression(expr) + if err != nil { + return err + } } - if r.IsNil() { - return fmt.Errorf("toml: decoding pointer target cannot be nil") + switch expr.Kind { + case ast.KeyValue: + if d.skipUntilTable { + return nil + } + x, err = d.handleKeyValue(expr, v) + case ast.Table: + d.skipUntilTable = false + d.strict.EnterTable(expr) + x, err = d.handleTable(expr.Key(), v) + case ast.ArrayTable: + d.skipUntilTable = false + d.strict.EnterArrayTable(expr) + x, err = d.handleArrayTable(expr.Key(), v) + default: + panic(fmt.Errorf("parser should not permit expression of kind %s at document root", expr.Kind)) } - var ( - skipUntilTable bool - root target = valueTarget(r.Elem()) - ) + if d.skipUntilTable { + if expr.Kind == ast.Table || expr.Kind == ast.ArrayTable { + d.strict.MissingTable(expr) + } + } else if err == nil && x.IsValid() { + v.Set(x) + } - current := root + return err +} - for p.NextExpression() { - node := p.Expression() +func (d *decoder) handleArrayTable(key ast.Iterator, v reflect.Value) (reflect.Value, error) { + if key.Next() { + return d.handleArrayTablePart(key, v) + } + return d.handleKeyValues(v) +} - if node.Kind == ast.KeyValue && skipUntilTable { - continue +func (d *decoder) handleArrayTableCollectionLast(key ast.Iterator, v reflect.Value) (reflect.Value, error) { + switch v.Kind() { + case reflect.Interface: + elem := v.Elem() + if !elem.IsValid() { + elem = reflect.New(sliceInterfaceType).Elem() + elem.Set(reflect.MakeSlice(sliceInterfaceType, 0, 16)) + } else if elem.Kind() == reflect.Slice { + if elem.Type() != sliceInterfaceType { + elem = reflect.New(sliceInterfaceType).Elem() + elem.Set(reflect.MakeSlice(sliceInterfaceType, 0, 16)) + } else if !elem.CanSet() { + nelem := reflect.New(sliceInterfaceType).Elem() + nelem.Set(reflect.MakeSlice(sliceInterfaceType, elem.Len(), elem.Cap())) + reflect.Copy(nelem, elem) + elem = nelem + } } - - err := d.seen.CheckExpression(node) - if err != nil { - return err + return d.handleArrayTableCollectionLast(key, elem) + case reflect.Ptr: + elem := v.Elem() + if !elem.IsValid() { + ptr := reflect.New(v.Type().Elem()) + v.Set(ptr) + elem = ptr.Elem() } - var found bool - - switch node.Kind { - case ast.KeyValue: - err = d.unmarshalKeyValue(current, node) - found = true - case ast.Table: - skipUntilTable = false - d.strict.EnterTable(node) - - current, found, err = d.scopeWithKey(root, node.Key()) - if err == nil && found { - // In case this table points to an interface, - // make sure it at least holds something that - // looks like a table. Otherwise the information - // of a table is lost, and marshal cannot do the - // round trip. - ensureMapIfInterface(current) - } - case ast.ArrayTable: - skipUntilTable = false - d.strict.EnterArrayTable(node) - current, found, err = d.scopeWithArrayTable(root, node.Key()) - default: - panic(fmt.Sprintf("this should not be a top level node type: %s", node.Kind)) + elem, err := d.handleArrayTableCollectionLast(key, elem) + if err != nil { + return reflect.Value{}, err } + v.Elem().Set(elem) + return v, nil + case reflect.Slice: + elem := reflect.New(v.Type().Elem()).Elem() + elem2, err := d.handleArrayTable(key, elem) if err != nil { - return err + return reflect.Value{}, err } - - if !found { - skipUntilTable = true - - d.strict.MissingTable(node) + if elem2.IsValid() { + elem = elem2 + } + return reflect.Append(v, elem), nil + case reflect.Array: + idx := d.arrayIndex(true, v) + if idx >= v.Len() { + return v, fmt.Errorf("toml: cannot decode array table into %s at position %d", v.Type(), idx) } + elem := v.Index(idx) + _, err := d.handleArrayTable(key, elem) + return v, err } - return p.Error() + return d.handleArrayTable(key, v) } -// scopeWithKey performs target scoping when unmarshaling an ast.KeyValue node. -// -// The goal is to hop from target to target recursively using the names in key. -// Parts of the key should be used to resolve field names for structs, and as -// keys when targeting maps. -// -// When encountering slices, it should always use its last element, and error -// if the slice does not have any. -func (d *decoder) scopeWithKey(x target, key ast.Iterator) (target, bool, error) { - var ( - err error - found bool - ) +// When parsing an array table expression, each part of the key needs to be +// evaluated like a normal key, but if it returns a collection, it also needs to +// point to the last element of the collection. Unless it is the last part of +// the key, then it needs to create a new element at the end. +func (d *decoder) handleArrayTableCollection(key ast.Iterator, v reflect.Value) (reflect.Value, error) { + if key.IsLast() { + return d.handleArrayTableCollectionLast(key, v) + } + + switch v.Kind() { + case reflect.Ptr: + elem := v.Elem() + if !elem.IsValid() { + ptr := reflect.New(v.Type().Elem()) + v.Set(ptr) + elem = ptr.Elem() + } - for key.Next() { - n := key.Node() + elem, err := d.handleArrayTableCollection(key, elem) + if err != nil { + return reflect.Value{}, err + } + v.Elem().Set(elem) - x, found, err = d.scopeTableTarget(false, x, string(n.Data)) - if err != nil || !found { - return nil, found, err + return v, nil + case reflect.Slice: + elem := v.Index(v.Len() - 1) + x, err := d.handleArrayTable(key, elem) + if err != nil || d.skipUntilTable { + return reflect.Value{}, err } + if x.IsValid() { + elem.Set(x) + } + + return v, err + case reflect.Array: + idx := d.arrayIndex(false, v) + if idx >= v.Len() { + return v, fmt.Errorf("toml: cannot decode array table into %s at position %d", v.Type(), idx) + } + elem := v.Index(idx) + _, err := d.handleArrayTable(key, elem) + return v, err } - return x, true, nil + return d.handleArrayTable(key, v) } -//nolint:cyclop -// scopeWithArrayTable performs target scoping when unmarshaling an -// ast.ArrayTable node. -// -// It is the same as scopeWithKey, but when scoping the last part of the key -// it creates a new element in the array instead of using the last one. -func (d *decoder) scopeWithArrayTable(x target, key ast.Iterator) (target, bool, error) { - var ( - err error - found bool - ) +func (d *decoder) handleKeyPart(key ast.Iterator, v reflect.Value, nextFn handlerFn, makeFn valueMakerFn) (reflect.Value, error) { + var rv reflect.Value - for key.Next() { - n := key.Node() - if !n.Next().Valid() { // want to stop at one before last - break + // First, dispatch over v to make sure it is a valid object. + // There is no guarantee over what it could be. + switch v.Kind() { + case reflect.Map: + // Create the key for the map element. For now assume it's a string. + mk := reflect.ValueOf(string(key.Node().Data)) + + // If the map does not exist, create it. + if v.IsNil() { + v = reflect.MakeMap(v.Type()) + rv = v } - x, found, err = d.scopeTableTarget(false, x, string(n.Data)) - if err != nil || !found { - return nil, found, err + mv := v.MapIndex(mk) + set := false + if !mv.IsValid() { + // If there is no value in the map, create a new one according to + // the map type. If the element type is interface, create either a + // map[string]interface{} or a []interface{} depending on whether + // this is the last part of the array table key. + + t := v.Type().Elem() + if t.Kind() == reflect.Interface { + mv = makeFn() + } else { + mv = reflect.New(t).Elem() + } + set = true + } else if mv.Kind() == reflect.Interface { + mv = mv.Elem() + if !mv.IsValid() { + mv = makeFn() + } + set = true } - } - - n := key.Node() - x, found, err = d.scopeTableTarget(false, x, string(n.Data)) - if err != nil || !found { - return x, found, err - } + x, err := nextFn(key, mv) + if err != nil { + return reflect.Value{}, err + } - v := x.get() + if x.IsValid() { + mv = x + set = true + } - if v.Kind() == reflect.Ptr { - x = scopePtr(x) - v = x.get() - } + if set { + v.SetMapIndex(mk, mv) + } + case reflect.Struct: + f, found := structField(v, string(key.Node().Data)) + if !found { + d.skipUntilTable = true + return reflect.Value{}, nil + } - if v.Kind() == reflect.Interface { - x = scopeInterface(true, x) - v = x.get() - } + x, err := nextFn(key, f) + if err != nil || d.skipUntilTable { + return reflect.Value{}, err + } + if x.IsValid() { + f.Set(x) + } + case reflect.Interface: + if v.Elem().IsValid() { + v = v.Elem() + } else { + v = reflect.MakeMap(mapStringInterfaceType) + } - switch v.Kind() { - case reflect.Slice: - x = scopeSlice(true, x) - case reflect.Array: - x, err = d.scopeArray(true, x) + x, err := d.handleKeyPart(key, v, nextFn, makeFn) + if err != nil { + return reflect.Value{}, err + } + if x.IsValid() { + v = x + } + rv = v default: + panic(fmt.Errorf("unhandled part: %s", v.Kind())) } - return x, found, err + return rv, nil } -func (d *decoder) unmarshalKeyValue(x target, node ast.Node) error { - assertNode(ast.KeyValue, node) - - d.strict.EnterKeyValue(node) - defer d.strict.ExitKeyValue(node) +// HandleArrayTablePart navigates the Go structure v using the key v. It is +// only used for the prefix (non-last) parts of an array-table. When +// encountering a collection, it should go to the last element. +func (d *decoder) handleArrayTablePart(key ast.Iterator, v reflect.Value) (reflect.Value, error) { + var makeFn valueMakerFn + if key.IsLast() { + makeFn = makeSliceInterface + } else { + makeFn = makeMapStringInterface + } + return d.handleKeyPart(key, v, d.handleArrayTableCollection, makeFn) +} - x, found, err := d.scopeWithKey(x, node.Key()) - if err != nil { - return err +// HandleTable returns a reference when it has checked the next expression but +// cannot handle it. +func (d *decoder) handleTable(key ast.Iterator, v reflect.Value) (reflect.Value, error) { + if v.Kind() == reflect.Slice { + elem := v.Index(v.Len() - 1) + x, err := d.handleTable(key, elem) + if err != nil { + return reflect.Value{}, err + } + if x.IsValid() { + elem.Set(x) + } + return reflect.Value{}, nil } + if key.Next() { + // Still scoping the key + return d.handleTablePart(key, v) + } + // Done scoping the key. + // Now handle all the key-value expressions in this table. + return d.handleKeyValues(v) +} - // A struct in the path was not found. Skip this value. - if !found { - d.strict.MissingField(node) +// Handle root expressions until the end of the document or the next +// non-key-value. +func (d *decoder) handleKeyValues(v reflect.Value) (reflect.Value, error) { + var rv reflect.Value + for d.nextExpr() { + expr := d.expr() + if expr.Kind != ast.KeyValue { + // Stash the expression so that fromParser can just loop and use + // the right handler. + // We could just recurse ourselves here, but at least this gives a + // chance to pop the stack a bit. + d.stashExpr() + break + } - return nil + x, err := d.handleKeyValue(expr, v) + if err != nil { + return reflect.Value{}, err + } + if x.IsValid() { + v = x + rv = x + } } + return rv, nil +} - return d.unmarshalValue(x, node.Value()) +type ( + handlerFn func(key ast.Iterator, v reflect.Value) (reflect.Value, error) + valueMakerFn func() reflect.Value +) + +func makeMapStringInterface() reflect.Value { + return reflect.MakeMap(mapStringInterfaceType) } -var textUnmarshalerType = reflect.TypeOf(new(encoding.TextUnmarshaler)).Elem() +func makeSliceInterface() reflect.Value { + return reflect.MakeSlice(sliceInterfaceType, 0, 16) +} -func tryTextUnmarshaler(x target, node ast.Node) (bool, error) { - v := x.get() +func (d *decoder) handleTablePart(key ast.Iterator, v reflect.Value) (reflect.Value, error) { + return d.handleKeyPart(key, v, d.handleTable, makeMapStringInterface) +} +func tryTextUnmarshaler(node ast.Node, v reflect.Value) (bool, error) { if v.Kind() != reflect.Struct { return false, nil } @@ -348,19 +540,12 @@ func tryTextUnmarshaler(x target, node ast.Node) (bool, error) { return false, nil } - if v.Type().Implements(textUnmarshalerType) { - err := v.Interface().(encoding.TextUnmarshaler).UnmarshalText(node.Data) - if err != nil { - return false, newDecodeError(node.Data, "error calling UnmarshalText: %w", err) - } - - return true, nil - } - if v.CanAddr() && v.Addr().Type().Implements(textUnmarshalerType) { err := v.Addr().Interface().(encoding.TextUnmarshaler).UnmarshalText(node.Data) if err != nil { - return false, newDecodeError(node.Data, "error calling UnmarshalText: %w", err) + return false, fmt.Errorf("toml: error calling UnmarshalText: %w", err) + // TODO: same as above + // return false, newDecodeError(node.Data, "error calling UnmarshalText: %w", err) } return true, nil @@ -369,65 +554,177 @@ func tryTextUnmarshaler(x target, node ast.Node) (bool, error) { return false, nil } -//nolint:cyclop -func (d *decoder) unmarshalValue(x target, node ast.Node) error { - v := x.get() - - if v.Kind() == reflect.Ptr { - if !v.Elem().IsValid() { - x.set(reflect.New(v.Type().Elem())) - v = x.get() - } - - return d.unmarshalValue(valueTarget(v.Elem()), node) +func (d *decoder) handleValue(value ast.Node, v reflect.Value) error { + for v.Kind() == reflect.Ptr { + v = initAndDereferencePointer(v) } - ok, err := tryTextUnmarshaler(x, node) - if ok { + ok, err := tryTextUnmarshaler(value, v) + if ok || err != nil { return err } - switch node.Kind { + switch value.Kind { case ast.String: - return unmarshalString(x, node) - case ast.Bool: - return unmarshalBool(x, node) + return d.unmarshalString(value, v) case ast.Integer: - return unmarshalInteger(x, node) + return d.unmarshalInteger(value, v) case ast.Float: - return unmarshalFloat(x, node) - case ast.Array: - return d.unmarshalArray(x, node) - case ast.InlineTable: - return d.unmarshalInlineTable(x, node) - case ast.LocalDateTime: - return unmarshalLocalDateTime(x, node) + return d.unmarshalFloat(value, v) + case ast.Bool: + return d.unmarshalBool(value, v) case ast.DateTime: - return unmarshalDateTime(x, node) + return d.unmarshalDateTime(value, v) case ast.LocalDate: - return unmarshalLocalDate(x, node) + return d.unmarshalLocalDate(value, v) + case ast.LocalDateTime: + return d.unmarshalLocalDateTime(value, v) + case ast.InlineTable: + return d.unmarshalInlineTable(value, v) + case ast.Array: + return d.unmarshalArray(value, v) + default: + panic(fmt.Errorf("handleValue not implemented for %s", value.Kind)) + } +} + +func (d *decoder) unmarshalArray(array ast.Node, v reflect.Value) error { + switch v.Kind() { + case reflect.Slice: + if v.IsNil() { + v.Set(reflect.MakeSlice(v.Type(), 0, 16)) + } else { + v.SetLen(0) + } + case reflect.Array: + // arrays are always initialized + case reflect.Interface: + elem := v.Elem() + if !elem.IsValid() { + elem = reflect.New(sliceInterfaceType).Elem() + elem.Set(reflect.MakeSlice(sliceInterfaceType, 0, 16)) + } else if elem.Kind() == reflect.Slice { + if elem.Type() != sliceInterfaceType { + elem = reflect.New(sliceInterfaceType).Elem() + elem.Set(reflect.MakeSlice(sliceInterfaceType, 0, 16)) + } else if !elem.CanSet() { + nelem := reflect.New(sliceInterfaceType).Elem() + nelem.Set(reflect.MakeSlice(sliceInterfaceType, elem.Len(), elem.Cap())) + reflect.Copy(nelem, elem) + elem = nelem + } + } + err := d.unmarshalArray(array, elem) + if err != nil { + return err + } + v.Set(elem) + return nil + default: + // TODO: use newDecodeError, but first the parser needs to fill + // array.Data. + return fmt.Errorf("toml: cannot store array in Go type %s", v.Kind()) + } + + elemType := v.Type().Elem() + + it := array.Children() + idx := 0 + for it.Next() { + n := it.Node() + + // TODO: optimize + if v.Kind() == reflect.Slice { + elem := reflect.New(elemType).Elem() + + err := d.handleValue(n, elem) + if err != nil { + return err + } + + v.Set(reflect.Append(v, elem)) + } else { // array + if idx >= v.Len() { + return nil + } + elem := v.Index(idx) + err := d.handleValue(n, elem) + if err != nil { + return err + } + idx++ + } + } + + return nil +} + +func (d *decoder) unmarshalInlineTable(itable ast.Node, v reflect.Value) error { + // Make sure v is an initialized object. + switch v.Kind() { + case reflect.Map: + if v.IsNil() { + v.Set(reflect.MakeMap(v.Type())) + } + case reflect.Struct: + // structs are always initialized. + case reflect.Interface: + elem := v.Elem() + if !elem.IsValid() { + elem = reflect.MakeMap(mapStringInterfaceType) + v.Set(elem) + } + return d.unmarshalInlineTable(itable, elem) default: - panic(fmt.Sprintf("unhandled node kind %s", node.Kind)) + return newDecodeError(itable.Data, "cannot store inline table in Go type %s", v.Kind()) } + + it := itable.Children() + for it.Next() { + n := it.Node() + + x, err := d.handleKeyValue(n, v) + if err != nil { + return err + } + if x.IsValid() { + v = x + } + } + + return nil } -func unmarshalLocalDate(x target, node ast.Node) error { - assertNode(ast.LocalDate, node) +func (d *decoder) unmarshalDateTime(value ast.Node, v reflect.Value) error { + dt, err := parseDateTime(value.Data) + if err != nil { + return err + } - v, err := parseLocalDate(node.Data) + v.Set(reflect.ValueOf(dt)) + return nil +} + +func (d *decoder) unmarshalLocalDate(value ast.Node, v reflect.Value) error { + ld, err := parseLocalDate(value.Data) if err != nil { return err } - setDate(x, v) + if v.Type() == timeType { + cast := ld.In(time.Local) + + v.Set(reflect.ValueOf(cast)) + return nil + } + + v.Set(reflect.ValueOf(ld)) return nil } -func unmarshalLocalDateTime(x target, node ast.Node) error { - assertNode(ast.LocalDateTime, node) - - v, rest, err := parseLocalDateTime(node.Data) +func (d *decoder) unmarshalLocalDateTime(value ast.Node, v reflect.Value) error { + ldt, rest, err := parseLocalDateTime(value.Data) if err != nil { return err } @@ -436,160 +733,366 @@ func unmarshalLocalDateTime(x target, node ast.Node) error { return newDecodeError(rest, "extra characters at the end of a local date time") } - setLocalDateTime(x, v) + if v.Type() == timeType { + cast := ldt.In(time.Local) + + v.Set(reflect.ValueOf(cast)) + return nil + } + + v.Set(reflect.ValueOf(ldt)) return nil } -func unmarshalDateTime(x target, node ast.Node) error { - assertNode(ast.DateTime, node) +func (d *decoder) unmarshalBool(value ast.Node, v reflect.Value) error { + b := value.Data[0] == 't' - v, err := parseDateTime(node.Data) + switch v.Kind() { + case reflect.Bool: + v.SetBool(b) + case reflect.Interface: + v.Set(reflect.ValueOf(b)) + default: + return newDecodeError(value.Data, "cannot assign boolean to a %t", b) + } + + return nil +} + +func (d *decoder) unmarshalFloat(value ast.Node, v reflect.Value) error { + f, err := parseFloat(value.Data) if err != nil { return err } - setDateTime(x, v) + switch v.Kind() { + case reflect.Float64: + v.SetFloat(f) + case reflect.Float32: + if f > math.MaxFloat32 { + return newDecodeError(value.Data, "number %f does not fit in a float32", f) + } + v.SetFloat(f) + case reflect.Interface: + v.Set(reflect.ValueOf(f)) + default: + return newDecodeError(value.Data, "float cannot be assigned to %s", v.Kind()) + } return nil } -func setLocalDateTime(x target, v LocalDateTime) { - if x.get().Type() == timeType { - cast := v.In(time.Local) +func (d *decoder) unmarshalInteger(value ast.Node, v reflect.Value) error { + const ( + maxInt = int64(^uint(0) >> 1) + minInt = -maxInt - 1 + ) - setDateTime(x, cast) - return + i, err := parseInteger(value.Data) + if err != nil { + return err } - x.set(reflect.ValueOf(v)) -} + switch v.Kind() { + case reflect.Int64: + v.SetInt(i) + case reflect.Int32: + if i < math.MinInt32 || i > math.MaxInt32 { + return fmt.Errorf("toml: number %d does not fit in an int32", i) + } -func setDateTime(x target, v time.Time) { - x.set(reflect.ValueOf(v)) -} + v.Set(reflect.ValueOf(int32(i))) + return nil + case reflect.Int16: + if i < math.MinInt16 || i > math.MaxInt16 { + return fmt.Errorf("toml: number %d does not fit in an int16", i) + } -var timeType = reflect.TypeOf(time.Time{}) + v.Set(reflect.ValueOf(int16(i))) + case reflect.Int8: + if i < math.MinInt8 || i > math.MaxInt8 { + return fmt.Errorf("toml: number %d does not fit in an int8", i) + } -func setDate(x target, v LocalDate) { - if x.get().Type() == timeType { - cast := v.In(time.Local) + v.Set(reflect.ValueOf(int8(i))) + case reflect.Int: + if i < minInt || i > maxInt { + return fmt.Errorf("toml: number %d does not fit in an int", i) + } - setDateTime(x, cast) - return - } + v.Set(reflect.ValueOf(int(i))) + case reflect.Uint64: + if i < 0 { + return fmt.Errorf("toml: negative number %d does not fit in an uint64", i) + } - x.set(reflect.ValueOf(v)) -} + v.Set(reflect.ValueOf(uint64(i))) + case reflect.Uint32: + if i < 0 || i > math.MaxUint32 { + return fmt.Errorf("toml: negative number %d does not fit in an uint32", i) + } -func unmarshalString(x target, node ast.Node) error { - assertNode(ast.String, node) + v.Set(reflect.ValueOf(uint32(i))) + case reflect.Uint16: + if i < 0 || i > math.MaxUint16 { + return fmt.Errorf("toml: negative number %d does not fit in an uint16", i) + } - return setString(x, string(node.Data)) -} + v.Set(reflect.ValueOf(uint16(i))) + case reflect.Uint8: + if i < 0 || i > math.MaxUint8 { + return fmt.Errorf("toml: negative number %d does not fit in an uint8", i) + } -func unmarshalBool(x target, node ast.Node) error { - assertNode(ast.Bool, node) - v := node.Data[0] == 't' + v.Set(reflect.ValueOf(uint8(i))) + case reflect.Uint: + if i < 0 { + return fmt.Errorf("toml: negative number %d does not fit in an uint", i) + } - return setBool(x, v) + v.Set(reflect.ValueOf(uint(i))) + case reflect.Interface: + v.Set(reflect.ValueOf(i)) + default: + err = fmt.Errorf("toml: cannot store TOML integer into a Go %s", v.Kind()) + } + + return err } -func unmarshalInteger(x target, node ast.Node) error { - assertNode(ast.Integer, node) +func (d *decoder) unmarshalString(value ast.Node, v reflect.Value) error { + var err error - v, err := parseInteger(node.Data) - if err != nil { - return err + switch v.Kind() { + case reflect.String: + v.SetString(string(value.Data)) + case reflect.Interface: + v.Set(reflect.ValueOf(string(value.Data))) + default: + err = fmt.Errorf("toml: cannot store TOML string into a Go %s", v.Kind()) } - return setInt64(x, v) + return err } -func unmarshalFloat(x target, node ast.Node) error { - assertNode(ast.Float, node) +func (d *decoder) handleKeyValue(expr ast.Node, v reflect.Value) (reflect.Value, error) { + d.strict.EnterKeyValue(expr) - v, err := parseFloat(node.Data) - if err != nil { - return err + v, err := d.handleKeyValueInner(expr.Key(), expr.Value(), v) + if d.skipUntilTable { + d.strict.MissingField(expr) + d.skipUntilTable = false } - return setFloat64(x, v) + d.strict.ExitKeyValue(expr) + + return v, err } -func (d *decoder) unmarshalInlineTable(x target, node ast.Node) error { - assertNode(ast.InlineTable, node) +func (d *decoder) handleKeyValueInner(key ast.Iterator, value ast.Node, v reflect.Value) (reflect.Value, error) { + if key.Next() { + // Still scoping the key + return d.handleKeyValuePart(key, value, v) + } + // Done scoping the key. + // v is whatever Go value we need to fill. + return reflect.Value{}, d.handleValue(value, v) +} - ensureMapIfInterface(x) +func (d *decoder) handleKeyValuePart(key ast.Iterator, value ast.Node, v reflect.Value) (reflect.Value, error) { + // contains the replacement for v + var rv reflect.Value - it := node.Children() - for it.Next() { - n := it.Node() + // First, dispatch over v to make sure it is a valid object. + // There is no guarantee over what it could be. + switch v.Kind() { + case reflect.Map: + mk := reflect.ValueOf(string(key.Node().Data)) + + keyType := v.Type().Key() + if !mk.Type().AssignableTo(keyType) { + if !mk.Type().ConvertibleTo(keyType) { + return reflect.Value{}, fmt.Errorf("toml: cannot convert map key of type %s to expected type %s", mk.Type(), keyType) + } - err := d.unmarshalKeyValue(x, n) + mk = mk.Convert(keyType) + } + + // If the map does not exist, create it. + if v.IsNil() { + v = reflect.MakeMap(v.Type()) + rv = v + } + + mv := v.MapIndex(mk) + set := false + if !mv.IsValid() { + set = true + mv = reflect.New(v.Type().Elem()).Elem() + } else { + if key.IsLast() { + var x interface{} + mv = reflect.ValueOf(&x).Elem() + set = true + } + } + + nv, err := d.handleKeyValueInner(key, value, mv) if err != nil { - return err + return reflect.Value{}, err + } + if nv.IsValid() { + mv = nv + set = true } - } - return nil -} + if set { + v.SetMapIndex(mk, mv) + } + case reflect.Struct: + f, found := structField(v, string(key.Node().Data)) + if !found { + d.skipUntilTable = true + break + } -func (d *decoder) unmarshalArray(x target, node ast.Node) error { - assertNode(ast.Array, node) + x, err := d.handleKeyValueInner(key, value, f) + if err != nil { + return reflect.Value{}, err + } - err := ensureValueIndexable(x) - if err != nil { - return err + if x.IsValid() { + f.Set(x) + } + case reflect.Interface: + v = v.Elem() + + // Following encoding/toml: decoding an object into an interface{}, it + // needs to always hold a map[string]interface{}. This is for the types + // to be consistent whether a previous value was set or not. + if !v.IsValid() || v.Type() != mapStringInterfaceType { + v = reflect.MakeMap(mapStringInterfaceType) + } + + x, err := d.handleKeyValuePart(key, value, v) + if err != nil { + return reflect.Value{}, err + } + if x.IsValid() { + v = x + } + rv = v + case reflect.Ptr: + elem := v.Elem() + if !elem.IsValid() { + ptr := reflect.New(v.Type().Elem()) + v.Set(ptr) + rv = v + elem = ptr.Elem() + } + + elem2, err := d.handleKeyValuePart(key, value, elem) + if err != nil { + return reflect.Value{}, err + } + if elem2.IsValid() { + elem = elem2 + } + v.Elem().Set(elem) + default: + return reflect.Value{}, fmt.Errorf("unhandled kv part: %s", v.Kind()) } - // Special work around when unmarshaling into an array. - // If the array is not addressable, for example when stored as a value in a - // map, calling elementAt in the inner function would fail. - // Instead, we allocate a new array that will be filled then inserted into - // the container. - // This problem does not exist with slices because they are addressable. - // There may be a better way of doing this, but it is not obvious to me - // with the target system. - if x.get().Kind() == reflect.Array { - container := x - newArrayPtr := reflect.New(x.get().Type()) - x = valueTarget(newArrayPtr.Elem()) - defer func() { - container.set(newArrayPtr.Elem()) - }() + return rv, nil +} + +func initAndDereferencePointer(v reflect.Value) reflect.Value { + var elem reflect.Value + if v.IsNil() { + ptr := reflect.New(v.Type().Elem()) + v.Set(ptr) } + elem = v.Elem() + return elem +} + +type fieldPathsMap = map[string][]int - return d.unmarshalArrayInner(x, node) +type fieldPathsCache struct { + m map[reflect.Type]fieldPathsMap + l sync.RWMutex } -func (d *decoder) unmarshalArrayInner(x target, node ast.Node) error { - idx := 0 +func (c *fieldPathsCache) get(t reflect.Type) (fieldPathsMap, bool) { + c.l.RLock() + paths, ok := c.m[t] + c.l.RUnlock() - it := node.Children() - for it.Next() { - n := it.Node() + return paths, ok +} - v := elementAt(x, idx) +func (c *fieldPathsCache) set(t reflect.Type, m fieldPathsMap) { + c.l.Lock() + c.m[t] = m + c.l.Unlock() +} - if v == nil { - // when we go out of bound for an array just stop processing it to - // mimic encoding/json - break - } +var globalFieldPathsCache = fieldPathsCache{ + m: map[reflect.Type]fieldPathsMap{}, + l: sync.RWMutex{}, +} - err := d.unmarshalValue(v, n) - if err != nil { - return err +func structField(v reflect.Value, name string) (reflect.Value, bool) { + //nolint:godox + // TODO: cache this, and reduce allocations + fieldPaths, ok := globalFieldPathsCache.get(v.Type()) + if !ok { + fieldPaths = map[string][]int{} + + path := make([]int, 0, 16) + + var walk func(reflect.Value) + walk = func(v reflect.Value) { + t := v.Type() + for i := 0; i < t.NumField(); i++ { + l := len(path) + path = append(path, i) + f := t.Field(i) + + if f.Anonymous { + walk(v.Field(i)) + } else if f.PkgPath == "" { + // only consider exported fields + fieldName, ok := f.Tag.Lookup("toml") + if !ok { + fieldName = f.Name + } + + pathCopy := make([]int, len(path)) + copy(pathCopy, path) + + fieldPaths[fieldName] = pathCopy + // extra copy for the case-insensitive match + fieldPaths[strings.ToLower(fieldName)] = pathCopy + } + path = path[:l] + } } - idx++ + walk(v) + + globalFieldPathsCache.set(v.Type(), fieldPaths) } - return nil -} -func assertNode(expected ast.Kind, node ast.Node) { - if node.Kind != expected { - panic(fmt.Sprintf("expected node of kind %s, not %s", expected, node.Kind)) + path, ok := fieldPaths[name] + if !ok { + path, ok = fieldPaths[strings.ToLower(name)] } + + if !ok { + return reflect.Value{}, false + } + + return v.FieldByIndex(path), true } diff --git a/unmarshaler_test.go b/unmarshaler_test.go index d9203a65..f3deb238 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -14,10 +14,23 @@ import ( "github.com/stretchr/testify/require" ) +type badReader struct{} + +func (r *badReader) Read([]byte) (int, error) { + return 0, fmt.Errorf("testing error") +} + +func TestDecodeReaderError(t *testing.T) { + r := &badReader{} + + dec := toml.NewDecoder(r) + m := map[string]interface{}{} + err := dec.Decode(&m) + require.Error(t, err) +} + // nolint:funlen func TestUnmarshal_Integers(t *testing.T) { - t.Parallel() - examples := []struct { desc string input string @@ -88,8 +101,6 @@ func TestUnmarshal_Integers(t *testing.T) { for _, e := range examples { e := e t.Run(e.desc, func(t *testing.T) { - t.Parallel() - doc := doc{} err := toml.Unmarshal([]byte(`A = `+e.input), &doc) if e.err { @@ -104,8 +115,6 @@ func TestUnmarshal_Integers(t *testing.T) { //nolint:funlen func TestUnmarshal_Floats(t *testing.T) { - t.Parallel() - examples := []struct { desc string input string @@ -197,8 +206,6 @@ func TestUnmarshal_Floats(t *testing.T) { for _, e := range examples { e := e t.Run(e.desc, func(t *testing.T) { - t.Parallel() - doc := doc{} err := toml.Unmarshal([]byte(`A = `+e.input), &doc) require.NoError(t, err) @@ -213,8 +220,6 @@ func TestUnmarshal_Floats(t *testing.T) { //nolint:funlen func TestUnmarshal(t *testing.T) { - t.Parallel() - type test struct { target interface{} expected interface{} @@ -814,6 +819,240 @@ B = "data"`, } }, }, + { + desc: "array table into interface in struct", + input: `[[foo]] + bar = "hello"`, + gen: func() test { + type doc struct { + Foo interface{} + } + return test{ + target: &doc{}, + expected: &doc{ + Foo: []interface{}{ + map[string]interface{}{ + "bar": "hello", + }, + }, + }, + } + }, + }, + { + desc: "array table into interface in struct already initialized with right type", + input: `[[foo]] + bar = "hello"`, + gen: func() test { + type doc struct { + Foo interface{} + } + return test{ + target: &doc{ + Foo: []interface{}{}, + }, + expected: &doc{ + Foo: []interface{}{ + map[string]interface{}{ + "bar": "hello", + }, + }, + }, + } + }, + }, + { + desc: "array table into interface in struct already initialized with wrong type", + input: `[[foo]] + bar = "hello"`, + gen: func() test { + type doc struct { + Foo interface{} + } + return test{ + target: &doc{ + Foo: []string{}, + }, + expected: &doc{ + Foo: []interface{}{ + map[string]interface{}{ + "bar": "hello", + }, + }, + }, + } + }, + }, + { + desc: "array table into nil ptr", + input: `[[foo]] + bar = "hello"`, + gen: func() test { + type doc struct { + Foo *[]interface{} + } + return test{ + target: &doc{}, + expected: &doc{ + Foo: &[]interface{}{ + map[string]interface{}{ + "bar": "hello", + }, + }, + }, + } + }, + }, + { + desc: "array table into nil ptr of invalid type", + input: `[[foo]] + bar = "hello"`, + gen: func() test { + type doc struct { + Foo *string + } + return test{ + target: &doc{}, + err: true, + } + }, + }, + { + desc: "array table with intermediate ptr", + input: `[[foo.bar]] + bar = "hello"`, + gen: func() test { + type doc struct { + Foo *map[string]interface{} + } + return test{ + target: &doc{}, + expected: &doc{ + Foo: &map[string]interface{}{ + "bar": []interface{}{ + map[string]interface{}{ + "bar": "hello", + }, + }, + }, + }, + } + }, + }, + { + desc: "unmarshal array into interface that contains a slice", + input: `a = [1,2,3]`, + gen: func() test { + type doc struct { + A interface{} + } + return test{ + target: &doc{ + A: []string{}, + }, + expected: &doc{ + A: []interface{}{ + int64(1), + int64(2), + int64(3), + }, + }, + } + }, + }, + { + desc: "unmarshal array into interface that contains a []interface{}", + input: `a = [1,2,3]`, + gen: func() test { + type doc struct { + A interface{} + } + return test{ + target: &doc{ + A: []interface{}{}, + }, + expected: &doc{ + A: []interface{}{ + int64(1), + int64(2), + int64(3), + }, + }, + } + }, + }, + { + desc: "unmarshal key into map with existing value", + input: `a = "new"`, + gen: func() test { + return test{ + target: &map[string]interface{}{"a": "old"}, + expected: &map[string]interface{}{"a": "new"}, + } + }, + }, + { + desc: "unmarshal key into map with existing value", + input: `a.b = "new"`, + gen: func() test { + type doc struct { + A interface{} + } + return test{ + target: &doc{}, + expected: &doc{ + A: map[string]interface{}{ + "b": "new", + }, + }, + } + }, + }, + { + desc: "unmarshal array into struct field with existing array", + input: `a = [1,2]`, + gen: func() test { + type doc struct { + A []int + } + return test{ + target: &doc{}, + expected: &doc{ + A: []int{1, 2}, + }, + } + }, + }, + { + desc: "unmarshal inline table into map", + input: `a = {b="hello"}`, + gen: func() test { + type doc struct { + A map[string]interface{} + } + return test{ + target: &doc{}, + expected: &doc{ + A: map[string]interface{}{ + "b": "hello", + }, + }, + } + }, + }, + { + desc: "unmarshal inline table into map of incorrect type", + input: `a = {b="hello"}`, + gen: func() test { + type doc struct { + A map[string]int + } + return test{ + target: &doc{}, + err: true, + } + }, + }, { desc: "slice pointer in slice pointer", input: `A = ["Hello"]`, @@ -1155,8 +1394,6 @@ B = "data"`, for _, e := range examples { e := e t.Run(e.desc, func(t *testing.T) { - t.Parallel() - if e.skip { t.Skip() } @@ -1241,6 +1478,16 @@ func TestUnmarshalOverflows(t *testing.T) { } } +func TestUnmarshalInvalidTarget(t *testing.T) { + x := "foo" + err := toml.Unmarshal([]byte{}, x) + require.Error(t, err) + + var m *map[string]interface{} + err = toml.Unmarshal([]byte{}, m) + require.Error(t, err) +} + func TestUnmarshalFloat32(t *testing.T) { t.Run("fits", func(t *testing.T) { doc := "A = 1.2" @@ -1277,8 +1524,6 @@ type Config484 struct { } func TestIssue484(t *testing.T) { - t.Parallel() - raw := []byte(`integers = ["1","2","3","100"]`) var cfg Config484 @@ -1299,8 +1544,6 @@ func (m Map458) A(s string) Slice458 { } func TestIssue458(t *testing.T) { - t.Parallel() - s := []byte(`[[package]] dependencies = ["regex"] name = "decode" @@ -1320,8 +1563,6 @@ version = "0.1.0"`) } func TestIssue252(t *testing.T) { - t.Parallel() - type config struct { Val1 string `toml:"val1"` Val2 string `toml:"val2"` @@ -1342,8 +1583,6 @@ val1 = "test1" } func TestIssue494(t *testing.T) { - t.Parallel() - data := ` foo = 2021-04-08 bar = 2021-04-08 @@ -1359,8 +1598,6 @@ bar = 2021-04-08 } func TestIssue507(t *testing.T) { - t.Parallel() - data := []byte{'0', '=', '\n', '0', 'a', 'm', 'e'} m := map[string]interface{}{} err := toml.Unmarshal(data, &m) @@ -1369,8 +1606,6 @@ func TestIssue507(t *testing.T) { //nolint:funlen func TestUnmarshalDecodeErrors(t *testing.T) { - t.Parallel() - examples := []struct { desc string data string @@ -1603,8 +1838,6 @@ world'`, for _, e := range examples { e := e t.Run(e.desc, func(t *testing.T) { - t.Parallel() - m := map[string]interface{}{} err := toml.Unmarshal([]byte(e.data), &m) require.Error(t, err) @@ -1624,8 +1857,6 @@ world'`, //nolint:funlen func TestLocalDateTime(t *testing.T) { - t.Parallel() - examples := []struct { desc string input string @@ -1675,7 +1906,6 @@ func TestLocalDateTime(t *testing.T) { for _, e := range examples { e := e t.Run(e.desc, func(t *testing.T) { - t.Parallel() t.Log("input:", e.input) doc := `a = ` + e.input m := map[string]toml.LocalDateTime{} @@ -1691,8 +1921,6 @@ func TestLocalDateTime(t *testing.T) { } func TestIssue287(t *testing.T) { - t.Parallel() - b := `y=[[{}]]` v := map[string]interface{}{} err := toml.Unmarshal([]byte(b), &v) @@ -1709,8 +1937,6 @@ func TestIssue287(t *testing.T) { } func TestIssue508(t *testing.T) { - t.Parallel() - type head struct { Title string `toml:"title"` } @@ -1729,8 +1955,6 @@ func TestIssue508(t *testing.T) { //nolint:funlen func TestDecoderStrict(t *testing.T) { - t.Parallel() - examples := []struct { desc string input string @@ -1801,8 +2025,6 @@ bar = 42 for _, e := range examples { e := e t.Run(e.desc, func(t *testing.T) { - t.Parallel() - t.Run("strict", func(t *testing.T) { r := strings.NewReader(e.input) d := toml.NewDecoder(r) From b2023754147348a6541f049c2af1c6d6fadfbe8f Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Tue, 1 Jun 2021 09:10:17 -0400 Subject: [PATCH 203/228] Add benchmarks results to readme (#548) --- README.md | 54 ++++++++++++++++++++++++++++-------- ci.sh | 83 +++++++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 120 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 521ed749..b2f3312a 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,6 @@ Go library for the [TOML](https://toml.io/en/) format. This library supports [TOML v1.0.0](https://toml.io/en/v1.0.0). - ## Development status This is the upcoming major version of go-toml. It is currently in active @@ -20,14 +19,12 @@ encouraged to try out this version. [💬 Anything else](https://github.com/pelletier/go-toml/discussions) - ## Documentation Full API, examples, and implementation notes are available in the Go documentation. [![Go Reference](https://pkg.go.dev/badge/github.com/pelletier/go-toml/v2.svg)](https://pkg.go.dev/github.com/pelletier/go-toml/v2) - ## Import ```go @@ -44,7 +41,7 @@ standard library's `encoding/json`. ### Performance While go-toml favors usability, it is written with performance in mind. Most -operations should not be shockingly slow. +operations should not be shockingly slow. See [benchmarks](#benchmarks). ### Strict mode @@ -150,6 +147,43 @@ fmt.Println(string(b)) [marshal]: https://pkg.go.dev/github.com/pelletier/go-toml/v2#Marshal +## Benchmarks + +Execution time speedup compared to other Go TOML libraries: + + + + + + + + + + +
Benchmarkgo-toml v1BurntSushi/toml
HugoFrontMatter2.6x2.2x
ReferenceFile/map2.8x3.0x
ReferenceFile/struct5.4x6.2x
+

See more +

The table above has the results of the most common use-cases. The table +below contains the results of all benchmarks, including unrealistic ones. is +provided for completeness.

+ + + + + + + + + + + + + + + +
Benchmarkgo-toml v1BurntSushi/toml
UnmarshalSimple/map3.8x2.4x
UnmarshalSimple/struct5.4x3.1x
UnmarshalDataset/example2.8x2.0x
UnmarshalDataset/code1.8x2.2x
UnmarshalDataset/twitter2.5x1.8x
UnmarshalDataset/citm_catalog1.9x1.2x
UnmarshalDataset/config3.0x2.5x
[Geo mean]3.0x2.4x
+

This table can be generated with ./ci.sh benchmark -a -html.

+
+ ## Migrating from v1 This section describes the differences between v1 and v2, with some pointers on @@ -191,7 +225,7 @@ d := doc{ } data := ` -[A] +[A] B = "After" ` @@ -252,7 +286,6 @@ This method was not widely used, poorly defined, and added a lot of complexity. A similar effect can be achieved by implementing the `encoding.TextUnmarshaler` interface and use strings. - #### Support for `default` struct tag has been dropped This feature adds complexity and a poorly defined API for an effect that can be @@ -311,7 +344,6 @@ manually sort the fields alphabetically in the struct definition. V1 automatically indents content of tables by default. V2 does not. However the same behavior can be obtained using [`Encoder.SetIndentTables`][sit]. For example: - ```go data := map[string]interface{}{ "table": map[string]string{ @@ -333,15 +365,15 @@ fmt.Println("v2 Encoder:\n" + string(buf.Bytes())) // Output: // v1: -// +// // [table] // key = "value" -// +// // v2: // [table] // key = 'value' -// -// +// +// // v2 Encoder: // [table] // key = 'value' diff --git a/ci.sh b/ci.sh index d7e10cde..22cda0ad 100755 --- a/ci.sh +++ b/ci.sh @@ -42,6 +42,9 @@ benchmark [OPTIONS...] [BRANCH] -a Compare benchmarks of HEAD against go-toml v1 and BurntSushi/toml. + -html When used with -a, emits the output as HTML, ready to be + embedded in the README. + coverage [OPTIONS...] [BRANCH] Generates code coverage. @@ -150,16 +153,78 @@ bench() { fi } +fmktemp() { + if mktemp --version|grep GNU >/dev/null; then + mktemp --suffix=-$1; + else + mktemp -t $1; + fi +} + +benchstathtml() { +python3 - $1 <<'EOF' +import sys + +lines = [] +stop = False + +with open(sys.argv[1]) as f: + for line in f.readlines(): + line = line.strip() + if line == "": + stop = True + if not stop: + lines.append(line.split(',')) + +results = [] +for line in reversed(lines[1:]): + v2 = float(line[1]) + results.append([ + line[0].replace("-32", ""), + "%.1fx" % (float(line[3])/v2), # v1 + "%.1fx" % (float(line[5])/v2), # bs + ]) +# move geomean to the end +results.append(results[0]) +del results[0] + + +def printtable(data): + print(""" + + + + + """) + + for r in data: + print(" ".format(*r)) + + print(""" +
Benchmarkgo-toml v1BurntSushi/toml
{}{}{}
""") + + +fold = 3 +printtable(results[:fold]) +print("
See more") +print('

The table above has the results of the most common use-cases. The table below contains the results of all benchmarks, including unrealistic ones. is provided for completeness.

') +printtable(results[fold:]) +print('

This table can be generated with ./ci.sh benchmark -a -html.

') +print("
") + +EOF +} + benchmark() { case "$1" in -d) shift target="${1?Need to provide a target branch argument}" - old=`mktemp --suffix=-${target}` + old=`fmktemp ${target}` bench "${target}" "${old}" - new=`mktemp --suffix=-HEAD` + new=`fmktemp HEAD` bench HEAD "${new}" benchstat "${old}" "${new}" @@ -168,18 +233,24 @@ benchmark() { -a) shift - v2stats=`mktemp -t go-toml-v2` + v2stats=`fmktemp go-toml-v2` bench HEAD "${v2stats}" "github.com/pelletier/go-toml/v2" - v1stats=`mktemp -t go-toml-v1` + v1stats=`fmktemp go-toml-v1` bench HEAD "${v1stats}" "github.com/pelletier/go-toml" - bsstats=`mktemp -t bs-toml` + bsstats=`fmktemp bs-toml` bench HEAD "${bsstats}" "github.com/BurntSushi/toml" cp "${v2stats}" go-toml-v2.txt cp "${v1stats}" go-toml-v1.txt cp "${bsstats}" bs-toml.txt - benchstat -geomean go-toml-v2.txt go-toml-v1.txt bs-toml.txt + if [ "$1" = "-html" ]; then + tmpcsv=`fmktemp csv` + benchstat -csv -geomean go-toml-v2.txt go-toml-v1.txt bs-toml.txt > $tmpcsv + benchstathtml $tmpcsv + else + benchstat -geomean go-toml-v2.txt go-toml-v1.txt bs-toml.txt + fi rm -f go-toml-v2.txt go-toml-v1.txt bs-toml.txt return $? From b0d6c62255d6f92918be9d77ccfcae8ee2565290 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Tue, 1 Jun 2021 09:51:59 -0400 Subject: [PATCH 204/228] Don't use bytes.Buffer when not necessary (#549) When parsing strings, they can be referenced directly from the document when they don't contain escaped characters. This avoids paying to cost of allocating (and sometimes growing) the bytes buffer unecessarily. --- parser.go | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/parser.go b/parser.go index b4caf737..aa97e2e5 100644 --- a/parser.go +++ b/parser.go @@ -511,8 +511,6 @@ func (p *parser) parseMultilineBasicString(b []byte) ([]byte, []byte, error) { return nil, nil, err } - var builder bytes.Buffer - i := 3 // skip the immediate new line @@ -522,6 +520,21 @@ func (p *parser) parseMultilineBasicString(b []byte) ([]byte, []byte, error) { i += 2 } + // fast path + startIdx := i + endIdx := len(token) - len(`"""`) + for ; i < endIdx; i++ { + if token[i] == '\\' { + break + } + } + if i == endIdx { + return token[startIdx:endIdx], rest, nil + } + + var builder bytes.Buffer + builder.Write(token[startIdx:i]) + // The scanner ensures that the token starts and ends with quotes and that // escapes are balanced. for ; i < len(token)-3; i++ { @@ -673,11 +686,25 @@ func (p *parser) parseBasicString(b []byte) ([]byte, []byte, error) { return nil, nil, err } + // fast path + i := len(`"`) + startIdx := i + endIdx := len(token) - len(`"`) + for ; i < endIdx; i++ { + if token[i] == '\\' { + break + } + } + if i == endIdx { + return token[startIdx:endIdx], rest, nil + } + var builder bytes.Buffer + builder.Write(token[startIdx:i]) // The scanner ensures that the token starts and ends with quotes and that // escapes are balanced. - for i := 1; i < len(token)-1; i++ { + for ; i < len(token)-1; i++ { c := token[i] if c == '\\' { i++ From f3bb20ea79ea197b44bbd3f675802b55e34eee23 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Wed, 2 Jun 2021 09:29:19 -0400 Subject: [PATCH 205/228] Benchmark marshal (#550) --- README.md | 31 +++-- benchmark/benchmark_test.go | 247 ++++++++++++++++++++++++++++-------- ci.sh | 15 ++- marshaler.go | 3 + unmarshaler.go | 6 +- unmarshaler_test.go | 16 ++- 6 files changed, 243 insertions(+), 75 deletions(-) diff --git a/README.md b/README.md index b2f3312a..5a03665a 100644 --- a/README.md +++ b/README.md @@ -156,14 +156,17 @@ Execution time speedup compared to other Go TOML libraries: Benchmarkgo-toml v1BurntSushi/toml - HugoFrontMatter2.6x2.2x - ReferenceFile/map2.8x3.0x - ReferenceFile/struct5.4x6.2x + Marshal/HugoFrontMatter1.9x1.9x + Marshal/ReferenceFile/map1.7x1.9x + Marshal/ReferenceFile/struct2.7x2.9x + Unmarshal/HugoFrontMatter2.9x2.4x + Unmarshal/ReferenceFile/map3.1x3.0x + Unmarshal/ReferenceFile/struct5.5x5.8x
See more -

The table above has the results of the most common use-cases. The table -below contains the results of all benchmarks, including unrealistic ones. is +

The table above has the results of the most common use-cases. The table below +contains the results of all benchmarks, including unrealistic ones. It is provided for completeness.

@@ -171,14 +174,16 @@ provided for completeness.

- - - - - - - - + + + + + + + + + +
Benchmarkgo-toml v1BurntSushi/toml
UnmarshalSimple/map3.8x2.4x
UnmarshalSimple/struct5.4x3.1x
UnmarshalDataset/example2.8x2.0x
UnmarshalDataset/code1.8x2.2x
UnmarshalDataset/twitter2.5x1.8x
UnmarshalDataset/citm_catalog1.9x1.2x
UnmarshalDataset/config3.0x2.5x
[Geo mean]3.0x2.4x
Marshal/SimpleDocument/map1.8x2.4x
Marshal/SimpleDocument/struct2.7x3.5x
Unmarshal/SimpleDocument/map4.3x2.4x
Unmarshal/SimpleDocument/struct5.8x3.3x
UnmarshalDataset/example3.1x2.2x
UnmarshalDataset/code1.8x2.1x
UnmarshalDataset/twitter2.7x1.9x
UnmarshalDataset/citm_catalog1.8x1.2x
UnmarshalDataset/config3.4x2.8x
[Geo mean]2.8x2.5x

This table can be generated with ./ci.sh benchmark -a -html.

diff --git a/benchmark/benchmark_test.go b/benchmark/benchmark_test.go index 6d90571b..a51476cd 100644 --- a/benchmark/benchmark_test.go +++ b/benchmark/benchmark_test.go @@ -1,6 +1,7 @@ package benchmark_test import ( + "bytes" "io/ioutil" "testing" "time" @@ -21,15 +22,101 @@ func TestUnmarshalSimple(t *testing.T) { } } -func BenchmarkUnmarshalSimple(b *testing.B) { - doc := []byte(`A = "hello"`) +func BenchmarkUnmarshal(b *testing.B) { + b.Run("SimpleDocument", func(b *testing.B) { + doc := []byte(`A = "hello"`) + + b.Run("struct", func(b *testing.B) { + b.SetBytes(int64(len(doc))) + b.ReportAllocs() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + d := struct { + A string + }{} + + err := toml.Unmarshal(doc, &d) + if err != nil { + panic(err) + } + } + }) + + b.Run("map", func(b *testing.B) { + b.SetBytes(int64(len(doc))) + b.ReportAllocs() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + d := map[string]interface{}{} + err := toml.Unmarshal(doc, &d) + if err != nil { + panic(err) + } + } + }) + }) + + b.Run("ReferenceFile", func(b *testing.B) { + bytes, err := ioutil.ReadFile("benchmark.toml") + if err != nil { + b.Fatal(err) + } + + b.Run("struct", func(b *testing.B) { + b.SetBytes(int64(len(bytes))) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + d := benchmarkDoc{} + err := toml.Unmarshal(bytes, &d) + if err != nil { + panic(err) + } + } + }) + + b.Run("map", func(b *testing.B) { + b.SetBytes(int64(len(bytes))) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + d := map[string]interface{}{} + err := toml.Unmarshal(bytes, &d) + if err != nil { + panic(err) + } + } + }) + }) - b.Run("struct", func(b *testing.B) { - b.SetBytes(int64(len(doc))) + b.Run("HugoFrontMatter", func(b *testing.B) { + b.SetBytes(int64(len(hugoFrontMatterbytes))) b.ReportAllocs() b.ResetTimer() - for i := 0; i < b.N; i++ { + d := map[string]interface{}{} + err := toml.Unmarshal(hugoFrontMatterbytes, &d) + if err != nil { + panic(err) + } + } + }) +} + +func marshal(v interface{}) ([]byte, error) { + var b bytes.Buffer + enc := toml.NewEncoder(&b) + err := enc.Encode(v) + return b.Bytes(), err +} + +func BenchmarkMarshal(b *testing.B) { + b.Run("SimpleDocument", func(b *testing.B) { + doc := []byte(`A = "hello"`) + + b.Run("struct", func(b *testing.B) { d := struct { A string }{} @@ -38,21 +125,114 @@ func BenchmarkUnmarshalSimple(b *testing.B) { if err != nil { panic(err) } + + b.ReportAllocs() + b.ResetTimer() + + var out []byte + + for i := 0; i < b.N; i++ { + out, err = marshal(d) + if err != nil { + panic(err) + } + } + + b.SetBytes(int64(len(out))) + }) + + b.Run("map", func(b *testing.B) { + d := map[string]interface{}{} + err := toml.Unmarshal(doc, &d) + if err != nil { + panic(err) + } + + b.ReportAllocs() + b.ResetTimer() + + var out []byte + + for i := 0; i < b.N; i++ { + out, err = marshal(d) + if err != nil { + panic(err) + } + } + + b.SetBytes(int64(len(out))) + }) + }) + + b.Run("ReferenceFile", func(b *testing.B) { + bytes, err := ioutil.ReadFile("benchmark.toml") + if err != nil { + b.Fatal(err) } + + b.Run("struct", func(b *testing.B) { + d := benchmarkDoc{} + err := toml.Unmarshal(bytes, &d) + if err != nil { + panic(err) + } + b.ReportAllocs() + b.ResetTimer() + + var out []byte + + for i := 0; i < b.N; i++ { + out, err = marshal(d) + if err != nil { + panic(err) + } + } + + b.SetBytes(int64(len(out))) + }) + + b.Run("map", func(b *testing.B) { + d := map[string]interface{}{} + err := toml.Unmarshal(bytes, &d) + if err != nil { + panic(err) + } + + b.ReportAllocs() + b.ResetTimer() + + var out []byte + for i := 0; i < b.N; i++ { + out, err = marshal(d) + if err != nil { + panic(err) + } + } + + b.SetBytes(int64(len(out))) + }) }) - b.Run("map", func(b *testing.B) { - b.SetBytes(int64(len(doc))) + b.Run("HugoFrontMatter", func(b *testing.B) { + d := map[string]interface{}{} + err := toml.Unmarshal(hugoFrontMatterbytes, &d) + if err != nil { + panic(err) + } + b.ReportAllocs() b.ResetTimer() + var out []byte + for i := 0; i < b.N; i++ { - d := map[string]interface{}{} - err := toml.Unmarshal(doc, &d) + out, err = marshal(d) if err != nil { panic(err) } } + + b.SetBytes(int64(len(out))) }) } @@ -163,40 +343,7 @@ type benchmarkDoc struct { } } -func BenchmarkReferenceFile(b *testing.B) { - bytes, err := ioutil.ReadFile("benchmark.toml") - if err != nil { - b.Fatal(err) - } - - b.Run("struct", func(b *testing.B) { - b.SetBytes(int64(len(bytes))) - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - d := benchmarkDoc{} - err := toml.Unmarshal(bytes, &d) - if err != nil { - panic(err) - } - } - }) - - b.Run("map", func(b *testing.B) { - b.SetBytes(int64(len(bytes))) - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - d := map[string]interface{}{} - err := toml.Unmarshal(bytes, &d) - if err != nil { - panic(err) - } - } - }) -} - -func TestReferenceFile(t *testing.T) { +func TestUnmarshalReferenceFile(t *testing.T) { bytes, err := ioutil.ReadFile("benchmark.toml") require.NoError(t, err) d := benchmarkDoc{} @@ -483,8 +630,7 @@ trimmed in raw strings. require.Equal(t, expected, d) } -func BenchmarkHugoFrontMatter(b *testing.B) { - bytes := []byte(` +var hugoFrontMatterbytes = []byte(` categories = ["Development", "VIM"] date = "2012-04-06" description = "spf13-vim is a cross platform distribution of vim plugins and resources for Vim." @@ -506,14 +652,3 @@ show_comments = false [cascade._target] kind = "section" `) - b.SetBytes(int64(len(bytes))) - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - d := map[string]interface{}{} - err := toml.Unmarshal(bytes, &d) - if err != nil { - panic(err) - } - } -} diff --git a/ci.sh b/ci.sh index 22cda0ad..3918f531 100755 --- a/ci.sh +++ b/ci.sh @@ -204,11 +204,18 @@ def printtable(data): """) -fold = 3 -printtable(results[:fold]) +def match(x): + return "ReferenceFile" in x[0] or "HugoFrontMatter" in x[0] + +above = [x for x in results if match(x)] +below = [x for x in results if not match(x)] + +printtable(above) print("
See more") -print('

The table above has the results of the most common use-cases. The table below contains the results of all benchmarks, including unrealistic ones. is provided for completeness.

') -printtable(results[fold:]) +print("""

The table above has the results of the most common use-cases. The table below +contains the results of all benchmarks, including unrealistic ones. It is +provided for completeness.

""") +printtable(below) print('

This table can be generated with ./ci.sh benchmark -a -html.

') print("
") diff --git a/marshaler.go b/marshaler.go index 0d8ed9c5..3d87dab6 100644 --- a/marshaler.go +++ b/marshaler.go @@ -641,6 +641,9 @@ func (enc *Encoder) encodeTableInline(b []byte, ctx encoderCtx, t table) ([]byte } func willConvertToTable(ctx encoderCtx, v reflect.Value) bool { + if !v.IsValid() { + return false + } if v.Type() == timeType || v.Type().Implements(textMarshalerType) { return false } diff --git a/unmarshaler.go b/unmarshaler.go index 6dcb5c5c..a3280454 100644 --- a/unmarshaler.go +++ b/unmarshaler.go @@ -290,7 +290,11 @@ func (d *decoder) handleArrayTableCollectionLast(key ast.Iterator, v reflect.Val return v, nil case reflect.Slice: - elem := reflect.New(v.Type().Elem()).Elem() + elemType := v.Type().Elem() + if elemType.Kind() == reflect.Interface { + elemType = mapStringInterfaceType + } + elem := reflect.New(elemType).Elem() elem2, err := d.handleArrayTable(key, elem) if err != nil { return reflect.Value{}, err diff --git a/unmarshaler_test.go b/unmarshaler_test.go index f3deb238..e14c9be0 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -688,7 +688,7 @@ B = "data"`, "Name": "Hammer", "Sku": int64(738594937), }, - nil, + map[string]interface{}(nil), map[string]interface{}{ "Name": "Nail", "Sku": int64(284758393), @@ -1201,6 +1201,20 @@ B = "data"`, } }, }, + { + desc: "empty array table in interface{}", + input: `[[products]]`, + gen: func() test { + return test{ + target: &map[string]interface{}{}, + expected: &map[string]interface{}{ + "products": []interface{}{ + map[string]interface{}(nil), + }, + }, + } + }, + }, { desc: "into map with invalid key type", input: `A = "hello"`, From 618f0181ac76015c3efdaf8b8ab5a468293f6e82 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Thu, 3 Jun 2021 21:48:51 -0400 Subject: [PATCH 206/228] AST Tweaks (#551) * Use pointers instead of copying around ast.Node Node is a 56B struct that is constantly in the hot path. Passing nodes around by copy had a cost that started to add up. This change replaces them by pointers. Using unsafe pointer arithmetic and converting sibling/child indexes to relative offsets, it removes the need to carry around a pointer to the root of the tree. This saves 8B per Node. This space will be used to store an extra []byte slice to provide contextual error handling on all nodes, including the ones whose data is different than the raw input (for example: strings with escaped characters), while staying under the size of a cache line. * Remove conditional * Add Raw to track range in data for parsed values * Simplify reference tracking --- README.md | 32 +++--- errors.go | 4 +- internal/ast/ast.go | 66 ++++++----- internal/ast/builder.go | 31 ++--- .../{unsafe/unsafe.go => danger/danger.go} | 8 +- .../unsafe_test.go => danger/danger_test.go} | 28 +++-- internal/tracker/key.go | 8 +- internal/tracker/seen.go | 8 +- parser.go | 106 ++++++++++-------- parser_test.go | 6 +- strict.go | 18 +-- unmarshaler.go | 41 ++++--- unmarshaler_test.go | 48 ++++++++ 13 files changed, 239 insertions(+), 165 deletions(-) rename internal/{unsafe/unsafe.go => danger/danger.go} (84%) rename internal/{unsafe/unsafe_test.go => danger/danger_test.go} (82%) diff --git a/README.md b/README.md index 5a03665a..698121ba 100644 --- a/README.md +++ b/README.md @@ -156,12 +156,12 @@ Execution time speedup compared to other Go TOML libraries: Benchmarkgo-toml v1BurntSushi/toml - Marshal/HugoFrontMatter1.9x1.9x - Marshal/ReferenceFile/map1.7x1.9x - Marshal/ReferenceFile/struct2.7x2.9x - Unmarshal/HugoFrontMatter2.9x2.4x - Unmarshal/ReferenceFile/map3.1x3.0x - Unmarshal/ReferenceFile/struct5.5x5.8x + Marshal/HugoFrontMatter2.0x2.0x + Marshal/ReferenceFile/map1.8x2.0x + Marshal/ReferenceFile/struct2.7x2.7x + Unmarshal/HugoFrontMatter3.0x2.6x + Unmarshal/ReferenceFile/map3.0x3.1x + Unmarshal/ReferenceFile/struct5.9x6.6x
See more @@ -174,16 +174,16 @@ provided for completeness.

Benchmarkgo-toml v1BurntSushi/toml - Marshal/SimpleDocument/map1.8x2.4x - Marshal/SimpleDocument/struct2.7x3.5x - Unmarshal/SimpleDocument/map4.3x2.4x - Unmarshal/SimpleDocument/struct5.8x3.3x - UnmarshalDataset/example3.1x2.2x - UnmarshalDataset/code1.8x2.1x - UnmarshalDataset/twitter2.7x1.9x - UnmarshalDataset/citm_catalog1.8x1.2x - UnmarshalDataset/config3.4x2.8x - [Geo mean]2.8x2.5x + Marshal/SimpleDocument/map1.7x2.1x + Marshal/SimpleDocument/struct2.6x2.9x + Unmarshal/SimpleDocument/map4.1x2.9x + Unmarshal/SimpleDocument/struct6.3x4.1x + UnmarshalDataset/example3.5x2.4x + UnmarshalDataset/code2.2x2.8x + UnmarshalDataset/twitter2.8x2.1x + UnmarshalDataset/citm_catalog2.3x1.5x + UnmarshalDataset/config4.2x3.2x + [Geo mean]3.0x2.7x

This table can be generated with ./ci.sh benchmark -a -html.

diff --git a/errors.go b/errors.go index 712765bb..a00924b5 100644 --- a/errors.go +++ b/errors.go @@ -5,7 +5,7 @@ import ( "strconv" "strings" - "github.com/pelletier/go-toml/v2/internal/unsafe" + "github.com/pelletier/go-toml/v2/internal/danger" ) // DecodeError represents an error encountered during the parsing or decoding @@ -105,7 +105,7 @@ func (e *DecodeError) Key() Key { // highlight can be freely deallocated. //nolint:funlen func wrapDecodeError(document []byte, de *decodeError) *DecodeError { - offset := unsafe.SubsliceOffset(document, de.highlight) + offset := danger.SubsliceOffset(document, de.highlight) errMessage := de.Error() errLine, errColumn := positionAtEnd(document[:offset]) diff --git a/internal/ast/ast.go b/internal/ast/ast.go index f9059d88..82c1cb9c 100644 --- a/internal/ast/ast.go +++ b/internal/ast/ast.go @@ -2,6 +2,9 @@ package ast import ( "fmt" + "unsafe" + + "github.com/pelletier/go-toml/v2/internal/danger" ) // Iterator starts uninitialized, you need to call Next() first. @@ -14,7 +17,7 @@ import ( // } type Iterator struct { started bool - node Node + node *Node } // Next moves the iterator forward and returns true if points to a node, false @@ -31,11 +34,11 @@ func (c *Iterator) Next() bool { // IsLast returns true if the current node of the iterator is the last one. // Subsequent call to Next() will return false. func (c *Iterator) IsLast() bool { - return c.node.next <= 0 + return c.node.next == 0 } // Node returns a copy of the node pointed at by the iterator. -func (c *Iterator) Node() Node { +func (c *Iterator) Node() *Node { return c.node } @@ -50,14 +53,13 @@ type Root struct { func (r *Root) Iterator() Iterator { it := Iterator{} if len(r.nodes) > 0 { - it.node = r.nodes[0] + it.node = &r.nodes[0] } return it } -func (r *Root) at(idx int) Node { - // TODO: unsafe to point to the node directly - return r.nodes[idx] +func (r *Root) at(idx Reference) *Node { + return &r.nodes[idx] } // Arrays have one child per element in the array. @@ -69,42 +71,48 @@ func (r *Root) at(idx int) Node { // children []Node type Node struct { Kind Kind - Data []byte // Raw bytes from the input + Raw Range // Raw bytes from the input. + Data []byte // Node value (could be either allocated or referencing the input). + + // References to other nodes, as offsets in the backing array from this + // node. References can go backward, so those can be negative. + next int // 0 if last element + child int // 0 if no child +} - // next idx (in the root array). 0 if last of the collection. - next int - // child idx (in the root array). 0 if no child. - child int - // pointer to the root array - root *Root +type Range struct { + Offset uint32 + Length uint32 } // Next returns a copy of the next node, or an invalid Node if there is no // next node. -func (n Node) Next() Node { - if n.next <= 0 { - return noNode +func (n *Node) Next() *Node { + if n.next == 0 { + return nil } - return n.root.at(n.next) + ptr := unsafe.Pointer(n) + size := unsafe.Sizeof(Node{}) + return (*Node)(danger.Stride(ptr, size, n.next)) } // Child returns a copy of the first child node of this node. Other children // can be accessed calling Next on the first child. // Returns an invalid Node if there is none. -func (n Node) Child() Node { - if n.child <= 0 { - return noNode +func (n *Node) Child() *Node { + if n.child == 0 { + return nil } - return n.root.at(n.child) + ptr := unsafe.Pointer(n) + size := unsafe.Sizeof(Node{}) + return (*Node)(danger.Stride(ptr, size, n.child)) } // Valid returns true if the node's kind is set (not to Invalid). -func (n Node) Valid() bool { - return n.Kind != Invalid +func (n *Node) Valid() bool { + return n != nil } -var noNode = Node{} - // Key returns the child nodes making the Key on a supported node. Panics // otherwise. // They are guaranteed to be all be of the Kind Key. A simple key would return @@ -127,13 +135,13 @@ func (n *Node) Key() Iterator { // Value returns a pointer to the value node of a KeyValue. // Guaranteed to be non-nil. // Panics if not called on a KeyValue node, or if the Children are malformed. -func (n Node) Value() Node { - assertKind(KeyValue, n) +func (n *Node) Value() *Node { + assertKind(KeyValue, *n) return n.Child() } // Children returns an iterator over a node's children. -func (n Node) Children() Iterator { +func (n *Node) Children() Iterator { return Iterator{node: n.Child()} } diff --git a/internal/ast/builder.go b/internal/ast/builder.go index 796b7f1d..120f16e5 100644 --- a/internal/ast/builder.go +++ b/internal/ast/builder.go @@ -1,12 +1,11 @@ package ast -type Reference struct { - idx int - set bool -} +type Reference int + +const InvalidReference Reference = -1 func (r Reference) Valid() bool { - return r.set + return r != InvalidReference } type Builder struct { @@ -18,8 +17,8 @@ func (b *Builder) Tree() *Root { return &b.tree } -func (b *Builder) NodeAt(ref Reference) Node { - return b.tree.at(ref.idx) +func (b *Builder) NodeAt(ref Reference) *Node { + return b.tree.at(ref) } func (b *Builder) Reset() { @@ -28,33 +27,25 @@ func (b *Builder) Reset() { } func (b *Builder) Push(n Node) Reference { - n.root = &b.tree b.lastIdx = len(b.tree.nodes) b.tree.nodes = append(b.tree.nodes, n) - return Reference{ - idx: b.lastIdx, - set: true, - } + return Reference(b.lastIdx) } func (b *Builder) PushAndChain(n Node) Reference { - n.root = &b.tree newIdx := len(b.tree.nodes) b.tree.nodes = append(b.tree.nodes, n) if b.lastIdx >= 0 { - b.tree.nodes[b.lastIdx].next = newIdx + b.tree.nodes[b.lastIdx].next = newIdx - b.lastIdx } b.lastIdx = newIdx - return Reference{ - idx: b.lastIdx, - set: true, - } + return Reference(b.lastIdx) } func (b *Builder) AttachChild(parent Reference, child Reference) { - b.tree.nodes[parent.idx].child = child.idx + b.tree.nodes[parent].child = int(child) - int(parent) } func (b *Builder) Chain(from Reference, to Reference) { - b.tree.nodes[from.idx].next = to.idx + b.tree.nodes[from].next = int(to) - int(from) } diff --git a/internal/unsafe/unsafe.go b/internal/danger/danger.go similarity index 84% rename from internal/unsafe/unsafe.go rename to internal/danger/danger.go index 742c6ab2..e38e1131 100644 --- a/internal/unsafe/unsafe.go +++ b/internal/danger/danger.go @@ -1,4 +1,4 @@ -package unsafe +package danger import ( "fmt" @@ -57,3 +57,9 @@ func BytesRange(start []byte, end []byte) []byte { return start[:l] } + +func Stride(ptr unsafe.Pointer, size uintptr, offset int) unsafe.Pointer { + // TODO: replace with unsafe.Add when Go 1.17 is released + // https://github.com/golang/go/issues/40481 + return unsafe.Pointer(uintptr(ptr) + uintptr(int(size)*offset)) +} diff --git a/internal/unsafe/unsafe_test.go b/internal/danger/danger_test.go similarity index 82% rename from internal/unsafe/unsafe_test.go rename to internal/danger/danger_test.go index 5462d086..cb975f84 100644 --- a/internal/unsafe/unsafe_test.go +++ b/internal/danger/danger_test.go @@ -1,15 +1,16 @@ -package unsafe_test +package danger_test import ( "testing" + "unsafe" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/pelletier/go-toml/v2/internal/unsafe" + "github.com/pelletier/go-toml/v2/internal/danger" ) -func TestUnsafeSubsliceOffsetValid(t *testing.T) { +func TestSubsliceOffsetValid(t *testing.T) { examples := []struct { desc string test func() ([]byte, []byte) @@ -28,13 +29,13 @@ func TestUnsafeSubsliceOffsetValid(t *testing.T) { for _, e := range examples { t.Run(e.desc, func(t *testing.T) { d, s := e.test() - offset := unsafe.SubsliceOffset(d, s) + offset := danger.SubsliceOffset(d, s) assert.Equal(t, e.offset, offset) }) } } -func TestUnsafeSubsliceOffsetInvalid(t *testing.T) { +func TestSubsliceOffsetInvalid(t *testing.T) { examples := []struct { desc string test func() ([]byte, []byte) @@ -72,13 +73,22 @@ func TestUnsafeSubsliceOffsetInvalid(t *testing.T) { t.Run(e.desc, func(t *testing.T) { d, s := e.test() require.Panics(t, func() { - unsafe.SubsliceOffset(d, s) + danger.SubsliceOffset(d, s) }) }) } } -func TestUnsafeBytesRange(t *testing.T) { +func TestStride(t *testing.T) { + a := []byte{1, 2, 3, 4} + x := &a[1] + n := (*byte)(danger.Stride(unsafe.Pointer(x), unsafe.Sizeof(byte(0)), 1)) + require.Equal(t, &a[2], n) + n = (*byte)(danger.Stride(unsafe.Pointer(x), unsafe.Sizeof(byte(0)), -1)) + require.Equal(t, &a[0], n) +} + +func TestBytesRange(t *testing.T) { type fn = func() ([]byte, []byte) examples := []struct { desc string @@ -157,10 +167,10 @@ func TestUnsafeBytesRange(t *testing.T) { start, end := e.test() if e.expected == nil { require.Panics(t, func() { - unsafe.BytesRange(start, end) + danger.BytesRange(start, end) }) } else { - res := unsafe.BytesRange(start, end) + res := danger.BytesRange(start, end) require.Equal(t, e.expected, res) } }) diff --git a/internal/tracker/key.go b/internal/tracker/key.go index be99f720..7c148f48 100644 --- a/internal/tracker/key.go +++ b/internal/tracker/key.go @@ -11,19 +11,19 @@ type KeyTracker struct { } // UpdateTable sets the state of the tracker with the AST table node. -func (t *KeyTracker) UpdateTable(node ast.Node) { +func (t *KeyTracker) UpdateTable(node *ast.Node) { t.reset() t.Push(node) } // UpdateArrayTable sets the state of the tracker with the AST array table node. -func (t *KeyTracker) UpdateArrayTable(node ast.Node) { +func (t *KeyTracker) UpdateArrayTable(node *ast.Node) { t.reset() t.Push(node) } // Push the given key on the stack. -func (t *KeyTracker) Push(node ast.Node) { +func (t *KeyTracker) Push(node *ast.Node) { it := node.Key() for it.Next() { t.k = append(t.k, string(it.Node().Data)) @@ -31,7 +31,7 @@ func (t *KeyTracker) Push(node ast.Node) { } // Pop key from stack. -func (t *KeyTracker) Pop(node ast.Node) { +func (t *KeyTracker) Pop(node *ast.Node) { it := node.Key() for it.Next() { t.k = t.k[:len(t.k)-1] diff --git a/internal/tracker/seen.go b/internal/tracker/seen.go index 0f6bd01b..af702418 100644 --- a/internal/tracker/seen.go +++ b/internal/tracker/seen.go @@ -104,7 +104,7 @@ func (s *SeenTracker) create(parentIdx int, name []byte, kind keyKind, explicit // CheckExpression takes a top-level node and checks that it does not contain keys // that have been seen in previous calls, and validates that types are consistent. -func (s *SeenTracker) CheckExpression(node ast.Node) error { +func (s *SeenTracker) CheckExpression(node *ast.Node) error { if s.entries == nil { // s.entries = make([]entry, 0, 8) // Skip ID = 0 to remove the confusion between nodes whose parent has @@ -125,7 +125,7 @@ func (s *SeenTracker) CheckExpression(node ast.Node) error { } } -func (s *SeenTracker) checkTable(node ast.Node) error { +func (s *SeenTracker) checkTable(node *ast.Node) error { it := node.Key() parentIdx := -1 @@ -169,7 +169,7 @@ func (s *SeenTracker) checkTable(node ast.Node) error { return nil } -func (s *SeenTracker) checkArrayTable(node ast.Node) error { +func (s *SeenTracker) checkArrayTable(node *ast.Node) error { it := node.Key() parentIdx := -1 @@ -207,7 +207,7 @@ func (s *SeenTracker) checkArrayTable(node ast.Node) error { return nil } -func (s *SeenTracker) checkKeyValue(node ast.Node) error { +func (s *SeenTracker) checkKeyValue(node *ast.Node) error { it := node.Key() parentIdx := s.currentIdx diff --git a/parser.go b/parser.go index aa97e2e5..453b08d1 100644 --- a/parser.go +++ b/parser.go @@ -5,6 +5,7 @@ import ( "strconv" "github.com/pelletier/go-toml/v2/internal/ast" + "github.com/pelletier/go-toml/v2/internal/danger" ) type parser struct { @@ -16,9 +17,20 @@ type parser struct { first bool } +func (p *parser) Range(b []byte) ast.Range { + return ast.Range{ + Offset: uint32(danger.SubsliceOffset(p.data, b)), + Length: uint32(len(b)), + } +} + +func (p *parser) Raw(raw ast.Range) []byte { + return p.data[raw.Offset : raw.Offset+raw.Length] +} + func (p *parser) Reset(b []byte) { p.builder.Reset() - p.ref = ast.Reference{} + p.ref = ast.InvalidReference p.data = b p.left = b p.err = nil @@ -32,7 +44,7 @@ func (p *parser) NextExpression() bool { } p.builder.Reset() - p.ref = ast.Reference{} + p.ref = ast.InvalidReference for { if len(p.left) == 0 || p.err != nil { @@ -61,7 +73,7 @@ func (p *parser) NextExpression() bool { } } -func (p *parser) Expression() ast.Node { +func (p *parser) Expression() *ast.Node { return p.builder.NodeAt(p.ref) } @@ -86,7 +98,7 @@ func (p *parser) parseExpression(b []byte) (ast.Reference, []byte, error) { // expression = ws [ comment ] // expression =/ ws keyval ws [ comment ] // expression =/ ws table ws [ comment ] - var ref ast.Reference + ref := ast.InvalidReference b = p.parseWhitespace(b) @@ -197,7 +209,7 @@ func (p *parser) parseKeyval(b []byte) (ast.Reference, []byte, error) { key, b, err := p.parseKey(b) if err != nil { - return ast.Reference{}, nil, err + return ast.InvalidReference, nil, err } // keyval-sep = ws %x3D ws ; = @@ -205,12 +217,12 @@ func (p *parser) parseKeyval(b []byte) (ast.Reference, []byte, error) { b = p.parseWhitespace(b) if len(b) == 0 { - return ast.Reference{}, nil, newDecodeError(b, "expected = after a key, but the document ends there") + return ast.InvalidReference, nil, newDecodeError(b, "expected = after a key, but the document ends there") } b, err = expect('=', b) if err != nil { - return ast.Reference{}, nil, err + return ast.InvalidReference, nil, err } b = p.parseWhitespace(b) @@ -229,7 +241,7 @@ func (p *parser) parseKeyval(b []byte) (ast.Reference, []byte, error) { //nolint:cyclop,funlen func (p *parser) parseVal(b []byte) (ast.Reference, []byte, error) { // val = string / boolean / array / inline-table / date-time / float / integer - var ref ast.Reference + ref := ast.InvalidReference if len(b) == 0 { return ref, nil, newDecodeError(b, "expected value, not eof") @@ -240,32 +252,36 @@ func (p *parser) parseVal(b []byte) (ast.Reference, []byte, error) { switch c { case '"': + var raw []byte var v []byte if scanFollowsMultilineBasicStringDelimiter(b) { - v, b, err = p.parseMultilineBasicString(b) + raw, v, b, err = p.parseMultilineBasicString(b) } else { - v, b, err = p.parseBasicString(b) + raw, v, b, err = p.parseBasicString(b) } if err == nil { ref = p.builder.Push(ast.Node{ Kind: ast.String, + Raw: p.Range(raw), Data: v, }) } return ref, b, err case '\'': + var raw []byte var v []byte if scanFollowsMultilineLiteralStringDelimiter(b) { - v, b, err = p.parseMultilineLiteralString(b) + raw, v, b, err = p.parseMultilineLiteralString(b) } else { - v, b, err = p.parseLiteralString(b) + raw, v, b, err = p.parseLiteralString(b) } if err == nil { ref = p.builder.Push(ast.Node{ Kind: ast.String, + Raw: p.Range(raw), Data: v, }) } @@ -310,13 +326,13 @@ func atmost(b []byte, n int) []byte { return b[:n] } -func (p *parser) parseLiteralString(b []byte) ([]byte, []byte, error) { +func (p *parser) parseLiteralString(b []byte) ([]byte, []byte, []byte, error) { v, rest, err := scanLiteralString(b) if err != nil { - return nil, nil, err + return nil, nil, nil, err } - return v[1 : len(v)-1], rest, nil + return v, v[1 : len(v)-1], rest, nil } func (p *parser) parseInlineTable(b []byte) (ast.Reference, []byte, error) { @@ -476,10 +492,10 @@ func (p *parser) parseOptionalWhitespaceCommentNewline(b []byte) ([]byte, error) return b, nil } -func (p *parser) parseMultilineLiteralString(b []byte) ([]byte, []byte, error) { +func (p *parser) parseMultilineLiteralString(b []byte) ([]byte, []byte, []byte, error) { token, rest, err := scanMultilineLiteralString(b) if err != nil { - return nil, nil, err + return nil, nil, nil, err } i := 3 @@ -491,11 +507,11 @@ func (p *parser) parseMultilineLiteralString(b []byte) ([]byte, []byte, error) { i += 2 } - return token[i : len(token)-3], rest, err + return token, token[i : len(token)-3], rest, err } //nolint:funlen,gocognit,cyclop -func (p *parser) parseMultilineBasicString(b []byte) ([]byte, []byte, error) { +func (p *parser) parseMultilineBasicString(b []byte) ([]byte, []byte, []byte, error) { // ml-basic-string = ml-basic-string-delim [ newline ] ml-basic-body // ml-basic-string-delim // ml-basic-string-delim = 3quotation-mark @@ -508,7 +524,7 @@ func (p *parser) parseMultilineBasicString(b []byte) ([]byte, []byte, error) { // mlb-escaped-nl = escape ws newline *( wschar / newline ) token, rest, err := scanMultilineBasicString(b) if err != nil { - return nil, nil, err + return nil, nil, nil, err } i := 3 @@ -529,7 +545,7 @@ func (p *parser) parseMultilineBasicString(b []byte) ([]byte, []byte, error) { } } if i == endIdx { - return token[startIdx:endIdx], rest, nil + return token, token[startIdx:endIdx], rest, nil } var builder bytes.Buffer @@ -579,7 +595,7 @@ func (p *parser) parseMultilineBasicString(b []byte) ([]byte, []byte, error) { case 'u': x, err := hexToString(atmost(token[i+1:], 4), 4) if err != nil { - return nil, nil, err + return nil, nil, nil, err } builder.WriteString(x) @@ -587,20 +603,20 @@ func (p *parser) parseMultilineBasicString(b []byte) ([]byte, []byte, error) { case 'U': x, err := hexToString(atmost(token[i+1:], 8), 8) if err != nil { - return nil, nil, err + return nil, nil, nil, err } builder.WriteString(x) i += 8 default: - return nil, nil, newDecodeError(token[i:i+1], "invalid escaped character %#U", c) + return nil, nil, nil, newDecodeError(token[i:i+1], "invalid escaped character %#U", c) } } else { builder.WriteByte(c) } } - return builder.Bytes(), rest, nil + return token, builder.Bytes(), rest, nil } func (p *parser) parseKey(b []byte) (ast.Reference, []byte, error) { @@ -612,13 +628,14 @@ func (p *parser) parseKey(b []byte) (ast.Reference, []byte, error) { // dotted-key = simple-key 1*( dot-sep simple-key ) // // dot-sep = ws %x2E ws ; . Period - key, b, err := p.parseSimpleKey(b) + raw, key, b, err := p.parseSimpleKey(b) if err != nil { - return ast.Reference{}, nil, err + return ast.InvalidReference, nil, err } ref := p.builder.Push(ast.Node{ Kind: ast.Key, + Raw: p.Range(raw), Data: key, }) @@ -627,13 +644,14 @@ func (p *parser) parseKey(b []byte) (ast.Reference, []byte, error) { if len(b) > 0 && b[0] == '.' { b = p.parseWhitespace(b[1:]) - key, b, err = p.parseSimpleKey(b) + raw, key, b, err = p.parseSimpleKey(b) if err != nil { return ref, nil, err } p.builder.PushAndChain(ast.Node{ Kind: ast.Key, + Raw: p.Range(raw), Data: key, }) } else { @@ -644,12 +662,12 @@ func (p *parser) parseKey(b []byte) (ast.Reference, []byte, error) { return ref, b, nil } -func (p *parser) parseSimpleKey(b []byte) (key, rest []byte, err error) { +func (p *parser) parseSimpleKey(b []byte) (raw, key, rest []byte, err error) { // simple-key = quoted-key / unquoted-key // unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _ // quoted-key = basic-string / literal-string if len(b) == 0 { - return nil, nil, newDecodeError(b, "key is incomplete") + return nil, nil, nil, newDecodeError(b, "key is incomplete") } switch { @@ -659,14 +677,14 @@ func (p *parser) parseSimpleKey(b []byte) (key, rest []byte, err error) { return p.parseBasicString(b) case isUnquotedKeyChar(b[0]): key, rest = scanUnquotedKey(b) - return key, rest, nil + return key, key, rest, nil default: - return nil, nil, newDecodeError(b[0:1], "invalid character at start of key: %c", b[0]) + return nil, nil, nil, newDecodeError(b[0:1], "invalid character at start of key: %c", b[0]) } } //nolint:funlen,cyclop -func (p *parser) parseBasicString(b []byte) ([]byte, []byte, error) { +func (p *parser) parseBasicString(b []byte) ([]byte, []byte, []byte, error) { // basic-string = quotation-mark *basic-char quotation-mark // quotation-mark = %x22 ; " // basic-char = basic-unescaped / escaped @@ -683,7 +701,7 @@ func (p *parser) parseBasicString(b []byte) ([]byte, []byte, error) { // escape-seq-char =/ %x55 8HEXDIG ; UXXXXXXXX U+XXXXXXXX token, rest, err := scanBasicString(b) if err != nil { - return nil, nil, err + return nil, nil, nil, err } // fast path @@ -696,7 +714,7 @@ func (p *parser) parseBasicString(b []byte) ([]byte, []byte, error) { } } if i == endIdx { - return token[startIdx:endIdx], rest, nil + return token, token[startIdx:endIdx], rest, nil } var builder bytes.Buffer @@ -726,7 +744,7 @@ func (p *parser) parseBasicString(b []byte) ([]byte, []byte, error) { case 'u': x, err := hexToString(token[i+1:len(token)-1], 4) if err != nil { - return nil, nil, err + return nil, nil, nil, err } builder.WriteString(x) @@ -734,20 +752,20 @@ func (p *parser) parseBasicString(b []byte) ([]byte, []byte, error) { case 'U': x, err := hexToString(token[i+1:len(token)-1], 8) if err != nil { - return nil, nil, err + return nil, nil, nil, err } builder.WriteString(x) i += 8 default: - return nil, nil, newDecodeError(token[i:i+1], "invalid escaped character %#U", c) + return nil, nil, nil, newDecodeError(token[i:i+1], "invalid escaped character %#U", c) } } else { builder.WriteByte(c) } } - return builder.Bytes(), rest, nil + return token, builder.Bytes(), rest, nil } func hexToString(b []byte, length int) (string, error) { @@ -780,7 +798,7 @@ func (p *parser) parseIntOrFloatOrDateTime(b []byte) (ast.Reference, []byte, err switch b[0] { case 'i': if !scanFollowsInf(b) { - return ast.Reference{}, nil, newDecodeError(atmost(b, 3), "expected 'inf'") + return ast.InvalidReference, nil, newDecodeError(atmost(b, 3), "expected 'inf'") } return p.builder.Push(ast.Node{ @@ -789,7 +807,7 @@ func (p *parser) parseIntOrFloatOrDateTime(b []byte) (ast.Reference, []byte, err }), b[3:], nil case 'n': if !scanFollowsNan(b) { - return ast.Reference{}, nil, newDecodeError(atmost(b, 3), "expected 'nan'") + return ast.InvalidReference, nil, newDecodeError(atmost(b, 3), "expected 'nan'") } return p.builder.Push(ast.Node{ @@ -945,7 +963,7 @@ func (p *parser) scanIntOrFloat(b []byte) (ast.Reference, []byte, error) { }), b[i+3:], nil } - return ast.Reference{}, nil, newDecodeError(b[i:i+1], "unexpected character 'i' while scanning for a number") + return ast.InvalidReference, nil, newDecodeError(b[i:i+1], "unexpected character 'i' while scanning for a number") } if c == 'n' { @@ -956,14 +974,14 @@ func (p *parser) scanIntOrFloat(b []byte) (ast.Reference, []byte, error) { }), b[i+3:], nil } - return ast.Reference{}, nil, newDecodeError(b[i:i+1], "unexpected character 'n' while scanning for a number") + return ast.InvalidReference, nil, newDecodeError(b[i:i+1], "unexpected character 'n' while scanning for a number") } break } if i == 0 { - return ast.Reference{}, b, newDecodeError(b, "incomplete number") + return ast.InvalidReference, b, newDecodeError(b, "incomplete number") } kind := ast.Integer diff --git a/parser_test.go b/parser_test.go index fdb4f27e..9fda4299 100644 --- a/parser_test.go +++ b/parser_test.go @@ -9,7 +9,6 @@ import ( //nolint:funlen func TestParser_AST_Numbers(t *testing.T) { - examples := []struct { desc string input string @@ -136,7 +135,6 @@ func TestParser_AST_Numbers(t *testing.T) { for _, e := range examples { e := e t.Run(e.desc, func(t *testing.T) { - p := parser{} p.Reset([]byte(`A = ` + e.input)) p.NextExpression() @@ -167,7 +165,7 @@ type ( } ) -func compareNode(t *testing.T, e astNode, n ast.Node) { +func compareNode(t *testing.T, e astNode, n *ast.Node) { t.Helper() require.Equal(t, e.Kind, n.Kind) require.Equal(t, e.Data, n.Data) @@ -199,7 +197,6 @@ func compareIterator(t *testing.T, expected []astNode, actual ast.Iterator) { //nolint:funlen func TestParser_AST(t *testing.T) { - examples := []struct { desc string input string @@ -338,7 +335,6 @@ func TestParser_AST(t *testing.T) { for _, e := range examples { e := e t.Run(e.desc, func(t *testing.T) { - p := parser{} p.Reset([]byte(e.input)) p.NextExpression() diff --git a/strict.go b/strict.go index ca482c4e..b7830d13 100644 --- a/strict.go +++ b/strict.go @@ -2,8 +2,8 @@ package toml import ( "github.com/pelletier/go-toml/v2/internal/ast" + "github.com/pelletier/go-toml/v2/internal/danger" "github.com/pelletier/go-toml/v2/internal/tracker" - "github.com/pelletier/go-toml/v2/internal/unsafe" ) type strict struct { @@ -15,7 +15,7 @@ type strict struct { missing []decodeError } -func (s *strict) EnterTable(node ast.Node) { +func (s *strict) EnterTable(node *ast.Node) { if !s.Enabled { return } @@ -23,7 +23,7 @@ func (s *strict) EnterTable(node ast.Node) { s.key.UpdateTable(node) } -func (s *strict) EnterArrayTable(node ast.Node) { +func (s *strict) EnterArrayTable(node *ast.Node) { if !s.Enabled { return } @@ -31,7 +31,7 @@ func (s *strict) EnterArrayTable(node ast.Node) { s.key.UpdateArrayTable(node) } -func (s *strict) EnterKeyValue(node ast.Node) { +func (s *strict) EnterKeyValue(node *ast.Node) { if !s.Enabled { return } @@ -39,7 +39,7 @@ func (s *strict) EnterKeyValue(node ast.Node) { s.key.Push(node) } -func (s *strict) ExitKeyValue(node ast.Node) { +func (s *strict) ExitKeyValue(node *ast.Node) { if !s.Enabled { return } @@ -47,7 +47,7 @@ func (s *strict) ExitKeyValue(node ast.Node) { s.key.Pop(node) } -func (s *strict) MissingTable(node ast.Node) { +func (s *strict) MissingTable(node *ast.Node) { if !s.Enabled { return } @@ -59,7 +59,7 @@ func (s *strict) MissingTable(node ast.Node) { }) } -func (s *strict) MissingField(node ast.Node) { +func (s *strict) MissingField(node *ast.Node) { if !s.Enabled { return } @@ -88,7 +88,7 @@ func (s *strict) Error(doc []byte) error { return err } -func keyLocation(node ast.Node) []byte { +func keyLocation(node *ast.Node) []byte { k := node.Key() hasOne := k.Next() @@ -103,5 +103,5 @@ func keyLocation(node ast.Node) []byte { end = k.Node().Data } - return unsafe.BytesRange(start, end) + return danger.BytesRange(start, end) } diff --git a/unmarshaler.go b/unmarshaler.go index a3280454..b5144137 100644 --- a/unmarshaler.go +++ b/unmarshaler.go @@ -106,7 +106,6 @@ func (d *Decoder) Decode(v interface{}) error { type decoder struct { // Which parser instance in use for this decoding session. - // TODO: Think about removing later. p *parser // Flag indicating that the current expression is stashed. @@ -132,7 +131,7 @@ type decoder struct { strict strict } -func (d *decoder) expr() ast.Node { +func (d *decoder) expr() *ast.Node { return d.p.Expression() } @@ -208,7 +207,7 @@ Rules for the unmarshal code: - An "object" is either a struct or a map. */ -func (d *decoder) handleRootExpression(expr ast.Node, v reflect.Value) error { +func (d *decoder) handleRootExpression(expr *ast.Node, v reflect.Value) error { var x reflect.Value var err error @@ -533,7 +532,7 @@ func (d *decoder) handleTablePart(key ast.Iterator, v reflect.Value) (reflect.Va return d.handleKeyPart(key, v, d.handleTable, makeMapStringInterface) } -func tryTextUnmarshaler(node ast.Node, v reflect.Value) (bool, error) { +func (d *decoder) tryTextUnmarshaler(node *ast.Node, v reflect.Value) (bool, error) { if v.Kind() != reflect.Struct { return false, nil } @@ -547,9 +546,7 @@ func tryTextUnmarshaler(node ast.Node, v reflect.Value) (bool, error) { if v.CanAddr() && v.Addr().Type().Implements(textUnmarshalerType) { err := v.Addr().Interface().(encoding.TextUnmarshaler).UnmarshalText(node.Data) if err != nil { - return false, fmt.Errorf("toml: error calling UnmarshalText: %w", err) - // TODO: same as above - // return false, newDecodeError(node.Data, "error calling UnmarshalText: %w", err) + return false, newDecodeError(d.p.Raw(node.Raw), "error calling UnmarshalText: %w", err) } return true, nil @@ -558,12 +555,12 @@ func tryTextUnmarshaler(node ast.Node, v reflect.Value) (bool, error) { return false, nil } -func (d *decoder) handleValue(value ast.Node, v reflect.Value) error { +func (d *decoder) handleValue(value *ast.Node, v reflect.Value) error { for v.Kind() == reflect.Ptr { v = initAndDereferencePointer(v) } - ok, err := tryTextUnmarshaler(value, v) + ok, err := d.tryTextUnmarshaler(value, v) if ok || err != nil { return err } @@ -592,7 +589,7 @@ func (d *decoder) handleValue(value ast.Node, v reflect.Value) error { } } -func (d *decoder) unmarshalArray(array ast.Node, v reflect.Value) error { +func (d *decoder) unmarshalArray(array *ast.Node, v reflect.Value) error { switch v.Kind() { case reflect.Slice: if v.IsNil() { @@ -663,7 +660,7 @@ func (d *decoder) unmarshalArray(array ast.Node, v reflect.Value) error { return nil } -func (d *decoder) unmarshalInlineTable(itable ast.Node, v reflect.Value) error { +func (d *decoder) unmarshalInlineTable(itable *ast.Node, v reflect.Value) error { // Make sure v is an initialized object. switch v.Kind() { case reflect.Map: @@ -699,7 +696,7 @@ func (d *decoder) unmarshalInlineTable(itable ast.Node, v reflect.Value) error { return nil } -func (d *decoder) unmarshalDateTime(value ast.Node, v reflect.Value) error { +func (d *decoder) unmarshalDateTime(value *ast.Node, v reflect.Value) error { dt, err := parseDateTime(value.Data) if err != nil { return err @@ -709,7 +706,7 @@ func (d *decoder) unmarshalDateTime(value ast.Node, v reflect.Value) error { return nil } -func (d *decoder) unmarshalLocalDate(value ast.Node, v reflect.Value) error { +func (d *decoder) unmarshalLocalDate(value *ast.Node, v reflect.Value) error { ld, err := parseLocalDate(value.Data) if err != nil { return err @@ -727,7 +724,7 @@ func (d *decoder) unmarshalLocalDate(value ast.Node, v reflect.Value) error { return nil } -func (d *decoder) unmarshalLocalDateTime(value ast.Node, v reflect.Value) error { +func (d *decoder) unmarshalLocalDateTime(value *ast.Node, v reflect.Value) error { ldt, rest, err := parseLocalDateTime(value.Data) if err != nil { return err @@ -749,7 +746,7 @@ func (d *decoder) unmarshalLocalDateTime(value ast.Node, v reflect.Value) error return nil } -func (d *decoder) unmarshalBool(value ast.Node, v reflect.Value) error { +func (d *decoder) unmarshalBool(value *ast.Node, v reflect.Value) error { b := value.Data[0] == 't' switch v.Kind() { @@ -764,7 +761,7 @@ func (d *decoder) unmarshalBool(value ast.Node, v reflect.Value) error { return nil } -func (d *decoder) unmarshalFloat(value ast.Node, v reflect.Value) error { +func (d *decoder) unmarshalFloat(value *ast.Node, v reflect.Value) error { f, err := parseFloat(value.Data) if err != nil { return err @@ -787,7 +784,7 @@ func (d *decoder) unmarshalFloat(value ast.Node, v reflect.Value) error { return nil } -func (d *decoder) unmarshalInteger(value ast.Node, v reflect.Value) error { +func (d *decoder) unmarshalInteger(value *ast.Node, v reflect.Value) error { const ( maxInt = int64(^uint(0) >> 1) minInt = -maxInt - 1 @@ -865,7 +862,7 @@ func (d *decoder) unmarshalInteger(value ast.Node, v reflect.Value) error { return err } -func (d *decoder) unmarshalString(value ast.Node, v reflect.Value) error { +func (d *decoder) unmarshalString(value *ast.Node, v reflect.Value) error { var err error switch v.Kind() { @@ -874,13 +871,13 @@ func (d *decoder) unmarshalString(value ast.Node, v reflect.Value) error { case reflect.Interface: v.Set(reflect.ValueOf(string(value.Data))) default: - err = fmt.Errorf("toml: cannot store TOML string into a Go %s", v.Kind()) + err = newDecodeError(d.p.Raw(value.Raw), "cannot store TOML string into a Go %s", v.Kind()) } return err } -func (d *decoder) handleKeyValue(expr ast.Node, v reflect.Value) (reflect.Value, error) { +func (d *decoder) handleKeyValue(expr *ast.Node, v reflect.Value) (reflect.Value, error) { d.strict.EnterKeyValue(expr) v, err := d.handleKeyValueInner(expr.Key(), expr.Value(), v) @@ -894,7 +891,7 @@ func (d *decoder) handleKeyValue(expr ast.Node, v reflect.Value) (reflect.Value, return v, err } -func (d *decoder) handleKeyValueInner(key ast.Iterator, value ast.Node, v reflect.Value) (reflect.Value, error) { +func (d *decoder) handleKeyValueInner(key ast.Iterator, value *ast.Node, v reflect.Value) (reflect.Value, error) { if key.Next() { // Still scoping the key return d.handleKeyValuePart(key, value, v) @@ -904,7 +901,7 @@ func (d *decoder) handleKeyValueInner(key ast.Iterator, value ast.Node, v reflec return reflect.Value{}, d.handleValue(value, v) } -func (d *decoder) handleKeyValuePart(key ast.Iterator, value ast.Node, v reflect.Value) (reflect.Value, error) { +func (d *decoder) handleKeyValuePart(key ast.Iterator, value *ast.Node, v reflect.Value) (reflect.Value, error) { // contains the replacement for v var rv reflect.Value diff --git a/unmarshaler_test.go b/unmarshaler_test.go index e14c9be0..35f4ad3b 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -287,6 +287,54 @@ func TestUnmarshal(t *testing.T) { } }, }, + { + desc: "local datetime into time.Time", + input: `a = 1979-05-27T00:32:00`, + gen: func() test { + type doc struct { + A time.Time + } + + return test{ + target: &doc{}, + expected: &doc{ + A: time.Date(1979, 5, 27, 0, 32, 0, 0, time.Local), + }, + } + }, + }, + { + desc: "local datetime into interface", + input: `a = 1979-05-27T00:32:00`, + gen: func() test { + type doc struct { + A interface{} + } + + return test{ + target: &doc{}, + expected: &doc{ + A: toml.LocalDateTimeOf(time.Date(1979, 5, 27, 0, 32, 0, 0, time.Local)), + }, + } + }, + }, + { + desc: "local date into interface", + input: `a = 1979-05-27`, + gen: func() test { + type doc struct { + A interface{} + } + + return test{ + target: &doc{}, + expected: &doc{ + A: toml.LocalDateOf(time.Date(1979, 5, 27, 0, 32, 0, 0, time.Local)), + }, + } + }, + }, { desc: "issue 475 - space between dots in key", input: `fruit. color = "yellow" From 773f10110c05fd3db8ded264674498c94a8b09a7 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Tue, 8 Jun 2021 14:22:39 -0400 Subject: [PATCH 207/228] Unmarshal recursive structs (#557) Co-authored-by: Nabetani --- unmarshaler.go | 7 ++ unmarshaler_test.go | 172 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 179 insertions(+) diff --git a/unmarshaler.go b/unmarshaler.go index b5144137..fd2e42f3 100644 --- a/unmarshaler.go +++ b/unmarshaler.go @@ -370,6 +370,13 @@ func (d *decoder) handleKeyPart(key ast.Iterator, v reflect.Value, nextFn handle // First, dispatch over v to make sure it is a valid object. // There is no guarantee over what it could be. switch v.Kind() { + case reflect.Ptr: + elem := v.Elem() + if !elem.IsValid() { + v.Set(reflect.New(v.Type().Elem())) + } + elem = v.Elem() + return d.handleKeyPart(key, elem, nextFn, makeFn) case reflect.Map: // Create the key for the map element. For now assume it's a string. mk := reflect.ValueOf(string(key.Node().Data)) diff --git a/unmarshaler_test.go b/unmarshaler_test.go index 35f4ad3b..1deb890e 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -1,6 +1,7 @@ package toml_test import ( + "encoding/json" "errors" "fmt" "math" @@ -2120,6 +2121,177 @@ bar = 42 } } +func TestUnmarshal_RecursiveTable(t *testing.T) { + type Foo struct { + I int + F *Foo + } + + examples := []struct { + desc string + input string + expected string + err bool + }{ + { + desc: "simplest", + input: ` + I=1 + `, + expected: `{"I":1,"F":null}`, + }, + { + desc: "depth 1", + input: ` + I=1 + [F] + I=2 + `, + expected: `{"I":1,"F":{"I":2,"F":null}}`, + }, + { + desc: "depth 3", + input: ` + I=1 + [F] + I=2 + [F.F] + I=3 + `, + expected: `{"I":1,"F":{"I":2,"F":{"I":3,"F":null}}}`, + }, + { + desc: "depth 4", + input: ` + I=1 + [F] + I=2 + [F.F] + I=3 + [F.F.F] + I=4 + `, + expected: `{"I":1,"F":{"I":2,"F":{"I":3,"F":{"I":4,"F":null}}}}`, + }, + { + desc: "skip mid step", + input: ` + I=1 + [F.F] + I=7 + `, + expected: `{"I":1,"F":{"I":0,"F":{"I":7,"F":null}}}`, + }, + } + + for _, ex := range examples { + e := ex + t.Run(e.desc, func(t *testing.T) { + foo := Foo{} + err := toml.Unmarshal([]byte(e.input), &foo) + if e.err { + require.Error(t, err) + } else { + require.NoError(t, err) + j, err := json.Marshal(foo) + require.NoError(t, err) + assert.Equal(t, e.expected, string(j)) + } + }) + } +} + +func TestUnmarshal_RecursiveTableArray(t *testing.T) { + type Foo struct { + I int + F []*Foo + } + + examples := []struct { + desc string + input string + expected string + err bool + }{ + { + desc: "simplest", + input: ` + I=1 + F=[] + `, + expected: `{"I":1,"F":[]}`, + }, + { + desc: "depth 1", + input: ` + I=1 + [[F]] + I=2 + F=[] + `, + expected: `{"I":1,"F":[{"I":2,"F":[]}]}`, + }, + { + desc: "depth 2", + input: ` + I=1 + [[F]] + I=2 + [[F.F]] + I=3 + F=[] + `, + expected: `{"I":1,"F":[{"I":2,"F":[{"I":3,"F":[]}]}]}`, + }, + { + desc: "depth 3", + input: ` + I=1 + [[F]] + I=2 + [[F.F]] + I=3 + [[F.F.F]] + I=4 + F=[] + `, + expected: `{"I":1,"F":[{"I":2,"F":[{"I":3,"F":[{"I":4,"F":[]}]}]}]}`, + }, + { + desc: "depth 4", + input: ` + I=1 + [[F]] + I=2 + [[F.F]] + I=3 + [[F.F.F]] + I=4 + [[F.F.F.F]] + I=5 + F=[] + `, + expected: `{"I":1,"F":[{"I":2,"F":[{"I":3,"F":[{"I":4,"F":[{"I":5,"F":[]}]}]}]}]}`, + }, + } + + for _, ex := range examples { + e := ex + t.Run(e.desc, func(t *testing.T) { + foo := Foo{} + err := toml.Unmarshal([]byte(e.input), &foo) + if e.err { + require.Error(t, err) + } else { + require.NoError(t, err) + j, err := json.Marshal(foo) + require.NoError(t, err) + assert.Equal(t, e.expected, string(j)) + } + }) + } +} + func ExampleDecoder_SetStrict() { type S struct { Key1 string From f6b38c33b7fe522e9c5539f66bd8966ce0006ac6 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Tue, 8 Jun 2021 20:27:05 -0400 Subject: [PATCH 208/228] Provide own implementation of Local* (#558) * Reduces the public API. * Reuses optimized parsing functions. * Removes reliance on Google code under Apache license. --- decode.go | 20 +- .../imported_tests/unmarshal_imported_test.go | 36 +- localtime.go | 317 +++-------- localtime_test.go | 526 +++--------------- unmarshaler.go | 5 +- unmarshaler_test.go | 12 +- 6 files changed, 174 insertions(+), 742 deletions(-) diff --git a/decode.go b/decode.go index 88e6a426..3f44d168 100644 --- a/decode.go +++ b/decode.go @@ -39,7 +39,7 @@ func parseLocalDate(b []byte) (LocalDate, error) { v := parseDecimalDigits(b[5:7]) - date.Month = time.Month(v) + date.Month = v date.Day = parseDecimalDigits(b[8:10]) @@ -100,13 +100,13 @@ func parseDateTime(b []byte) (time.Time, error) { } t := time.Date( - dt.Date.Year, - dt.Date.Month, - dt.Date.Day, - dt.Time.Hour, - dt.Time.Minute, - dt.Time.Second, - dt.Time.Nanosecond, + dt.Year, + time.Month(dt.Month), + dt.Day, + dt.Hour, + dt.Minute, + dt.Second, + dt.Nanosecond, zone) return t, nil @@ -124,7 +124,7 @@ func parseLocalDateTime(b []byte) (LocalDateTime, []byte, error) { if err != nil { return dt, nil, err } - dt.Date = date + dt.LocalDate = date sep := b[10] if sep != 'T' && sep != ' ' { @@ -135,7 +135,7 @@ func parseLocalDateTime(b []byte) (LocalDateTime, []byte, error) { if err != nil { return dt, nil, err } - dt.Time = t + dt.LocalTime = t return dt, rest, nil } diff --git a/internal/imported_tests/unmarshal_imported_test.go b/internal/imported_tests/unmarshal_imported_test.go index 2345445d..bac07ac4 100644 --- a/internal/imported_tests/unmarshal_imported_test.go +++ b/internal/imported_tests/unmarshal_imported_test.go @@ -1487,12 +1487,12 @@ func TestUnmarshalLocalDateTime(t *testing.T) { name: "normal", in: "1979-05-27T07:32:00", out: toml.LocalDateTime{ - Date: toml.LocalDate{ + LocalDate: toml.LocalDate{ Year: 1979, Month: 5, Day: 27, }, - Time: toml.LocalTime{ + LocalTime: toml.LocalTime{ Hour: 7, Minute: 32, Second: 0, @@ -1504,12 +1504,12 @@ func TestUnmarshalLocalDateTime(t *testing.T) { name: "with nanoseconds", in: "1979-05-27T00:32:00.999999", out: toml.LocalDateTime{ - Date: toml.LocalDate{ + LocalDate: toml.LocalDate{ Year: 1979, Month: 5, Day: 27, }, - Time: toml.LocalTime{ + LocalTime: toml.LocalTime{ Hour: 0, Minute: 32, Second: 0, @@ -1551,26 +1551,26 @@ func TestUnmarshalLocalDateTime(t *testing.T) { t.Fatal(err) } - if obj.Date.Year() != example.out.Date.Year { - t.Errorf("expected year %d, got %d", example.out.Date.Year, obj.Date.Year()) + if obj.Date.Year() != example.out.Year { + t.Errorf("expected year %d, got %d", example.out.Year, obj.Date.Year()) } - if obj.Date.Month() != example.out.Date.Month { - t.Errorf("expected month %d, got %d", example.out.Date.Month, obj.Date.Month()) + if obj.Date.Month() != time.Month(example.out.Month) { + t.Errorf("expected month %d, got %d", example.out.Month, obj.Date.Month()) } - if obj.Date.Day() != example.out.Date.Day { - t.Errorf("expected day %d, got %d", example.out.Date.Day, obj.Date.Day()) + if obj.Date.Day() != example.out.Day { + t.Errorf("expected day %d, got %d", example.out.Day, obj.Date.Day()) } - if obj.Date.Hour() != example.out.Time.Hour { - t.Errorf("expected hour %d, got %d", example.out.Time.Hour, obj.Date.Hour()) + if obj.Date.Hour() != example.out.Hour { + t.Errorf("expected hour %d, got %d", example.out.Hour, obj.Date.Hour()) } - if obj.Date.Minute() != example.out.Time.Minute { - t.Errorf("expected minute %d, got %d", example.out.Time.Minute, obj.Date.Minute()) + if obj.Date.Minute() != example.out.Minute { + t.Errorf("expected minute %d, got %d", example.out.Minute, obj.Date.Minute()) } - if obj.Date.Second() != example.out.Time.Second { - t.Errorf("expected second %d, got %d", example.out.Time.Second, obj.Date.Second()) + if obj.Date.Second() != example.out.Second { + t.Errorf("expected second %d, got %d", example.out.Second, obj.Date.Second()) } - if obj.Date.Nanosecond() != example.out.Time.Nanosecond { - t.Errorf("expected nanoseconds %d, got %d", example.out.Time.Nanosecond, obj.Date.Nanosecond()) + if obj.Date.Nanosecond() != example.out.Nanosecond { + t.Errorf("expected nanoseconds %d, got %d", example.out.Nanosecond, obj.Date.Nanosecond()) } }) } diff --git a/localtime.go b/localtime.go index a947044a..4e32ecc0 100644 --- a/localtime.go +++ b/localtime.go @@ -1,29 +1,3 @@ -// Implementation of TOML's local date/time. -// Copied over from https://github.com/googleapis/google-cloud-go/blob/master/civil/civil.go -// to avoid pulling all the Google dependencies. -// -// Copyright 2016 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package civil implements types for civil time, a time-zone-independent -// representation of time that follows the rules of the proleptic -// Gregorian calendar with exactly 24-hour days, 60-minute hours, and 60-second -// minutes. -// -// Because they lack location information, these types do not represent unique -// moments or intervals of time. Use time.Time for that purpose. - package toml import ( @@ -31,270 +5,105 @@ import ( "time" ) -// A LocalDate represents a date (year, month, day). -// -// This type does not include location information, and therefore does not -// describe a unique 24-hour timespan. +// LocalDate represents a calendar day in no specific timezone. type LocalDate struct { - Year int // Year (e.g., 2014). - Month time.Month // Month of the year (January = 1, ...). - Day int // Day of the month, starting at 1. -} - -// LocalDateOf returns the LocalDate in which a time occurs in that time's location. -func LocalDateOf(t time.Time) LocalDate { - var d LocalDate - d.Year, d.Month, d.Day = t.Date() - - return d + Year int + Month int + Day int } -// ParseLocalDate parses a string in RFC3339 full-date format and returns the date value it represents. -func ParseLocalDate(s string) (LocalDate, error) { - t, err := time.Parse("2006-01-02", s) - if err != nil { - return LocalDate{}, err - } - - return LocalDateOf(t), nil +// AsTime converts d into a specific time instance at midnight in zone. +func (d LocalDate) AsTime(zone *time.Location) time.Time { + return time.Date(d.Year, time.Month(d.Month), d.Day, 0, 0, 0, 0, zone) } -// String returns the date in RFC3339 full-date format. +// String returns RFC 3339 representation of d. func (d LocalDate) String() string { return fmt.Sprintf("%04d-%02d-%02d", d.Year, d.Month, d.Day) } -// IsValid reports whether the date is valid. -func (d LocalDate) IsValid() bool { - return LocalDateOf(d.In(time.UTC)) == d -} - -// In returns the time corresponding to time 00:00:00 of the date in the location. -// -// In is always consistent with time.LocalDate, even when time.LocalDate returns a time -// on a different day. For example, if loc is America/Indiana/Vincennes, then both -// time.LocalDate(1955, time.May, 1, 0, 0, 0, 0, loc) -// and -// civil.LocalDate{Year: 1955, Month: time.May, Day: 1}.In(loc) -// return 23:00:00 on April 30, 1955. -// -// In panics if loc is nil. -func (d LocalDate) In(loc *time.Location) time.Time { - return time.Date(d.Year, d.Month, d.Day, 0, 0, 0, 0, loc) -} - -// AddDays returns the date that is n days in the future. -// n can also be negative to go into the past. -func (d LocalDate) AddDays(n int) LocalDate { - return LocalDateOf(d.In(time.UTC).AddDate(0, 0, n)) -} - -// DaysSince returns the signed number of days between the date and s, not including the end day. -// This is the inverse operation to AddDays. -func (d LocalDate) DaysSince(s LocalDate) (days int) { - // We convert to Unix time so we do not have to worry about leap seconds: - // Unix time increases by exactly 86400 seconds per day. - deltaUnix := d.In(time.UTC).Unix() - s.In(time.UTC).Unix() - - const secondsInADay = 86400 - - return int(deltaUnix / secondsInADay) -} - -// Before reports whether d1 occurs before future date. -func (d LocalDate) Before(future LocalDate) bool { - if d.Year != future.Year { - return d.Year < future.Year - } - - if d.Month != future.Month { - return d.Month < future.Month - } - - return d.Day < future.Day -} - -// After reports whether d1 occurs after past date. -func (d LocalDate) After(past LocalDate) bool { - return past.Before(d) -} - -// MarshalText implements the encoding.TextMarshaler interface. -// The output is the result of d.String(). +// MarshalText returns RFC 3339 representation of d. func (d LocalDate) MarshalText() ([]byte, error) { return []byte(d.String()), nil } -// UnmarshalText implements the encoding.TextUnmarshaler interface. -// The date is expected to be a string in a format accepted by ParseLocalDate. -func (d *LocalDate) UnmarshalText(data []byte) error { - var err error - *d, err = ParseLocalDate(string(data)) - - return err -} - -// A LocalTime represents a time with nanosecond precision. -// -// This type does not include location information, and therefore does not -// describe a unique moment in time. -// -// This type exists to represent the TIME type in storage-based APIs like BigQuery. -// Most operations on Times are unlikely to be meaningful. Prefer the LocalDateTime type. -type LocalTime struct { - Hour int // The hour of the day in 24-hour format; range [0-23] - Minute int // The minute of the hour; range [0-59] - Second int // The second of the minute; range [0-59] - Nanosecond int // The nanosecond of the second; range [0-999999999] -} - -// LocalTimeOf returns the LocalTime representing the time of day in which a time occurs -// in that time's location. It ignores the date. -func LocalTimeOf(t time.Time) LocalTime { - var tm LocalTime - tm.Hour, tm.Minute, tm.Second = t.Clock() - tm.Nanosecond = t.Nanosecond() - - return tm -} - -// ParseLocalTime parses a string and returns the time value it represents. -// ParseLocalTime accepts an extended form of the RFC3339 partial-time format. After -// the HH:MM:SS part of the string, an optional fractional part may appear, -// consisting of a decimal point followed by one to nine decimal digits. -// (RFC3339 admits only one digit after the decimal point). -func ParseLocalTime(s string) (LocalTime, error) { - t, err := time.Parse("15:04:05.999999999", s) +// UnmarshalText parses b using RFC 3339 to fill d. +func (d *LocalDate) UnmarshalText(b []byte) error { + res, err := parseLocalDate(b) if err != nil { - return LocalTime{}, err + return err } + *d = res + return nil +} - return LocalTimeOf(t), nil +// LocalTime represents a time of day of no specific day in no specific +// timezone. +type LocalTime struct { + Hour int + Minute int + Second int + Nanosecond int } -// String returns the date in the format described in ParseLocalTime. If Nanoseconds -// is zero, no fractional part will be generated. Otherwise, the result will -// end with a fractional part consisting of a decimal point and nine digits. -func (t LocalTime) String() string { - s := fmt.Sprintf("%02d:%02d:%02d", t.Hour, t.Minute, t.Second) - if t.Nanosecond == 0 { +// String returns RFC 3339 representation of d. +func (d LocalTime) String() string { + s := fmt.Sprintf("%02d:%02d:%02d", d.Hour, d.Minute, d.Second) + if d.Nanosecond == 0 { return s } - - return s + fmt.Sprintf(".%09d", t.Nanosecond) -} - -// IsValid reports whether the time is valid. -func (t LocalTime) IsValid() bool { - // Construct a non-zero time. - tm := time.Date(2, 2, 2, t.Hour, t.Minute, t.Second, t.Nanosecond, time.UTC) - - return LocalTimeOf(tm) == t -} - -// MarshalText implements the encoding.TextMarshaler interface. -// The output is the result of t.String(). -func (t LocalTime) MarshalText() ([]byte, error) { - return []byte(t.String()), nil -} - -// UnmarshalText implements the encoding.TextUnmarshaler interface. -// The time is expected to be a string in a format accepted by ParseLocalTime. -func (t *LocalTime) UnmarshalText(data []byte) error { - var err error - *t, err = ParseLocalTime(string(data)) - - return err + return s + fmt.Sprintf(".%09d", d.Nanosecond) } -// A LocalDateTime represents a date and time. -// -// This type does not include location information, and therefore does not -// describe a unique moment in time. -type LocalDateTime struct { - Date LocalDate - Time LocalTime +// MarshalText returns RFC 3339 representation of d. +func (d LocalTime) MarshalText() ([]byte, error) { + return []byte(d.String()), nil } -// Note: We deliberately do not embed LocalDate into LocalDateTime, to avoid promoting AddDays and Sub. - -// LocalDateTimeOf returns the LocalDateTime in which a time occurs in that time's location. -func LocalDateTimeOf(t time.Time) LocalDateTime { - return LocalDateTime{ - Date: LocalDateOf(t), - Time: LocalTimeOf(t), +// UnmarshalText parses b using RFC 3339 to fill d. +func (d *LocalTime) UnmarshalText(b []byte) error { + res, left, err := parseLocalTime(b) + if err == nil && len(left) != 0 { + err = newDecodeError(left, "extra characters") } -} - -// ParseLocalDateTime parses a string and returns the LocalDateTime it represents. -// ParseLocalDateTime accepts a variant of the RFC3339 date-time format that omits -// the time offset but includes an optional fractional time, as described in -// ParseLocalTime. Informally, the accepted format is -// YYYY-MM-DDTHH:MM:SS[.FFFFFFFFF] -// where the 'T' may be a lower-case 't'. -func ParseLocalDateTime(s string) (LocalDateTime, error) { - t, err := time.Parse("2006-01-02T15:04:05.999999999", s) if err != nil { - t, err = time.Parse("2006-01-02t15:04:05.999999999", s) - if err != nil { - return LocalDateTime{}, err - } + return err } - - return LocalDateTimeOf(t), nil -} - -// String returns the date in the format described in ParseLocalDate. -func (dt LocalDateTime) String() string { - return dt.Date.String() + "T" + dt.Time.String() + *d = res + return nil } -// IsValid reports whether the datetime is valid. -func (dt LocalDateTime) IsValid() bool { - return dt.Date.IsValid() && dt.Time.IsValid() -} - -// In returns the time corresponding to the LocalDateTime in the given location. -// -// If the time is missing or ambigous at the location, In returns the same -// result as time.LocalDate. For example, if loc is America/Indiana/Vincennes, then -// both -// time.LocalDate(1955, time.May, 1, 0, 30, 0, 0, loc) -// and -// civil.LocalDateTime{ -// civil.LocalDate{Year: 1955, Month: time.May, Day: 1}}, -// civil.LocalTime{Minute: 30}}.In(loc) -// return 23:30:00 on April 30, 1955. -// -// In panics if loc is nil. -func (dt LocalDateTime) In(loc *time.Location) time.Time { - return time.Date( - dt.Date.Year, dt.Date.Month, dt.Date.Day, - dt.Time.Hour, dt.Time.Minute, dt.Time.Second, dt.Time.Nanosecond, loc, - ) +// LocalDateTime represents a time of a specific day in no specific timezone. +type LocalDateTime struct { + LocalDate + LocalTime } -// Before reports whether dt occurs before future. -func (dt LocalDateTime) Before(future LocalDateTime) bool { - return dt.In(time.UTC).Before(future.In(time.UTC)) +// AsTime converts d into a specific time instance in zone. +func (d LocalDateTime) AsTime(zone *time.Location) time.Time { + return time.Date(d.Year, time.Month(d.Month), d.Day, d.Hour, d.Minute, d.Second, d.Nanosecond, zone) } -// After reports whether dt occurs after past. -func (dt LocalDateTime) After(past LocalDateTime) bool { - return past.Before(dt) +// String returns RFC 3339 representation of d. +func (d LocalDateTime) String() string { + return d.LocalDate.String() + " " + d.LocalTime.String() } -// MarshalText implements the encoding.TextMarshaler interface. -// The output is the result of dt.String(). -func (dt LocalDateTime) MarshalText() ([]byte, error) { - return []byte(dt.String()), nil +// MarshalText returns RFC 3339 representation of d. +func (d LocalDateTime) MarshalText() ([]byte, error) { + return []byte(d.String()), nil } -// UnmarshalText implements the encoding.TextUnmarshaler interface. -// The datetime is expected to be a string in a format accepted by ParseLocalDateTime. -func (dt *LocalDateTime) UnmarshalText(data []byte) error { - var err error - *dt, err = ParseLocalDateTime(string(data)) +// UnmarshalText parses b using RFC 3339 to fill d. +func (d *LocalDateTime) UnmarshalText(data []byte) error { + res, left, err := parseLocalDateTime(data) + if err == nil && len(left) != 0 { + err = newDecodeError(left, "extra characters") + } + if err != nil { + return err + } - return err + *d = res + return nil } diff --git a/localtime_test.go b/localtime_test.go index 646a3dbc..6ad9f0ab 100644 --- a/localtime_test.go +++ b/localtime_test.go @@ -1,489 +1,107 @@ -// Copyright 2016 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package toml +package toml_test import ( - "encoding/json" - "reflect" "testing" "time" -) - -func cmpEqual(x, y interface{}) bool { - return reflect.DeepEqual(x, y) -} - -func TestDates(t *testing.T) { - - for _, test := range []struct { - date LocalDate - loc *time.Location - wantStr string - wantTime time.Time - }{ - { - date: LocalDate{2014, 7, 29}, - loc: time.Local, - wantStr: "2014-07-29", - wantTime: time.Date(2014, time.July, 29, 0, 0, 0, 0, time.Local), - }, - { - date: LocalDateOf(time.Date(2014, 8, 20, 15, 8, 43, 1, time.Local)), - loc: time.UTC, - wantStr: "2014-08-20", - wantTime: time.Date(2014, 8, 20, 0, 0, 0, 0, time.UTC), - }, - { - date: LocalDateOf(time.Date(999, time.January, 26, 0, 0, 0, 0, time.Local)), - loc: time.UTC, - wantStr: "0999-01-26", - wantTime: time.Date(999, 1, 26, 0, 0, 0, 0, time.UTC), - }, - } { - if got := test.date.String(); got != test.wantStr { - t.Errorf("%#v.String() = %q, want %q", test.date, got, test.wantStr) - } - - if got := test.date.In(test.loc); !got.Equal(test.wantTime) { - t.Errorf("%#v.In(%v) = %v, want %v", test.date, test.loc, got, test.wantTime) - } - } -} - -func TestDateIsValid(t *testing.T) { - - for _, test := range []struct { - date LocalDate - want bool - }{ - {LocalDate{2014, 7, 29}, true}, - {LocalDate{2000, 2, 29}, true}, - {LocalDate{10000, 12, 31}, true}, - {LocalDate{1, 1, 1}, true}, - {LocalDate{0, 1, 1}, true}, // year zero is OK - {LocalDate{-1, 1, 1}, true}, // negative year is OK - {LocalDate{1, 0, 1}, false}, - {LocalDate{1, 1, 0}, false}, - {LocalDate{2016, 1, 32}, false}, - {LocalDate{2016, 13, 1}, false}, - {LocalDate{1, -1, 1}, false}, - {LocalDate{1, 1, -1}, false}, - } { - got := test.date.IsValid() - if got != test.want { - t.Errorf("%#v: got %t, want %t", test.date, got, test.want) - } - } -} - -func TestParseDate(t *testing.T) { - - var emptyDate LocalDate - - for _, test := range []struct { - str string - want LocalDate // if empty, expect an error - }{ - {"2016-01-02", LocalDate{2016, 1, 2}}, - {"2016-12-31", LocalDate{2016, 12, 31}}, - {"0003-02-04", LocalDate{3, 2, 4}}, - {"999-01-26", emptyDate}, - {"", emptyDate}, - {"2016-01-02x", emptyDate}, - } { - got, err := ParseLocalDate(test.str) - if got != test.want { - t.Errorf("ParseLocalDate(%q) = %+v, want %+v", test.str, got, test.want) - } - - if err != nil && test.want != (emptyDate) { - t.Errorf("Unexpected error %v from ParseLocalDate(%q)", err, test.str) - } - } -} - -func TestDateArithmetic(t *testing.T) { - - for _, test := range []struct { - desc string - start LocalDate - end LocalDate - days int - }{ - { - desc: "zero days noop", - start: LocalDate{2014, 5, 9}, - end: LocalDate{2014, 5, 9}, - days: 0, - }, - { - desc: "crossing a year boundary", - start: LocalDate{2014, 12, 31}, - end: LocalDate{2015, 1, 1}, - days: 1, - }, - { - desc: "negative number of days", - start: LocalDate{2015, 1, 1}, - end: LocalDate{2014, 12, 31}, - days: -1, - }, - { - desc: "full leap year", - start: LocalDate{2004, 1, 1}, - end: LocalDate{2005, 1, 1}, - days: 366, - }, - { - desc: "full non-leap year", - start: LocalDate{2001, 1, 1}, - end: LocalDate{2002, 1, 1}, - days: 365, - }, - { - desc: "crossing a leap second", - start: LocalDate{1972, 6, 30}, - end: LocalDate{1972, 7, 1}, - days: 1, - }, - { - desc: "dates before the unix epoch", - start: LocalDate{101, 1, 1}, - end: LocalDate{102, 1, 1}, - days: 365, - }, - } { - if got := test.start.AddDays(test.days); got != test.end { - t.Errorf("[%s] %#v.AddDays(%v) = %#v, want %#v", test.desc, test.start, test.days, got, test.end) - } - - if got := test.end.DaysSince(test.start); got != test.days { - t.Errorf("[%s] %#v.Sub(%#v) = %v, want %v", test.desc, test.end, test.start, got, test.days) - } - } -} - -func TestDateBefore(t *testing.T) { - - for _, test := range []struct { - d1, d2 LocalDate - want bool - }{ - {LocalDate{2016, 12, 31}, LocalDate{2017, 1, 1}, true}, - {LocalDate{2016, 1, 1}, LocalDate{2016, 1, 1}, false}, - {LocalDate{2016, 12, 30}, LocalDate{2016, 12, 31}, true}, - {LocalDate{2016, 1, 30}, LocalDate{2016, 12, 31}, true}, - } { - if got := test.d1.Before(test.d2); got != test.want { - t.Errorf("%v.Before(%v): got %t, want %t", test.d1, test.d2, got, test.want) - } - } -} - -func TestDateAfter(t *testing.T) { - - for _, test := range []struct { - d1, d2 LocalDate - want bool - }{ - {LocalDate{2016, 12, 31}, LocalDate{2017, 1, 1}, false}, - {LocalDate{2016, 1, 1}, LocalDate{2016, 1, 1}, false}, - {LocalDate{2016, 12, 30}, LocalDate{2016, 12, 31}, false}, - } { - if got := test.d1.After(test.d2); got != test.want { - t.Errorf("%v.After(%v): got %t, want %t", test.d1, test.d2, got, test.want) - } - } -} - -func TestTimeToString(t *testing.T) { - - for _, test := range []struct { - str string - time LocalTime - roundTrip bool // ParseLocalTime(str).String() == str? - }{ - {"13:26:33", LocalTime{13, 26, 33, 0}, true}, - {"01:02:03.000023456", LocalTime{1, 2, 3, 23456}, true}, - {"00:00:00.000000001", LocalTime{0, 0, 0, 1}, true}, - {"13:26:03.1", LocalTime{13, 26, 3, 100000000}, false}, - {"13:26:33.0000003", LocalTime{13, 26, 33, 300}, false}, - } { - gotTime, err := ParseLocalTime(test.str) - if err != nil { - t.Errorf("ParseLocalTime(%q): got error: %v", test.str, err) - continue - } - - if gotTime != test.time { - t.Errorf("ParseLocalTime(%q) = %+v, want %+v", test.str, gotTime, test.time) - } + "github.com/pelletier/go-toml/v2" + "github.com/stretchr/testify/require" +) - if test.roundTrip { - gotStr := test.time.String() - if gotStr != test.str { - t.Errorf("%#v.String() = %q, want %q", test.time, gotStr, test.str) - } - } - } +func TestLocalDate_AsTime(t *testing.T) { + d := toml.LocalDate{2021, 6, 8} + cast := d.AsTime(time.UTC) + require.Equal(t, time.Date(2021, time.June, 8, 0, 0, 0, 0, time.UTC), cast) } -func TestTimeOf(t *testing.T) { - - for _, test := range []struct { - time time.Time - want LocalTime - }{ - {time.Date(2014, 8, 20, 15, 8, 43, 1, time.Local), LocalTime{15, 8, 43, 1}}, - {time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC), LocalTime{0, 0, 0, 0}}, - } { - if got := LocalTimeOf(test.time); got != test.want { - t.Errorf("LocalTimeOf(%v) = %+v, want %+v", test.time, got, test.want) - } - } +func TestLocalDate_String(t *testing.T) { + d := toml.LocalDate{2021, 6, 8} + require.Equal(t, "2021-06-08", d.String()) } -func TestTimeIsValid(t *testing.T) { - - for _, test := range []struct { - time LocalTime - want bool - }{ - {LocalTime{0, 0, 0, 0}, true}, - {LocalTime{23, 0, 0, 0}, true}, - {LocalTime{23, 59, 59, 999999999}, true}, - {LocalTime{24, 59, 59, 999999999}, false}, - {LocalTime{23, 60, 59, 999999999}, false}, - {LocalTime{23, 59, 60, 999999999}, false}, - {LocalTime{23, 59, 59, 1000000000}, false}, - {LocalTime{-1, 0, 0, 0}, false}, - {LocalTime{0, -1, 0, 0}, false}, - {LocalTime{0, 0, -1, 0}, false}, - {LocalTime{0, 0, 0, -1}, false}, - } { - got := test.time.IsValid() - if got != test.want { - t.Errorf("%#v: got %t, want %t", test.time, got, test.want) - } - } +func TestLocalDate_MarshalText(t *testing.T) { + d := toml.LocalDate{2021, 6, 8} + b, err := d.MarshalText() + require.NoError(t, err) + require.Equal(t, []byte("2021-06-08"), b) } -func TestDateTimeToString(t *testing.T) { - - for _, test := range []struct { - str string - dateTime LocalDateTime - roundTrip bool // ParseLocalDateTime(str).String() == str? - }{ - {"2016-03-22T13:26:33", LocalDateTime{LocalDate{2016, 3, 22}, LocalTime{13, 26, 33, 0}}, true}, - {"2016-03-22T13:26:33.000000600", LocalDateTime{LocalDate{2016, 3, 22}, LocalTime{13, 26, 33, 600}}, true}, - {"2016-03-22t13:26:33", LocalDateTime{LocalDate{2016, 3, 22}, LocalTime{13, 26, 33, 0}}, false}, - } { - gotDateTime, err := ParseLocalDateTime(test.str) - if err != nil { - t.Errorf("ParseLocalDateTime(%q): got error: %v", test.str, err) - - continue - } +func TestLocalDate_UnmarshalMarshalText(t *testing.T) { + d := toml.LocalDate{} + err := d.UnmarshalText([]byte("2021-06-08")) + require.NoError(t, err) + require.Equal(t, toml.LocalDate{2021, 6, 8}, d) - if gotDateTime != test.dateTime { - t.Errorf("ParseLocalDateTime(%q) = %+v, want %+v", test.str, gotDateTime, test.dateTime) - } - - if test.roundTrip { - gotStr := test.dateTime.String() - if gotStr != test.str { - t.Errorf("%#v.String() = %q, want %q", test.dateTime, gotStr, test.str) - } - } - } + err = d.UnmarshalText([]byte("what")) + require.Error(t, err) } -func TestParseDateTimeErrors(t *testing.T) { - - for _, str := range []string{ - "", - "2016-03-22", // just a date - "13:26:33", // just a time - "2016-03-22 13:26:33", // wrong separating character - "2016-03-22T13:26:33x", // extra at end - } { - if _, err := ParseLocalDateTime(str); err == nil { - t.Errorf("ParseLocalDateTime(%q) succeeded, want error", str) - } - } +func TestLocalTime_String(t *testing.T) { + d := toml.LocalTime{20, 12, 1, 2} + require.Equal(t, "20:12:01.000000002", d.String()) + d = toml.LocalTime{20, 12, 1, 0} + require.Equal(t, "20:12:01", d.String()) } -func TestDateTimeOf(t *testing.T) { - - for _, test := range []struct { - time time.Time - want LocalDateTime - }{ - { - time.Date(2014, 8, 20, 15, 8, 43, 1, time.Local), - LocalDateTime{LocalDate{2014, 8, 20}, LocalTime{15, 8, 43, 1}}, - }, - { - time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC), - LocalDateTime{LocalDate{1, 1, 1}, LocalTime{0, 0, 0, 0}}, - }, - } { - if got := LocalDateTimeOf(test.time); got != test.want { - t.Errorf("LocalDateTimeOf(%v) = %+v, want %+v", test.time, got, test.want) - } - } +func TestLocalTime_MarshalText(t *testing.T) { + d := toml.LocalTime{20, 12, 1, 2} + b, err := d.MarshalText() + require.NoError(t, err) + require.Equal(t, []byte("20:12:01.000000002"), b) } -func TestDateTimeIsValid(t *testing.T) { +func TestLocalTime_UnmarshalMarshalText(t *testing.T) { + d := toml.LocalTime{} + err := d.UnmarshalText([]byte("20:12:01.000000002")) + require.NoError(t, err) + require.Equal(t, toml.LocalTime{20, 12, 1, 2}, d) - // No need to be exhaustive here; it's just LocalDate.IsValid && LocalTime.IsValid. - for _, test := range []struct { - dt LocalDateTime - want bool - }{ - {LocalDateTime{LocalDate{2016, 3, 20}, LocalTime{0, 0, 0, 0}}, true}, - {LocalDateTime{LocalDate{2016, -3, 20}, LocalTime{0, 0, 0, 0}}, false}, - {LocalDateTime{LocalDate{2016, 3, 20}, LocalTime{24, 0, 0, 0}}, false}, - } { - got := test.dt.IsValid() - if got != test.want { - t.Errorf("%#v: got %t, want %t", test.dt, got, test.want) - } - } -} + err = d.UnmarshalText([]byte("what")) + require.Error(t, err) -func TestDateTimeIn(t *testing.T) { - - dt := LocalDateTime{LocalDate{2016, 1, 2}, LocalTime{3, 4, 5, 6}} - - want := time.Date(2016, 1, 2, 3, 4, 5, 6, time.UTC) - if got := dt.In(time.UTC); !got.Equal(want) { - t.Errorf("got %v, want %v", got, want) - } + err = d.UnmarshalText([]byte("20:12:01.000000002 bad")) + require.Error(t, err) } -func TestDateTimeBefore(t *testing.T) { - - d1 := LocalDate{2016, 12, 31} - d2 := LocalDate{2017, 1, 1} - t1 := LocalTime{5, 6, 7, 8} - t2 := LocalTime{5, 6, 7, 9} - - for _, test := range []struct { - dt1, dt2 LocalDateTime - want bool - }{ - {LocalDateTime{d1, t1}, LocalDateTime{d2, t1}, true}, - {LocalDateTime{d1, t1}, LocalDateTime{d1, t2}, true}, - {LocalDateTime{d2, t1}, LocalDateTime{d1, t1}, false}, - {LocalDateTime{d2, t1}, LocalDateTime{d2, t1}, false}, - } { - if got := test.dt1.Before(test.dt2); got != test.want { - t.Errorf("%v.Before(%v): got %t, want %t", test.dt1, test.dt2, got, test.want) - } +func TestLocalDateTime_AsTime(t *testing.T) { + d := toml.LocalDateTime{ + toml.LocalDate{2021, 6, 8}, + toml.LocalTime{20, 12, 1, 2}, } + cast := d.AsTime(time.UTC) + require.Equal(t, time.Date(2021, time.June, 8, 20, 12, 1, 2, time.UTC), cast) } -func TestDateTimeAfter(t *testing.T) { - - d1 := LocalDate{2016, 12, 31} - d2 := LocalDate{2017, 1, 1} - t1 := LocalTime{5, 6, 7, 8} - t2 := LocalTime{5, 6, 7, 9} - - for _, test := range []struct { - dt1, dt2 LocalDateTime - want bool - }{ - {LocalDateTime{d1, t1}, LocalDateTime{d2, t1}, false}, - {LocalDateTime{d1, t1}, LocalDateTime{d1, t2}, false}, - {LocalDateTime{d2, t1}, LocalDateTime{d1, t1}, true}, - {LocalDateTime{d2, t1}, LocalDateTime{d2, t1}, false}, - } { - if got := test.dt1.After(test.dt2); got != test.want { - t.Errorf("%v.After(%v): got %t, want %t", test.dt1, test.dt2, got, test.want) - } +func TestLocalDateTime_String(t *testing.T) { + d := toml.LocalDateTime{ + toml.LocalDate{2021, 6, 8}, + toml.LocalTime{20, 12, 1, 2}, } + require.Equal(t, "2021-06-08 20:12:01.000000002", d.String()) } -func TestMarshalJSON(t *testing.T) { - - for _, test := range []struct { - value interface{} - want string - }{ - {LocalDate{1987, 4, 15}, `"1987-04-15"`}, - {LocalTime{18, 54, 2, 0}, `"18:54:02"`}, - {LocalDateTime{LocalDate{1987, 4, 15}, LocalTime{18, 54, 2, 0}}, `"1987-04-15T18:54:02"`}, - } { - bgot, err := json.Marshal(test.value) - if err != nil { - t.Fatal(err) - } - - if got := string(bgot); got != test.want { - t.Errorf("%#v: got %s, want %s", test.value, got, test.want) - } +func TestLocalDateTime_MarshalText(t *testing.T) { + d := toml.LocalDateTime{ + toml.LocalDate{2021, 6, 8}, + toml.LocalTime{20, 12, 1, 2}, } + b, err := d.MarshalText() + require.NoError(t, err) + require.Equal(t, []byte("2021-06-08 20:12:01.000000002"), b) } -func TestUnmarshalJSON(t *testing.T) { - - var ( - d LocalDate - tm LocalTime - dt LocalDateTime - ) - - for _, test := range []struct { - data string - ptr interface{} - want interface{} - }{ - {`"1987-04-15"`, &d, &LocalDate{1987, 4, 15}}, - {`"1987-04-\u0031\u0035"`, &d, &LocalDate{1987, 4, 15}}, - {`"18:54:02"`, &tm, &LocalTime{18, 54, 2, 0}}, - {`"1987-04-15T18:54:02"`, &dt, &LocalDateTime{LocalDate{1987, 4, 15}, LocalTime{18, 54, 2, 0}}}, - } { - if err := json.Unmarshal([]byte(test.data), test.ptr); err != nil { - t.Fatalf("%s: %v", test.data, err) - } - - if !cmpEqual(test.ptr, test.want) { - t.Errorf("%s: got %#v, want %#v", test.data, test.ptr, test.want) - } - } - - for _, bad := range []string{ - "", `""`, `"bad"`, `"1987-04-15x"`, - `19870415`, // a JSON number - `11987-04-15x`, // not a JSON string +func TestLocalDateTime_UnmarshalMarshalText(t *testing.T) { + d := toml.LocalDateTime{} + err := d.UnmarshalText([]byte("2021-06-08 20:12:01.000000002")) + require.NoError(t, err) + require.Equal(t, toml.LocalDateTime{ + toml.LocalDate{2021, 6, 8}, + toml.LocalTime{20, 12, 1, 2}, + }, d) - } { - if json.Unmarshal([]byte(bad), &d) == nil { - t.Errorf("%q, LocalDate: got nil, want error", bad) - } + err = d.UnmarshalText([]byte("what")) + require.Error(t, err) - if json.Unmarshal([]byte(bad), &tm) == nil { - t.Errorf("%q, LocalTime: got nil, want error", bad) - } - - if json.Unmarshal([]byte(bad), &dt) == nil { - t.Errorf("%q, LocalDateTime: got nil, want error", bad) - } - } + err = d.UnmarshalText([]byte("2021-06-08 20:12:01.000000002 bad")) + require.Error(t, err) } diff --git a/unmarshaler.go b/unmarshaler.go index fd2e42f3..63469852 100644 --- a/unmarshaler.go +++ b/unmarshaler.go @@ -720,8 +720,7 @@ func (d *decoder) unmarshalLocalDate(value *ast.Node, v reflect.Value) error { } if v.Type() == timeType { - cast := ld.In(time.Local) - + cast := ld.AsTime(time.Local) v.Set(reflect.ValueOf(cast)) return nil } @@ -742,7 +741,7 @@ func (d *decoder) unmarshalLocalDateTime(value *ast.Node, v reflect.Value) error } if v.Type() == timeType { - cast := ldt.In(time.Local) + cast := ldt.AsTime(time.Local) v.Set(reflect.ValueOf(cast)) return nil diff --git a/unmarshaler_test.go b/unmarshaler_test.go index 1deb890e..6b134609 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -315,7 +315,10 @@ func TestUnmarshal(t *testing.T) { return test{ target: &doc{}, expected: &doc{ - A: toml.LocalDateTimeOf(time.Date(1979, 5, 27, 0, 32, 0, 0, time.Local)), + A: toml.LocalDateTime{ + toml.LocalDate{1979, 5, 27}, + toml.LocalTime{0, 32, 0, 0}, + }, }, } }, @@ -331,7 +334,7 @@ func TestUnmarshal(t *testing.T) { return test{ target: &doc{}, expected: &doc{ - A: toml.LocalDateOf(time.Date(1979, 5, 27, 0, 32, 0, 0, time.Local)), + A: toml.LocalDate{1979, 5, 27}, }, } }, @@ -1977,7 +1980,10 @@ func TestLocalDateTime(t *testing.T) { actual := m["a"] golang, err := time.Parse("2006-01-02T15:04:05.999999999", e.input) require.NoError(t, err) - expected := toml.LocalDateTimeOf(golang) + expected := toml.LocalDateTime{ + toml.LocalDate{golang.Year(), int(golang.Month()), golang.Day()}, + toml.LocalTime{golang.Hour(), golang.Minute(), golang.Second(), golang.Nanosecond()}, + } require.Equal(t, expected, actual) }) } From 9c24fbeaad098070d385024df8daa94ede588ca6 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Tue, 20 Jul 2021 16:54:26 +0200 Subject: [PATCH 209/228] Set up Dependabot for GitHub actions and docker (#570) --- .github/dependabot.yml | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 14030fd5..5cbcee36 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,6 +1,17 @@ version: 2 updates: - - package-ecosystem: "gomod" - directory: "/" # Location of package manifests - schedule: - interval: "daily" +- package-ecosystem: gomod + directory: / + schedule: + interval: daily + open-pull-requests-limit: 10 +- package-ecosystem: github-actions + directory: / + schedule: + interval: daily + open-pull-requests-limit: 10 +- package-ecosystem: docker + directory: / + schedule: + interval: daily + open-pull-requests-limit: 10 From a93b34d984798f7d447d4313534a1c407e2c4d63 Mon Sep 17 00:00:00 2001 From: kkHAIKE Date: Wed, 21 Jul 2021 16:50:03 +0800 Subject: [PATCH 210/228] Unicode parsing optimization (#568) Inline call to hexToRune and uses specialized parsing, as found in encoding/json. Co-authored-by: Thomas Pelletier --- parser.go | 42 ++++++++++++++++++++++++------------------ parser_test.go | 22 ++++++++++++++++++++++ 2 files changed, 46 insertions(+), 18 deletions(-) diff --git a/parser.go b/parser.go index 453b08d1..5040b5e9 100644 --- a/parser.go +++ b/parser.go @@ -2,7 +2,6 @@ package toml import ( "bytes" - "strconv" "github.com/pelletier/go-toml/v2/internal/ast" "github.com/pelletier/go-toml/v2/internal/danger" @@ -593,20 +592,19 @@ func (p *parser) parseMultilineBasicString(b []byte) ([]byte, []byte, []byte, er case 't': builder.WriteByte('\t') case 'u': - x, err := hexToString(atmost(token[i+1:], 4), 4) + x, err := hexToRune(atmost(token[i+1:], 4), 4) if err != nil { return nil, nil, nil, err } - - builder.WriteString(x) + builder.WriteRune(x) i += 4 case 'U': - x, err := hexToString(atmost(token[i+1:], 8), 8) + x, err := hexToRune(atmost(token[i+1:], 8), 8) if err != nil { return nil, nil, nil, err } - builder.WriteString(x) + builder.WriteRune(x) i += 8 default: return nil, nil, nil, newDecodeError(token[i:i+1], "invalid escaped character %#U", c) @@ -742,20 +740,20 @@ func (p *parser) parseBasicString(b []byte) ([]byte, []byte, []byte, error) { case 't': builder.WriteByte('\t') case 'u': - x, err := hexToString(token[i+1:len(token)-1], 4) + x, err := hexToRune(token[i+1:len(token)-1], 4) if err != nil { return nil, nil, nil, err } - builder.WriteString(x) + builder.WriteRune(x) i += 4 case 'U': - x, err := hexToString(token[i+1:len(token)-1], 8) + x, err := hexToRune(token[i+1:len(token)-1], 8) if err != nil { return nil, nil, nil, err } - builder.WriteString(x) + builder.WriteRune(x) i += 8 default: return nil, nil, nil, newDecodeError(token[i:i+1], "invalid escaped character %#U", c) @@ -768,20 +766,28 @@ func (p *parser) parseBasicString(b []byte) ([]byte, []byte, []byte, error) { return token, builder.Bytes(), rest, nil } -func hexToString(b []byte, length int) (string, error) { +func hexToRune(b []byte, length int) (rune, error) { if len(b) < length { - return "", newDecodeError(b, "unicode point needs %d character, not %d", length, len(b)) + return -1, newDecodeError(b, "unicode point needs %d character, not %d", length, len(b)) } b = b[:length] - //nolint:godox - // TODO: slow - intcode, err := strconv.ParseInt(string(b), 16, 32) - if err != nil { - return "", newDecodeError(b, "couldn't parse hexadecimal number: %w", err) + var r rune + for i, c := range b { + switch { + case '0' <= c && c <= '9': + c = c - '0' + case 'a' <= c && c <= 'f': + c = c - 'a' + 10 + case 'A' <= c && c <= 'F': + c = c - 'A' + 10 + default: + return -1, newDecodeError(b[i:i+1], "non-hex character") + } + r = r*16 + rune(c) } - return string(rune(intcode)), nil + return r, nil } func (p *parser) parseWhitespace(b []byte) []byte { diff --git a/parser_test.go b/parser_test.go index 9fda4299..fb260e05 100644 --- a/parser_test.go +++ b/parser_test.go @@ -348,3 +348,25 @@ func TestParser_AST(t *testing.T) { }) } } + +func BenchmarkParseBasicStringWithUnicode(b *testing.B) { + p := &parser{} + b.Run("4", func(b *testing.B) { + input := []byte(`"\u1234\u5678\u9ABC\u1234\u5678\u9ABC"`) + b.ReportAllocs() + b.SetBytes(int64(len(input))) + + for i := 0; i < b.N; i++ { + p.parseBasicString(input) + } + }) + b.Run("8", func(b *testing.B) { + input := []byte(`"\u12345678\u9ABCDEF0\u12345678\u9ABCDEF0"`) + b.ReportAllocs() + b.SetBytes(int64(len(input))) + + for i := 0; i < b.N; i++ { + p.parseBasicString(input) + } + }) +} From 8be357dfa190aed96094e4825ec04cb1d53df452 Mon Sep 17 00:00:00 2001 From: kkHAIKE Date: Wed, 21 Jul 2021 23:50:12 +0800 Subject: [PATCH 211/228] Add LocalTime to interface{} decode support (#567) Co-authored-by: Thomas Pelletier --- internal/ast/kind.go | 6 ++--- marshaler_test.go | 7 ++++++ parser.go | 12 ++++++--- parser_test.go | 59 ++++++++++++++++++++++++++++++++++++++++++++ unmarshaler.go | 16 ++++++++++++ unmarshaler_test.go | 52 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 146 insertions(+), 6 deletions(-) diff --git a/internal/ast/kind.go b/internal/ast/kind.go index bcf6c91c..2b50c67f 100644 --- a/internal/ast/kind.go +++ b/internal/ast/kind.go @@ -25,9 +25,9 @@ const ( Float Integer LocalDate + LocalTime LocalDateTime DateTime - Time ) func (k Kind) String() string { @@ -58,12 +58,12 @@ func (k Kind) String() string { return "Integer" case LocalDate: return "LocalDate" + case LocalTime: + return "LocalTime" case LocalDateTime: return "LocalDateTime" case DateTime: return "DateTime" - case Time: - return "Time" } panic(fmt.Errorf("Kind.String() not implemented for '%d'", k)) } diff --git a/marshaler_test.go b/marshaler_test.go index 333c93c1..8667a813 100644 --- a/marshaler_test.go +++ b/marshaler_test.go @@ -806,3 +806,10 @@ func ExampleMarshal() { // Name = 'go-toml' // Tags = ['go', 'toml'] } + +func TestIssue567(t *testing.T) { + var m map[string]interface{} + err := toml.Unmarshal([]byte("A = 12:08:05"), &m) + require.NoError(t, err) + require.IsType(t, m["A"], toml.LocalTime{}) +} diff --git a/parser.go b/parser.go index 5040b5e9..03385c12 100644 --- a/parser.go +++ b/parser.go @@ -862,6 +862,7 @@ func digitsToInt(b []byte) int { func (p *parser) scanDateTime(b []byte) (ast.Reference, []byte, error) { // scans for contiguous characters in [0-9T:Z.+-], and up to one space if // followed by a digit. + hasDate := false hasTime := false hasTz := false seenSpace := false @@ -874,6 +875,7 @@ byteLoop: switch { case isDigit(c): case c == '-': + hasDate = true const minOffsetOfTz = 8 if i >= minOffsetOfTz { hasTz = true @@ -898,10 +900,14 @@ byteLoop: var kind ast.Kind if hasTime { - if hasTz { - kind = ast.DateTime + if hasDate { + if hasTz { + kind = ast.DateTime + } else { + kind = ast.LocalDateTime + } } else { - kind = ast.LocalDateTime + kind = ast.LocalTime } } else { kind = ast.LocalDate diff --git a/parser_test.go b/parser_test.go index fb260e05..1f260d16 100644 --- a/parser_test.go +++ b/parser_test.go @@ -370,3 +370,62 @@ func BenchmarkParseBasicStringWithUnicode(b *testing.B) { } }) } + +func TestParser_AST_DateTimes(t *testing.T) { + examples := []struct { + desc string + input string + kind ast.Kind + err bool + }{ + { + desc: "offset-date-time with delim 'T' and UTC offset", + input: `2021-07-21T12:08:05Z`, + kind: ast.DateTime, + }, + { + desc: "offset-date-time with space delim and +8hours offset", + input: `2021-07-21 12:08:05+08:00`, + kind: ast.DateTime, + }, + { + desc: "local-date-time with nano second", + input: `2021-07-21T12:08:05.666666666`, + kind: ast.LocalDateTime, + }, + { + desc: "local-date-time", + input: `2021-07-21T12:08:05`, + kind: ast.LocalDateTime, + }, + { + desc: "local-date", + input: `2021-07-21`, + kind: ast.LocalDate, + }, + } + + for _, e := range examples { + e := e + t.Run(e.desc, func(t *testing.T) { + p := parser{} + p.Reset([]byte(`A = ` + e.input)) + p.NextExpression() + err := p.Error() + if e.err { + require.Error(t, err) + } else { + require.NoError(t, err) + + expected := astNode{ + Kind: ast.KeyValue, + Children: []astNode{ + {Kind: e.kind, Data: []byte(e.input)}, + {Kind: ast.Key, Data: []byte(`A`)}, + }, + } + compareNode(t, expected, p.Expression()) + } + }) + } +} diff --git a/unmarshaler.go b/unmarshaler.go index 63469852..71793bd6 100644 --- a/unmarshaler.go +++ b/unmarshaler.go @@ -585,6 +585,8 @@ func (d *decoder) handleValue(value *ast.Node, v reflect.Value) error { return d.unmarshalDateTime(value, v) case ast.LocalDate: return d.unmarshalLocalDate(value, v) + case ast.LocalTime: + return d.unmarshalLocalTime(value, v) case ast.LocalDateTime: return d.unmarshalLocalDateTime(value, v) case ast.InlineTable: @@ -730,6 +732,20 @@ func (d *decoder) unmarshalLocalDate(value *ast.Node, v reflect.Value) error { return nil } +func (d *decoder) unmarshalLocalTime(value *ast.Node, v reflect.Value) error { + lt, rest, err := parseLocalTime(value.Data) + if err != nil { + return err + } + + if len(rest) > 0 { + return newDecodeError(rest, "extra characters at the end of a local time") + } + + v.Set(reflect.ValueOf(lt)) + return nil +} + func (d *decoder) unmarshalLocalDateTime(value *ast.Node, v reflect.Value) error { ldt, rest, err := parseLocalDateTime(value.Data) if err != nil { diff --git a/unmarshaler_test.go b/unmarshaler_test.go index 6b134609..b4900f9b 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -339,6 +339,58 @@ func TestUnmarshal(t *testing.T) { } }, }, + { + desc: "local-time with nano second", + input: `a = 12:08:05.666666666`, + gen: func() test { + var v map[string]interface{} + + return test{ + target: &v, + expected: &map[string]interface{}{ + "a": toml.LocalTime{Hour: 12, Minute: 8, Second: 5, Nanosecond: 666666666}, + }, + } + }, + }, + { + desc: "local-time", + input: `a = 12:08:05`, + gen: func() test { + var v map[string]interface{} + + return test{ + target: &v, + expected: &map[string]interface{}{ + "a": toml.LocalTime{Hour: 12, Minute: 8, Second: 5}, + }, + } + }, + }, + { + desc: "local-time missing digit", + input: `a = 12:08:0`, + gen: func() test { + var v map[string]interface{} + + return test{ + target: &v, + err: true, + } + }, + }, + { + desc: "local-time extra digit", + input: `a = 12:08:000`, + gen: func() test { + var v map[string]interface{} + + return test{ + target: &v, + err: true, + } + }, + }, { desc: "issue 475 - space between dots in key", input: `fruit. color = "yellow" From fa07960695263dd9014282118a250e87df338821 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Tue, 27 Jul 2021 18:12:44 -0400 Subject: [PATCH 212/228] Add installation instructions (#572) --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index 698121ba..4931d75d 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,8 @@ Full API, examples, and implementation notes are available in the Go documentati import "github.com/pelletier/go-toml/v2" ``` +See [Modules](#Modules). + ## Features ### Stdlib behavior @@ -189,6 +191,20 @@ provided for completeness.

This table can be generated with ./ci.sh benchmark -a -html.

+## Modules + +go-toml uses Go's standard modules system. + +Installation instructions: + +- Go ≥ 1.16: Nothing to do. Use the import in your code. The `go` command deals + with it automatically. +- Go ≥ 1.13: `GO111MODULE=on go get github.com/pelletier/go-toml/v2`. + +In case of trouble: [Go Modules FAQ][mod-faq]. + +[mod-faq]: https://github.com/golang/go/wiki/Modules#why-does-installing-a-tool-via-go-get-fail-with-error-cannot-find-main-module + ## Migrating from v1 This section describes the differences between v1 and v2, with some pointers on From 69ab7e10d1561a00100500980f098aa16c4dc1df Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Tue, 17 Aug 2021 09:43:52 -0400 Subject: [PATCH 213/228] Go 1.17 release (#574) Minimum supported version: Go 1.16. --- .github/workflows/workflow.yml | 2 +- go.mod | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index f9e073ea..97cddf74 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -12,7 +12,7 @@ jobs: strategy: matrix: os: [ 'ubuntu-latest', 'windows-latest', 'macos-latest'] - go: [ '1.15', '1.16' ] + go: [ '1.16', '1.17' ] runs-on: ${{ matrix.os }} name: ${{ matrix.go }}/${{ matrix.os }} steps: diff --git a/go.mod b/go.mod index 8fed3421..6965c3cd 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/pelletier/go-toml/v2 -go 1.15 +go 1.16 // latest (v1.7.0) doesn't have the fix for time.Time require github.com/stretchr/testify v1.7.1-0.20210427113832-6241f9ab9942 From 1230ca485e02b9a6ba183e39225e693c21a44078 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Tue, 31 Aug 2021 20:22:38 -0400 Subject: [PATCH 214/228] unmarshal: make copy of non addressable values (#576) When unmarshaling into a nested struct in a map, the value is not addressable. In that case, make a copy of it and modify it instead. Fixes #575 --- unmarshaler.go | 6 +++++ unmarshaler_test.go | 54 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/unmarshaler.go b/unmarshaler.go index 71793bd6..5e30a20a 100644 --- a/unmarshaler.go +++ b/unmarshaler.go @@ -408,6 +408,12 @@ func (d *decoder) handleKeyPart(key ast.Iterator, v reflect.Value, nextFn handle mv = makeFn() } set = true + } else if !mv.CanAddr() { + t := v.Type().Elem() + oldmv := mv + mv = reflect.New(t).Elem() + mv.Set(oldmv) + set = true } x, err := nextFn(key, mv) diff --git a/unmarshaler_test.go b/unmarshaler_test.go index b4900f9b..89b96fd6 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -2074,6 +2074,60 @@ func TestIssue508(t *testing.T) { require.Equal(t, "This is a title", t1.head.Title) } +func TestIssue575(t *testing.T) { + b := []byte(` +[pkg.cargo] +version = "0.55.0 (5ae8d74b3 2021-06-22)" +git_commit_hash = "a178d0322ce20e33eac124758e837cbd80a6f633" +[pkg.cargo.target.aarch64-apple-darwin] +available = true +url = "https://static.rust-lang.org/dist/2021-07-29/cargo-1.54.0-aarch64-apple-darwin.tar.gz" +hash = "7bac3901d8eb6a4191ffeebe75b29c78bcb270158ec901addb31f588d965d35d" +xz_url = "https://static.rust-lang.org/dist/2021-07-29/cargo-1.54.0-aarch64-apple-darwin.tar.xz" +xz_hash = "5207644fd6379f3e5b8ae60016b854efa55a381b0c363bff7f9b2f25bfccc430" + +[pkg.cargo.target.aarch64-pc-windows-msvc] +available = true +url = "https://static.rust-lang.org/dist/2021-07-29/cargo-1.54.0-aarch64-pc-windows-msvc.tar.gz" +hash = "eb8ccd9b1f6312b06dc749c17896fa4e9c163661c273dcb61cd7a48376227f6d" +xz_url = "https://static.rust-lang.org/dist/2021-07-29/cargo-1.54.0-aarch64-pc-windows-msvc.tar.xz" +xz_hash = "1a48f723fea1f17d786ce6eadd9d00914d38062d28fd9c455ed3c3801905b388" +`) + + type target struct { + XZ_URL string + } + + type pkg struct { + Target map[string]target + } + + type doc struct { + Pkg map[string]pkg + } + + var dist doc + err := toml.Unmarshal(b, &dist) + require.NoError(t, err) + + expected := doc{ + Pkg: map[string]pkg{ + "cargo": pkg{ + Target: map[string]target{ + "aarch64-apple-darwin": { + XZ_URL: "https://static.rust-lang.org/dist/2021-07-29/cargo-1.54.0-aarch64-apple-darwin.tar.xz", + }, + "aarch64-pc-windows-msvc": { + XZ_URL: "https://static.rust-lang.org/dist/2021-07-29/cargo-1.54.0-aarch64-pc-windows-msvc.tar.xz", + }, + }, + }, + }, + } + + require.Equal(t, expected, dist) +} + //nolint:funlen func TestDecoderStrict(t *testing.T) { examples := []struct { From 40cfb6f45824f8f5cd16957894b5c42e10b65f6e Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Mon, 6 Sep 2021 12:18:45 -0400 Subject: [PATCH 215/228] parser: don't crash on unterminated table key (#580) * parser: don't crash on unterminated table key Fixes #579 * parser: fix format of error returned by expect EOF was missing the format string and %U is not very human friendly. --- parser.go | 6 +++++- unmarshaler_test.go | 6 ++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/parser.go b/parser.go index 03385c12..1781cc94 100644 --- a/parser.go +++ b/parser.go @@ -1030,8 +1030,12 @@ func isValidBinaryRune(r byte) bool { } func expect(x byte, b []byte) ([]byte, error) { + if len(b) == 0 { + return nil, newDecodeError(b, "expected character %c but the document ended here", x) + } + if b[0] != x { - return nil, newDecodeError(b[0:1], "expected character %U", x) + return nil, newDecodeError(b[0:1], "expected character %c", x) } return b[1:], nil diff --git a/unmarshaler_test.go b/unmarshaler_test.go index 89b96fd6..008dfda3 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -1722,6 +1722,12 @@ func TestIssue507(t *testing.T) { require.Error(t, err) } +func TestIssue579(t *testing.T) { + var v interface{} + err := toml.Unmarshal([]byte(`[foo`), &v) + require.Error(t, err) +} + //nolint:funlen func TestUnmarshalDecodeErrors(t *testing.T) { examples := []struct { From 7e2fa1bc80f68e42c9f372db5f4f575991d5c827 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Tue, 7 Sep 2021 10:19:45 -0400 Subject: [PATCH 216/228] unmarshal: fix non-terminated array error Fixes #581 --- parser.go | 3 ++- unmarshaler_test.go | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/parser.go b/parser.go index 1781cc94..a53c2b7b 100644 --- a/parser.go +++ b/parser.go @@ -397,6 +397,7 @@ func (p *parser) parseValArray(b []byte) (ast.Reference, []byte, error) { // array-values =/ ws-comment-newline val ws-comment-newline [ array-sep ] // array-sep = %x2C ; , Comma // ws-comment-newline = *( wschar / [ comment ] newline ) + arrayStart := b b = b[1:] parent := p.builder.Push(ast.Node{ @@ -415,7 +416,7 @@ func (p *parser) parseValArray(b []byte) (ast.Reference, []byte, error) { } if len(b) == 0 { - return parent, nil, newDecodeError(b, "array is incomplete") + return parent, nil, newDecodeError(arrayStart[:1], "array is incomplete") } if b[0] == ']' { diff --git a/unmarshaler_test.go b/unmarshaler_test.go index 008dfda3..25484c2c 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -1728,6 +1728,12 @@ func TestIssue579(t *testing.T) { require.Error(t, err) } +func TestIssue581(t *testing.T) { + var v interface{} + err := toml.Unmarshal([]byte(`P=[#`), &v) + require.Error(t, err) +} + //nolint:funlen func TestUnmarshalDecodeErrors(t *testing.T) { examples := []struct { From 4a5ae9e81e09e3cc5197fccde9105e9fa5ff65b9 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Tue, 7 Sep 2021 10:21:14 -0400 Subject: [PATCH 217/228] errors: fix context generation with only one line --- errors.go | 11 ++++++++++- errors_test.go | 7 +++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/errors.go b/errors.go index a00924b5..d9a6ee76 100644 --- a/errors.go +++ b/errors.go @@ -116,6 +116,7 @@ func wrapDecodeError(document []byte, de *decodeError) *DecodeError { maxLine := errLine + len(after) - 1 lineColumnWidth := len(strconv.Itoa(maxLine)) + // Write the lines of context strictly before the error. for i := len(before) - 1; i > 0; i-- { line := errLine - i buf.WriteString(formatLineNumber(line, lineColumnWidth)) @@ -129,6 +130,8 @@ func wrapDecodeError(document []byte, de *decodeError) *DecodeError { buf.WriteRune('\n') } + // Write the document line that contains the error. + buf.WriteString(formatLineNumber(errLine, lineColumnWidth)) buf.WriteString("| ") @@ -143,6 +146,10 @@ func wrapDecodeError(document []byte, de *decodeError) *DecodeError { } buf.WriteRune('\n') + + // Write the line with the error message itself (so it does not have a line + // number). + buf.WriteString(strings.Repeat(" ", lineColumnWidth)) buf.WriteString("| ") @@ -157,6 +164,8 @@ func wrapDecodeError(document []byte, de *decodeError) *DecodeError { buf.WriteString(errMessage) } + // Write the lines of context strictly after the error. + for i := 1; i < len(after); i++ { buf.WriteRune('\n') line := errLine + i @@ -230,7 +239,7 @@ forward: rest = rest[o+1:] o = 0 - case o == len(rest)-1 && o > 0: + case o == len(rest)-1: // add last line only if it's non-empty afterLines = append(afterLines, rest) diff --git a/errors_test.go b/errors_test.go index d0986478..1c02595f 100644 --- a/errors_test.go +++ b/errors_test.go @@ -148,6 +148,13 @@ line 5`, 6| 7| line 4`, }, + { + desc: "handle remainder of the error line when there is only one line", + doc: [3]string{`P=`, `[`, `#`}, + msg: "array is incomplete", + expected: `1| P=[# + | ~ array is incomplete`, + }, } for _, e := range examples { From a0d685d4822fed8f5589a75bbf6cf734231e1a9c Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Tue, 7 Sep 2021 20:08:59 -0400 Subject: [PATCH 218/228] unmarshal: don't crash on unterminated inline table (#587) Fixes #586 --- parser.go | 6 ++++++ unmarshaler_test.go | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/parser.go b/parser.go index a53c2b7b..829f89e0 100644 --- a/parser.go +++ b/parser.go @@ -353,7 +353,13 @@ func (p *parser) parseInlineTable(b []byte) (ast.Reference, []byte, error) { var err error for len(b) > 0 { + previousB := b b = p.parseWhitespace(b) + + if len(b) == 0 { + return parent, nil, newDecodeError(previousB[:1], "inline table is incomplete") + } + if b[0] == '}' { break } diff --git a/unmarshaler_test.go b/unmarshaler_test.go index 25484c2c..43d49a3d 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -1734,6 +1734,12 @@ func TestIssue581(t *testing.T) { require.Error(t, err) } +func TestIssue586(t *testing.T) { + var v interface{} + err := toml.Unmarshal([]byte(`a={ `), &v) + require.Error(t, err) +} + //nolint:funlen func TestUnmarshalDecodeErrors(t *testing.T) { examples := []struct { From f34c9c332fa0e0b8d3b55c961b46828d92c2cd1c Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Wed, 8 Sep 2021 21:54:30 -0400 Subject: [PATCH 219/228] scanner: fix error reporting for last comments (#591) When an invalid TOML expression ends with a comment before the end of file, the decode error would take a nil from scanComment, which is not part of the document. Fixes #588 --- scanner.go | 2 +- unmarshaler_test.go | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/scanner.go b/scanner.go index b2037022..043adc3d 100644 --- a/scanner.go +++ b/scanner.go @@ -118,7 +118,7 @@ func scanComment(b []byte) ([]byte, []byte) { } } - return b, nil + return b, b[len(b):] } func scanBasicString(b []byte) ([]byte, []byte, error) { diff --git a/unmarshaler_test.go b/unmarshaler_test.go index 43d49a3d..30fc53d0 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -1740,6 +1740,12 @@ func TestIssue586(t *testing.T) { require.Error(t, err) } +func TestIssue588(t *testing.T) { + var v interface{} + err := toml.Unmarshal([]byte(`a=[1#`), &v) + require.Error(t, err) +} + //nolint:funlen func TestUnmarshalDecodeErrors(t *testing.T) { examples := []struct { From fa56f48dafbcbb560f5959149e64683eccac6341 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Thu, 9 Sep 2021 11:59:37 -0400 Subject: [PATCH 220/228] parser: don't overflow when parsing bad times (#593) Fixes #585 --- parser.go | 5 +++++ unmarshaler_test.go | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/parser.go b/parser.go index 829f89e0..da3e5a3a 100644 --- a/parser.go +++ b/parser.go @@ -894,6 +894,11 @@ byteLoop: case c == ' ': if !seenSpace && i+1 < len(b) && isDigit(b[i+1]) { i += 2 + // Avoid reaching past the end of the document in case the time + // is malformed. See TestIssue585. + if i >= len(b) { + i-- + } seenSpace = true hasTime = true } else { diff --git a/unmarshaler_test.go b/unmarshaler_test.go index 30fc53d0..e634a486 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -1746,6 +1746,12 @@ func TestIssue588(t *testing.T) { require.Error(t, err) } +func TestIssue585(t *testing.T) { + var v interface{} + err := toml.Unmarshal([]byte(`a=1979-05127T 0`), &v) + require.Error(t, err) +} + //nolint:funlen func TestUnmarshalDecodeErrors(t *testing.T) { examples := []struct { From ee9b902222c33ad156960573001c3aae9bcc963c Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Thu, 9 Sep 2021 21:25:14 -0400 Subject: [PATCH 221/228] unmarshal: convert ints if target type is compatible (#594) This is required to support custom types. Fixes #590 --- marshaler_test.go | 23 ++++++++++++++++------- unmarshaler.go | 34 +++++++++++++++++++++------------- 2 files changed, 37 insertions(+), 20 deletions(-) diff --git a/marshaler_test.go b/marshaler_test.go index 8667a813..dcc976d7 100644 --- a/marshaler_test.go +++ b/marshaler_test.go @@ -782,6 +782,22 @@ func TestIssue424(t *testing.T) { require.Equal(t, msg2, msg2parsed) } +func TestIssue567(t *testing.T) { + var m map[string]interface{} + err := toml.Unmarshal([]byte("A = 12:08:05"), &m) + require.NoError(t, err) + require.IsType(t, m["A"], toml.LocalTime{}) +} + +func TestIssue590(t *testing.T) { + type CustomType int + var cfg struct { + Option CustomType `toml:"option"` + } + err := toml.Unmarshal([]byte("option = 42"), &cfg) + require.NoError(t, err) +} + func ExampleMarshal() { type MyConfig struct { Version int @@ -806,10 +822,3 @@ func ExampleMarshal() { // Name = 'go-toml' // Tags = ['go', 'toml'] } - -func TestIssue567(t *testing.T) { - var m map[string]interface{} - err := toml.Unmarshal([]byte("A = 12:08:05"), &m) - require.NoError(t, err) - require.IsType(t, m["A"], toml.LocalTime{}) -} diff --git a/unmarshaler.go b/unmarshaler.go index 5e30a20a..1fdd6866 100644 --- a/unmarshaler.go +++ b/unmarshaler.go @@ -823,71 +823,79 @@ func (d *decoder) unmarshalInteger(value *ast.Node, v reflect.Value) error { return err } + var r reflect.Value + switch v.Kind() { case reflect.Int64: v.SetInt(i) + return nil case reflect.Int32: if i < math.MinInt32 || i > math.MaxInt32 { return fmt.Errorf("toml: number %d does not fit in an int32", i) } - v.Set(reflect.ValueOf(int32(i))) - return nil + r = reflect.ValueOf(int32(i)) case reflect.Int16: if i < math.MinInt16 || i > math.MaxInt16 { return fmt.Errorf("toml: number %d does not fit in an int16", i) } - v.Set(reflect.ValueOf(int16(i))) + r = reflect.ValueOf(int16(i)) case reflect.Int8: if i < math.MinInt8 || i > math.MaxInt8 { return fmt.Errorf("toml: number %d does not fit in an int8", i) } - v.Set(reflect.ValueOf(int8(i))) + r = reflect.ValueOf(int8(i)) case reflect.Int: if i < minInt || i > maxInt { return fmt.Errorf("toml: number %d does not fit in an int", i) } - v.Set(reflect.ValueOf(int(i))) + r = reflect.ValueOf(int(i)) case reflect.Uint64: if i < 0 { return fmt.Errorf("toml: negative number %d does not fit in an uint64", i) } - v.Set(reflect.ValueOf(uint64(i))) + r = reflect.ValueOf(uint64(i)) case reflect.Uint32: if i < 0 || i > math.MaxUint32 { return fmt.Errorf("toml: negative number %d does not fit in an uint32", i) } - v.Set(reflect.ValueOf(uint32(i))) + r = reflect.ValueOf(uint32(i)) case reflect.Uint16: if i < 0 || i > math.MaxUint16 { return fmt.Errorf("toml: negative number %d does not fit in an uint16", i) } - v.Set(reflect.ValueOf(uint16(i))) + r = reflect.ValueOf(uint16(i)) case reflect.Uint8: if i < 0 || i > math.MaxUint8 { return fmt.Errorf("toml: negative number %d does not fit in an uint8", i) } - v.Set(reflect.ValueOf(uint8(i))) + r = reflect.ValueOf(uint8(i)) case reflect.Uint: if i < 0 { return fmt.Errorf("toml: negative number %d does not fit in an uint", i) } - v.Set(reflect.ValueOf(uint(i))) + r = reflect.ValueOf(uint(i)) case reflect.Interface: - v.Set(reflect.ValueOf(i)) + r = reflect.ValueOf(i) default: - err = fmt.Errorf("toml: cannot store TOML integer into a Go %s", v.Kind()) + return fmt.Errorf("toml: cannot store TOML integer into a Go %s", v.Kind()) } - return err + if !r.Type().AssignableTo(v.Type()) { + r = r.Convert(v.Type()) + } + + v.Set(r) + + return nil } func (d *decoder) unmarshalString(value *ast.Node, v reflect.Value) error { From 476492a85c7b4862cf1f9e0bc26c86d017ea7828 Mon Sep 17 00:00:00 2001 From: Cameron Moore Date: Sat, 25 Sep 2021 12:02:23 -0500 Subject: [PATCH 222/228] unmarshal: support lowercase 'T' and 'Z' in date-time parsing (#601) RFC3399 allows for lowercase 't' and 'z' in date-time values. Fixes #600 --- decode.go | 4 ++-- parser.go | 4 ++-- unmarshaler_test.go | 7 +++++++ 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/decode.go b/decode.go index 3f44d168..aa646100 100644 --- a/decode.go +++ b/decode.go @@ -75,7 +75,7 @@ func parseDateTime(b []byte) (time.Time, error) { panic("date time should have a timezone") } - if b[0] == 'Z' { + if b[0] == 'Z' || b[0] == 'z' { b = b[1:] zone = time.UTC } else { @@ -127,7 +127,7 @@ func parseLocalDateTime(b []byte) (LocalDateTime, []byte, error) { dt.LocalDate = date sep := b[10] - if sep != 'T' && sep != ' ' { + if sep != 'T' && sep != ' ' && sep != 't' { return dt, nil, newDecodeError(b[10:11], "datetime separator is expected to be T or a space") } diff --git a/parser.go b/parser.go index da3e5a3a..b86904ae 100644 --- a/parser.go +++ b/parser.go @@ -887,9 +887,9 @@ byteLoop: if i >= minOffsetOfTz { hasTz = true } - case c == 'T' || c == ':' || c == '.': + case c == 'T' || c == 't' || c == ':' || c == '.': hasTime = true - case c == '+' || c == '-' || c == 'Z': + case c == '+' || c == '-' || c == 'Z' || c == 'z': hasTz = true case c == ' ': if !seenSpace && i+1 < len(b) && isDigit(b[i+1]) { diff --git a/unmarshaler_test.go b/unmarshaler_test.go index e634a486..dbe8fb34 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -1752,6 +1752,13 @@ func TestIssue585(t *testing.T) { require.Error(t, err) } +// Support lowercase 'T' and 'Z' +func TestIssue600(t *testing.T) { + var v interface{} + err := toml.Unmarshal([]byte(`a=1979-05-27t00:32:00z`), &v) + require.NoError(t, err) +} + //nolint:funlen func TestUnmarshalDecodeErrors(t *testing.T) { examples := []struct { From 62acca2b685b7ddbf8a73b85667bec0a9f74b9a3 Mon Sep 17 00:00:00 2001 From: Cameron Moore Date: Sun, 3 Oct 2021 21:15:30 -0500 Subject: [PATCH 223/228] tomltestgen: add toml-test unit test generation command (#610) Tests are hidden behind a "testsuite" build tag for now since many tests are failing. Use `go test -tags testsuite` to activate. Use `go generate` to regenerate toml_testgen_test.go. Co-authored-by: Thomas Pelletier --- cmd/tomltestgen/main.go | 224 ++++ testsuite/add.go | 74 ++ testsuite/parser.go | 69 ++ testsuite/rm.go | 110 ++ testsuite/testsuite.go | 48 + toml_testgen_support_test.go | 135 +-- toml_testgen_test.go | 2056 +++++++++++++++++++++------------- 7 files changed, 1829 insertions(+), 887 deletions(-) create mode 100644 cmd/tomltestgen/main.go create mode 100644 testsuite/add.go create mode 100644 testsuite/parser.go create mode 100644 testsuite/rm.go create mode 100644 testsuite/testsuite.go diff --git a/cmd/tomltestgen/main.go b/cmd/tomltestgen/main.go new file mode 100644 index 00000000..6603d802 --- /dev/null +++ b/cmd/tomltestgen/main.go @@ -0,0 +1,224 @@ +// tomltestgen retrieves a given version of the language-agnostic TOML test suite in +// https://github.com/BurntSushi/toml-test and generates go-toml unit tests. +// +// Within the go-toml package, run `go generate`. Otherwise, use: +// +// go run github.com/pelletier/go-toml/cmd/tomltestgen -o toml_testgen_test.go +package main + +import ( + "archive/zip" + "bytes" + "flag" + "fmt" + "go/format" + "io" + "io/ioutil" + "log" + "net/http" + "os" + "regexp" + "strconv" + "strings" + "text/template" + "time" +) + +type invalid struct { + Name string + Input string +} + +type valid struct { + Name string + Input string + JsonRef string +} + +type testsCollection struct { + Ref string + Timestamp string + Invalid []invalid + Valid []valid + Count int +} + +const srcTemplate = "// +build testsuite\n\n" + + "// Generated by tomltestgen for toml-test ref {{.Ref}} on {{.Timestamp}}\n" + + "package toml_test\n" + + " import (\n" + + " \"testing\"\n" + + ")\n" + + + "{{range .Invalid}}\n" + + "func TestTOMLTest_Invalid_{{.Name}}(t *testing.T) {\n" + + " input := {{.Input|gostr}}\n" + + " testgenInvalid(t, input)\n" + + "}\n" + + "{{end}}\n" + + "\n" + + "{{range .Valid}}\n" + + "func TestTOMLTest_Valid_{{.Name}}(t *testing.T) {\n" + + " input := {{.Input|gostr}}\n" + + " jsonRef := {{.JsonRef|gostr}}\n" + + " testgenValid(t, input, jsonRef)\n" + + "}\n" + + "{{end}}\n" + +func downloadTmpFile(url string) string { + log.Println("starting to download file from", url) + resp, err := http.Get(url) + if err != nil { + panic(err) + } + defer resp.Body.Close() + + tmpfile, err := ioutil.TempFile("", "toml-test-*.zip") + if err != nil { + panic(err) + } + defer tmpfile.Close() + + copiedLen, err := io.Copy(tmpfile, resp.Body) + if err != nil { + panic(err) + } + if resp.ContentLength > 0 && copiedLen != resp.ContentLength { + panic(fmt.Errorf("copied %d bytes, request body had %d", copiedLen, resp.ContentLength)) + } + return tmpfile.Name() +} + +func kebabToCamel(kebab string) string { + camel := "" + nextUpper := true + for _, c := range kebab { + if nextUpper { + camel += strings.ToUpper(string(c)) + nextUpper = false + } else if c == '-' { + nextUpper = true + } else if c == '/' { + nextUpper = true + camel += "_" + } else { + camel += string(c) + } + } + return camel +} + +func readFileFromZip(f *zip.File) string { + reader, err := f.Open() + if err != nil { + panic(err) + } + defer reader.Close() + bytes, err := ioutil.ReadAll(reader) + if err != nil { + panic(err) + } + return string(bytes) +} + +func templateGoStr(input string) string { + return strconv.Quote(input) +} + +var ( + ref = flag.String("r", "master", "git reference") + out = flag.String("o", "", "output file") +) + +func usage() { + _, _ = fmt.Fprintf(os.Stderr, "usage: tomltestgen [flags]\n") + flag.PrintDefaults() +} + +func main() { + flag.Usage = usage + flag.Parse() + + url := "https://codeload.github.com/BurntSushi/toml-test/zip/" + *ref + resultFile := downloadTmpFile(url) + defer os.Remove(resultFile) + log.Println("file written to", resultFile) + + zipReader, err := zip.OpenReader(resultFile) + if err != nil { + panic(err) + } + defer zipReader.Close() + + collection := testsCollection{ + Ref: *ref, + Timestamp: time.Now().Format(time.RFC3339), + } + + zipFilesMap := map[string]*zip.File{} + + for _, f := range zipReader.File { + zipFilesMap[f.Name] = f + } + + testFileRegexp := regexp.MustCompile(`([^/]+/tests/(valid|invalid)/(.+))\.(toml)`) + for _, f := range zipReader.File { + groups := testFileRegexp.FindStringSubmatch(f.Name) + if len(groups) > 0 { + name := kebabToCamel(groups[3]) + testType := groups[2] + + log.Printf("> [%s] %s\n", testType, name) + + tomlContent := readFileFromZip(f) + + switch testType { + case "invalid": + collection.Invalid = append(collection.Invalid, invalid{ + Name: name, + Input: tomlContent, + }) + collection.Count++ + case "valid": + baseFilePath := groups[1] + jsonFilePath := baseFilePath + ".json" + jsonContent := readFileFromZip(zipFilesMap[jsonFilePath]) + + collection.Valid = append(collection.Valid, valid{ + Name: name, + Input: tomlContent, + JsonRef: jsonContent, + }) + collection.Count++ + default: + panic(fmt.Sprintf("unknown test type: %s", testType)) + } + } + } + + log.Printf("Collected %d tests from toml-test\n", collection.Count) + + funcMap := template.FuncMap{ + "gostr": templateGoStr, + } + t := template.Must(template.New("src").Funcs(funcMap).Parse(srcTemplate)) + buf := new(bytes.Buffer) + err = t.Execute(buf, collection) + if err != nil { + panic(err) + } + outputBytes, err := format.Source(buf.Bytes()) + if err != nil { + panic(err) + } + + if *out == "" { + fmt.Println(string(outputBytes)) + return + } + + err = os.WriteFile(*out, outputBytes, 0644) + if err != nil { + panic(err) + } +} diff --git a/testsuite/add.go b/testsuite/add.go new file mode 100644 index 00000000..3f9bea99 --- /dev/null +++ b/testsuite/add.go @@ -0,0 +1,74 @@ +package testsuite + +import ( + "fmt" + "math" + "time" + + "github.com/pelletier/go-toml/v2" +) + +// addTag adds JSON tags to a data structure as expected by toml-test. +func addTag(key string, tomlData interface{}) interface{} { + // Switch on the data type. + switch orig := tomlData.(type) { + default: + //return map[string]interface{}{} + panic(fmt.Sprintf("Unknown type: %T", tomlData)) + + // A table: we don't need to add any tags, just recurse for every table + // entry. + case map[string]interface{}: + typed := make(map[string]interface{}, len(orig)) + for k, v := range orig { + typed[k] = addTag(k, v) + } + return typed + + // An array: we don't need to add any tags, just recurse for every table + // entry. + case []map[string]interface{}: + typed := make([]map[string]interface{}, len(orig)) + for i, v := range orig { + typed[i] = addTag("", v).(map[string]interface{}) + } + return typed + case []interface{}: + typed := make([]interface{}, len(orig)) + for i, v := range orig { + typed[i] = addTag("", v) + } + return typed + + // Datetime: tag as datetime. + case toml.LocalTime: + return tag("time-local", orig.String()) + case toml.LocalDate: + return tag("date-local", orig.String()) + case toml.LocalDateTime: + return tag("datetime-local", orig.String()) + case time.Time: + return tag("datetime", orig.Format("2006-01-02T15:04:05.999999999Z07:00")) + + // Tag primitive values: bool, string, int, and float64. + case bool: + return tag("bool", fmt.Sprintf("%v", orig)) + case string: + return tag("string", orig) + case int64: + return tag("integer", fmt.Sprintf("%d", orig)) + case float64: + // Special case for nan since NaN == NaN is false. + if math.IsNaN(orig) { + return tag("float", "nan") + } + return tag("float", fmt.Sprintf("%v", orig)) + } +} + +func tag(typeName string, data interface{}) map[string]interface{} { + return map[string]interface{}{ + "type": typeName, + "value": data, + } +} diff --git a/testsuite/parser.go b/testsuite/parser.go new file mode 100644 index 00000000..afb0005c --- /dev/null +++ b/testsuite/parser.go @@ -0,0 +1,69 @@ +package testsuite + +import ( + "bytes" + "encoding/json" + "fmt" + + "github.com/pelletier/go-toml/v2" +) + +type parser struct{} + +func (p parser) Decode(input string) (output string, outputIsError bool, retErr error) { + defer func() { + if r := recover(); r != nil { + switch rr := r.(type) { + case error: + retErr = rr + default: + retErr = fmt.Errorf("%s", rr) + } + } + }() + + var v interface{} + + if err := toml.Unmarshal([]byte(input), &v); err != nil { + return err.Error(), true, nil + } + + j, err := json.MarshalIndent(addTag("", v), "", " ") + if err != nil { + return "", false, retErr + } + + return string(j), false, retErr +} + +func (p parser) Encode(input string) (output string, outputIsError bool, retErr error) { + defer func() { + if r := recover(); r != nil { + switch rr := r.(type) { + case error: + retErr = rr + default: + retErr = fmt.Errorf("%s", rr) + } + } + }() + + var tmp interface{} + err := json.Unmarshal([]byte(input), &tmp) + if err != nil { + return "", false, err + } + + rm, err := rmTag(tmp) + if err != nil { + return err.Error(), true, retErr + } + + buf := new(bytes.Buffer) + err = toml.NewEncoder(buf).Encode(rm) + if err != nil { + return err.Error(), true, retErr + } + + return buf.String(), false, retErr +} diff --git a/testsuite/rm.go b/testsuite/rm.go new file mode 100644 index 00000000..18a97039 --- /dev/null +++ b/testsuite/rm.go @@ -0,0 +1,110 @@ +package testsuite + +import ( + "fmt" + "strconv" + "time" +) + +// Remove JSON tags to a data structure as returned by toml-test. +func rmTag(typedJson interface{}) (interface{}, error) { + // Check if key is in the table m. + in := func(key string, m map[string]interface{}) bool { + _, ok := m[key] + return ok + } + + // Switch on the data type. + switch v := typedJson.(type) { + + // Object: this can either be a TOML table or a primitive with tags. + case map[string]interface{}: + // This value represents a primitive: remove the tags and return just + // the primitive value. + if len(v) == 2 && in("type", v) && in("value", v) { + ut, err := untag(v) + if err != nil { + return ut, fmt.Errorf("tag.Remove: %w", err) + } + return ut, nil + } + + // Table: remove tags on all children. + m := make(map[string]interface{}, len(v)) + for k, v2 := range v { + var err error + m[k], err = rmTag(v2) + if err != nil { + return nil, err + } + } + return m, nil + + // Array: remove tags from all itenm. + case []interface{}: + a := make([]interface{}, len(v)) + for i := range v { + var err error + a[i], err = rmTag(v[i]) + if err != nil { + return nil, err + } + } + return a, nil + } + + // The top level must be an object or array. + return nil, fmt.Errorf("unrecognized JSON format '%T'", typedJson) +} + +// Return a primitive: read the "type" and convert the "value" to that. +func untag(typed map[string]interface{}) (interface{}, error) { + t := typed["type"].(string) + v := typed["value"].(string) + switch t { + case "string": + return v, nil + case "integer": + n, err := strconv.ParseInt(v, 10, 64) + if err != nil { + return nil, fmt.Errorf("untag: %w", err) + } + return n, nil + case "float": + f, err := strconv.ParseFloat(v, 64) + if err != nil { + return nil, fmt.Errorf("untag: %w", err) + } + return f, nil + case "datetime": + return parseTime(v, "2006-01-02T15:04:05.999999999Z07:00", false) + case "datetime-local": + return parseTime(v, "2006-01-02T15:04:05.999999999", true) + case "date-local": + return parseTime(v, "2006-01-02", true) + case "time-local": + return parseTime(v, "15:04:05.999999999", true) + case "bool": + switch v { + case "true": + return true, nil + case "false": + return false, nil + } + return nil, fmt.Errorf("untag: could not parse %q as a boolean", v) + } + + return nil, fmt.Errorf("untag: unrecognized tag type %q", t) +} + +func parseTime(v, format string, local bool) (t time.Time, err error) { + if local { + t, err = time.ParseInLocation(format, v, time.Local) + } else { + t, err = time.Parse(format, v) + } + if err != nil { + return time.Time{}, fmt.Errorf("Could not parse %q as a datetime: %w", v, err) + } + return t, nil +} diff --git a/testsuite/testsuite.go b/testsuite/testsuite.go new file mode 100644 index 00000000..56c3a037 --- /dev/null +++ b/testsuite/testsuite.go @@ -0,0 +1,48 @@ +// Package testsuite provides helper functions for interoperating with the +// language-agnostic TOML test suite at github.com/BurntSushi/toml-test. +package testsuite + +import ( + "encoding/json" + "log" + "os" + + "github.com/pelletier/go-toml/v2" +) + +// Marshal is a helpfer function for calling toml.Marshal +// +// Only needed to avoid package import loops. +func Marshal(v interface{}) ([]byte, error) { + return toml.Marshal(v) +} + +// Unmarshal is a helper function for calling toml.Unmarshal. +// +// Only needed to avoid package import loops. +func Unmarshal(data []byte, v interface{}) error { + return toml.Unmarshal(data, v) +} + +// ValueToTaggedJSON takes a data structure and returns the tagged JSON +// representation. +func ValueToTaggedJSON(doc interface{}) ([]byte, error) { + return json.MarshalIndent(addTag("", doc), "", " ") +} + +// DecodeStdin is a helper function for the toml-test binary interface. TOML input +// is read from STDIN and a resulting tagged JSON representation is written to +// STDOUT. +func DecodeStdin() { + var decoded map[string]interface{} + + if err := toml.NewDecoder(os.Stdin).Decode(&decoded); err != nil { + log.Fatalf("Error decoding TOML: %s", err) + } + + j := json.NewEncoder(os.Stdout) + j.SetIndent("", " ") + if err := j.Encode(addTag("", decoded)); err != nil { + log.Fatalf("Error encoding JSON: %s", err) + } +} diff --git a/toml_testgen_support_test.go b/toml_testgen_support_test.go index 071ea6da..ea974181 100644 --- a/toml_testgen_support_test.go +++ b/toml_testgen_support_test.go @@ -1,14 +1,13 @@ +//go:generate go run ./cmd/tomltestgen/main.go -o toml_testgen_test.go + // This is a support file for toml_testgen_test.go package toml_test import ( "encoding/json" - "fmt" - "strconv" "testing" - "time" - "github.com/pelletier/go-toml/v2" + "github.com/pelletier/go-toml/v2/testsuite" "github.com/stretchr/testify/require" ) @@ -17,10 +16,14 @@ func testgenInvalid(t *testing.T, input string) { t.Logf("Input TOML:\n%s", input) doc := map[string]interface{}{} - err := toml.Unmarshal([]byte(input), &doc) + err := testsuite.Unmarshal([]byte(input), &doc) if err == nil { - t.Log(json.Marshal(doc)) + out, err := json.Marshal(doc) + if err != nil { + panic("could not marshal map to json") + } + t.Log("JSON output from unmarshal:", string(out)) t.Fatalf("test did not fail") } } @@ -29,124 +32,14 @@ func testgenValid(t *testing.T, input string, jsonRef string) { t.Helper() t.Logf("Input TOML:\n%s", input) - doc := map[string]interface{}{} + // TODO: change this to interface{} + var doc map[string]interface{} - err := toml.Unmarshal([]byte(input), &doc) + err := testsuite.Unmarshal([]byte(input), &doc) if err != nil { t.Fatalf("failed parsing toml: %s", err) } - - refDoc := testgenBuildRefDoc(jsonRef) - - require.Equal(t, refDoc, doc) - - out, err := toml.Marshal(doc) - require.NoError(t, err) - - doc2 := map[string]interface{}{} - err = toml.Unmarshal(out, &doc2) + j, err := testsuite.ValueToTaggedJSON(doc) require.NoError(t, err) - - require.Equal(t, refDoc, doc2) -} - -func testgenBuildRefDoc(jsonRef string) map[string]interface{} { - descTree := map[string]interface{}{} - - err := json.Unmarshal([]byte(jsonRef), &descTree) - if err != nil { - panic(fmt.Sprintf("reference doc should be valid JSON: %s", err)) - } - - doc := testGenTranslateDesc(descTree) - if doc == nil { - return map[string]interface{}{} - } - - return doc.(map[string]interface{}) -} - -//nolint:funlen,gocognit,cyclop -func testGenTranslateDesc(input interface{}) interface{} { - a, ok := input.([]interface{}) - if ok { - xs := make([]interface{}, len(a)) - for i, v := range a { - xs[i] = testGenTranslateDesc(v) - } - - return xs - } - - d, ok := input.(map[string]interface{}) - if !ok { - panic(fmt.Sprintf("input should be valid map[string]: %v", input)) - } - - var ( - dtype string - dvalue interface{} - ) - - //nolint:nestif - if len(d) == 2 { - dtypeiface, ok := d["type"] - if ok { - dvalue, ok = d["value"] - if ok { - dtype = dtypeiface.(string) - - switch dtype { - case "string": - return dvalue.(string) - case "float": - v, err := strconv.ParseFloat(dvalue.(string), 64) - if err != nil { - panic(fmt.Sprintf("invalid float '%s': %s", dvalue, err)) - } - - return v - case "integer": - v, err := strconv.ParseInt(dvalue.(string), 10, 64) - if err != nil { - panic(fmt.Sprintf("invalid int '%s': %s", dvalue, err)) - } - - return v - case "bool": - return dvalue.(string) == "true" - case "datetime": - dt, err := time.Parse("2006-01-02T15:04:05Z", dvalue.(string)) - if err != nil { - panic(fmt.Sprintf("invalid datetime '%s': %s", dvalue, err)) - } - - return dt - case "array": - if dvalue == nil { - return nil - } - - a := dvalue.([]interface{}) - - xs := make([]interface{}, len(a)) - - for i, v := range a { - xs[i] = testGenTranslateDesc(v) - } - - return xs - } - - panic(fmt.Sprintf("unknown type: %s", dtype)) - } - } - } - - dest := map[string]interface{}{} - for k, v := range d { - dest[k] = testGenTranslateDesc(v) - } - - return dest + require.Equal(t, jsonRef, string(j)+"\n") } diff --git a/toml_testgen_test.go b/toml_testgen_test.go index 1be0d14f..3eab4f73 100644 --- a/toml_testgen_test.go +++ b/toml_testgen_test.go @@ -1,1002 +1,1526 @@ -// Generated by tomltestgen for toml-test ref 39e37e6 on 2019-03-19T23:58:45-07:00 +//go:build testsuite +// +build testsuite + +// Generated by tomltestgen for toml-test ref master on 2021-09-30T20:29:36-05:00 package toml_test import ( "testing" ) -func TestInvalidDatetimeMalformedNoLeads(t *testing.T) { +func TestTOMLTest_Invalid_Array_MissingSeparator(t *testing.T) { + input := "wrong = [ 1 2 3 ]\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Array_NoClose2(t *testing.T) { + input := "x = [42 #\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Array_NoCloseTable2(t *testing.T) { + input := "x = [{ key = 42 #\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Array_NoCloseTable(t *testing.T) { + input := "x = [{ key = 42\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Array_NoClose(t *testing.T) { + input := "long_array = [ 1, 2, 3\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Array_Tables1(t *testing.T) { + input := "# INVALID TOML DOC\nfruit = []\n\n[[fruit]] # Not allowed\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Array_Tables2(t *testing.T) { + input := "# INVALID TOML DOC\n[[fruit]]\n name = \"apple\"\n\n [[fruit.variety]]\n name = \"red delicious\"\n\n # This table conflicts with the previous table\n [fruit.variety]\n name = \"granny smith\"\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Array_TextAfterArrayEntries(t *testing.T) { + input := "array = [\n \"Is there life after an array separator?\", No\n \"Entry\"\n]\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Array_TextBeforeArraySeparator(t *testing.T) { + input := "array = [\n \"Is there life before an array separator?\" No,\n \"Entry\"\n]\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Array_TextInArray(t *testing.T) { + input := "array = [\n \"Entry 1\",\n I don't belong,\n \"Entry 2\",\n]\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Bool_MixedCase(t *testing.T) { + input := "valid = False\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Bool_WrongCaseFalse(t *testing.T) { + input := "b = FALSE\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Bool_WrongCaseTrue(t *testing.T) { + input := "a = TRUE\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Control_CommentDel(t *testing.T) { + input := "comment-del = \"0x7f\" # \u007f\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Control_CommentLf(t *testing.T) { + input := "comment-lf = \"ctrl-P\" # \x10\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Control_CommentNull(t *testing.T) { + input := "comment-null = \"null\" # \x00\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Control_CommentUs(t *testing.T) { + input := "comment-us = \"ctrl-_\" # \x1f\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Control_MultiDel(t *testing.T) { + input := "multi-del = \"\"\"null\u007f\"\"\"\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Control_MultiLf(t *testing.T) { + input := "multi-lf = \"\"\"null\x10\"\"\"\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Control_MultiNull(t *testing.T) { + input := "multi-null = \"\"\"null\x00\"\"\"\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Control_MultiUs(t *testing.T) { + input := "multi-us = \"\"\"null\x1f\"\"\"\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Control_RawmultiDel(t *testing.T) { + input := "rawmulti-del = '''null\u007f'''\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Control_RawmultiLf(t *testing.T) { + input := "rawmulti-lf = '''null\x10'''\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Control_RawmultiNull(t *testing.T) { + input := "rawmulti-null = '''null\x00'''\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Control_RawmultiUs(t *testing.T) { + input := "rawmulti-us = '''null\x1f'''\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Control_RawstringDel(t *testing.T) { + input := "rawstring-del = 'null\u007f'\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Control_RawstringLf(t *testing.T) { + input := "rawstring-lf = 'null\x10'\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Control_RawstringNull(t *testing.T) { + input := "rawstring-null = 'null\x00'\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Control_RawstringUs(t *testing.T) { + input := "rawstring-us = 'null\x1f'\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Control_StringBs(t *testing.T) { + input := "string-bs = \"backspace\b\"\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Control_StringDel(t *testing.T) { + input := "string-del = \"null\u007f\"\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Control_StringLf(t *testing.T) { + input := "string-lf = \"null\x10\"\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Control_StringNull(t *testing.T) { + input := "string-null = \"null\x00\"\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Control_StringUs(t *testing.T) { + input := "string-us = \"null\x1f\"\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Datetime_ImpossibleDate(t *testing.T) { + input := "d = 2006-01-50T00:00:00Z\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Datetime_NoLeadsWithMilli(t *testing.T) { + input := "with-milli = 1987-07-5T17:45:00.12Z\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Datetime_NoLeads(t *testing.T) { + input := "no-leads = 1987-7-05T17:45:00Z\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Datetime_NoSecs(t *testing.T) { + input := "no-secs = 1987-07-05T17:45Z\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Datetime_NoT(t *testing.T) { + input := "no-t = 1987-07-0517:45:00Z\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Datetime_TrailingT(t *testing.T) { + input := "d = 2006-01-30T\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Encoding_BadUtf8AtEnd(t *testing.T) { + input := "# There is a 0xda at after the quotes, and no EOL at the end of the file.\n#\n# This is a bit of an edge case: This indicates there should be two bytes\n# (0b1101_1010) but there is no byte to follow because it's the end of the file.\nx = \"\"\"\"\"\"\xda" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Encoding_BadUtf8InComment(t *testing.T) { + input := "# \xc3\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Encoding_BadUtf8InString(t *testing.T) { + input := "# The following line contains an invalid UTF-8 sequence.\nbad = \"\xc3\"\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Encoding_BomNotAtStart1(t *testing.T) { + input := "bom-not-at-start \xff\xfd\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Encoding_BomNotAtStart2(t *testing.T) { + input := "bom-not-at-start= \xff\xfd\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Encoding_Utf16Bom(t *testing.T) { + input := "\xfe\xff\x00#\x00 \x00U\x00T\x00F\x00-\x001\x006\x00 \x00w\x00i\x00t\x00h\x00 \x00B\x00O\x00M\x00\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Encoding_Utf16(t *testing.T) { + input := "\x00#\x00 \x00U\x00T\x00F\x00-\x001\x006\x00 \x00w\x00i\x00t\x00h\x00o\x00u\x00t\x00 \x00B\x00O\x00M\x00\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Float_DoublePoint1(t *testing.T) { + input := "double-point-1 = 0..1\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Float_DoublePoint2(t *testing.T) { + input := "double-point-2 = 0.1.2\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Float_ExpDoubleE1(t *testing.T) { + input := "exp-double-e-1 = 1ee2\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Float_ExpDoubleE2(t *testing.T) { + input := "exp-double-e-2 = 1e2e3\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Float_ExpDoubleUs(t *testing.T) { + input := "exp-double-us = 1e__23\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Float_ExpLeadingUs(t *testing.T) { + input := "exp-leading-us = 1e_23\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Float_ExpPoint1(t *testing.T) { + input := "exp-point-1 = 1e2.3\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Float_ExpPoint2(t *testing.T) { + input := "exp-point-2 = 1.e2\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Float_ExpTrailingUs(t *testing.T) { + input := "exp-trailing-us = 1e_23_\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Float_InfIncomplete1(t *testing.T) { + input := "inf-incomplete-1 = in\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Float_InfIncomplete2(t *testing.T) { + input := "inf-incomplete-2 = +in\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Float_InfIncomplete3(t *testing.T) { + input := "inf-incomplete-3 = -in\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Float_Inf_underscore(t *testing.T) { + input := "inf_underscore = in_f\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Float_LeadingPointNeg(t *testing.T) { + input := "leading-point-neg = -.12345\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Float_LeadingPointPlus(t *testing.T) { + input := "leading-point-plus = +.12345\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Float_LeadingPoint(t *testing.T) { + input := "leading-point = .12345\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Float_LeadingUs(t *testing.T) { + input := "leading-us = _1.2\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Float_LeadingZeroNeg(t *testing.T) { + input := "leading-zero-neg = -03.14\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Float_LeadingZeroPlus(t *testing.T) { + input := "leading-zero-plus = +03.14\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Float_LeadingZero(t *testing.T) { + input := "leading-zero = 03.14\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Float_NanIncomplete1(t *testing.T) { + input := "nan-incomplete-1 = na\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Float_NanIncomplete2(t *testing.T) { + input := "nan-incomplete-2 = +na\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Float_NanIncomplete3(t *testing.T) { + input := "nan-incomplete-3 = -na\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Float_Nan_underscore(t *testing.T) { + input := "nan_underscore = na_n\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Float_TrailingPointMin(t *testing.T) { + input := "trailing-point-min = -1.\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Float_TrailingPointPlus(t *testing.T) { + input := "trailing-point-plus = +1.\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Float_TrailingPoint(t *testing.T) { + input := "trailing-point = 1.\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Float_TrailingUs(t *testing.T) { + input := "trailing-us = 1.2_\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Float_UsAfterPoint(t *testing.T) { + input := "us-after-point = 1._2\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Float_UsBeforePoint(t *testing.T) { + input := "us-before-point = 1_.2\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_InlineTable_DoubleComma(t *testing.T) { + input := "t = {x=3,,y=4}\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_InlineTable_Empty(t *testing.T) { + input := "t = {,}\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_InlineTable_Linebreak1(t *testing.T) { + input := "# No newlines are allowed between the curly braces unless they are valid within\n# a value.\nsimple = { a = 1 \n}\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_InlineTable_Linebreak2(t *testing.T) { + input := "t = {a=1,\nb=2}\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_InlineTable_Linebreak3(t *testing.T) { + input := "t = {a=1\n,b=2}\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_InlineTable_Linebreak4(t *testing.T) { + input := "json_like = {\n first = \"Tom\",\n last = \"Preston-Werner\"\n}\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_InlineTable_NoComma(t *testing.T) { + input := "t = {x = 3 y = 4}\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_InlineTable_TrailingComma(t *testing.T) { + input := "# A terminating comma (also called trailing comma) is not permitted after the\n# last key/value pair in an inline table\nabc = { abc = 123, }\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Integer_CapitalBin(t *testing.T) { + input := "capital-bin = 0B0\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Integer_CapitalHex(t *testing.T) { + input := "capital-hex = 0X1\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Integer_CapitalOct(t *testing.T) { + input := "capital-oct = 0O0\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Integer_DoubleSignNex(t *testing.T) { + input := "double-sign-nex = --99\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Integer_DoubleSignPlus(t *testing.T) { + input := "double-sign-plus = ++99\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Integer_DoubleUs(t *testing.T) { + input := "double-us = 1__23\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Integer_InvalidBin(t *testing.T) { + input := "invalid-bin = 0b0012\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Integer_InvalidHex(t *testing.T) { + input := "invalid-hex = 0xaafz\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Integer_InvalidOct(t *testing.T) { + input := "invalid-oct = 0o778\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Integer_LeadingUsBin(t *testing.T) { + input := "leading-us-bin = _0o1\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Integer_LeadingUsHex(t *testing.T) { + input := "leading-us-hex = _0o1\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Integer_LeadingUsOct(t *testing.T) { + input := "leading-us-oct = _0o1\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Integer_LeadingUs(t *testing.T) { + input := "leading-us = _123\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Integer_LeadingZero1(t *testing.T) { + input := "leading-zero-1 = 01\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Integer_LeadingZero2(t *testing.T) { + input := "leading-zero-2 = 00\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Integer_LeadingZeroSign1(t *testing.T) { + input := "leading-zero-sign-1 = -01\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Integer_LeadingZeroSign2(t *testing.T) { + input := "leading-zero-sign-2 = +01\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Integer_NegativeBin(t *testing.T) { + input := "negative-bin = -0b11010110\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Integer_NegativeHex(t *testing.T) { + input := "negative-hex = -0xff\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Integer_NegativeOct(t *testing.T) { + input := "negative-oct = -0o99\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Integer_PositiveBin(t *testing.T) { + input := "positive-bin = +0b11010110\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Integer_PositiveHex(t *testing.T) { + input := "positive-hex = +0xff\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Integer_PositiveOct(t *testing.T) { + input := "positive-oct = +0o99\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Integer_TextAfterInteger(t *testing.T) { + input := "answer = 42 the ultimate answer?\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Integer_TrailingUsBin(t *testing.T) { + input := "trailing-us-bin = 0b1_\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Integer_TrailingUsHex(t *testing.T) { + input := "trailing-us-hex = 0x1_\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Integer_TrailingUsOct(t *testing.T) { + input := "trailing-us-oct = 0o1_\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Integer_TrailingUs(t *testing.T) { + input := "trailing-us = 123_\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Integer_UsAfterBin(t *testing.T) { + input := "us-after-bin = 0b_1\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Integer_UsAfterHex(t *testing.T) { + input := "us-after-hex = 0x_1\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Integer_UsAfterOct(t *testing.T) { + input := "us-after-oct = 0o_1\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Key_AfterArray(t *testing.T) { + input := "[[agencies]] owner = \"S Cjelli\"\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Key_AfterTable(t *testing.T) { + input := "[error] this = \"should not be here\"\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Key_AfterValue(t *testing.T) { + input := "first = \"Tom\" last = \"Preston-Werner\" # INVALID\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Key_BareInvalidCharacter(t *testing.T) { + input := "bare!key = 123\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Key_DottedRedefineTable(t *testing.T) { + input := "# Defined a.b as int\na.b = 1\n# Tries to access it as table: error\na.b.c = 2\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Key_DuplicateKeys(t *testing.T) { + input := "dupe = false\ndupe = true\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Key_Duplicate(t *testing.T) { + input := "# DO NOT DO THIS\nname = \"Tom\"\nname = \"Pradyun\"\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Key_Empty(t *testing.T) { + input := " = 1\n" + testgenInvalid(t, input) +} + +func TestTOMLTest_Invalid_Key_Escape(t *testing.T) { + input := "\\u00c0 = \"latin capital letter A with grave\"\n" + testgenInvalid(t, input) +} - input := `no-leads = 1987-7-05T17:45:00Z` +func TestTOMLTest_Invalid_Key_Hash(t *testing.T) { + input := "a# = 1\n" testgenInvalid(t, input) } -func TestInvalidDatetimeMalformedNoSecs(t *testing.T) { +func TestTOMLTest_Invalid_Key_Multiline(t *testing.T) { + input := "\"\"\"long\nkey\"\"\" = 1\n" + testgenInvalid(t, input) +} - input := `no-secs = 1987-07-05T17:45Z` +func TestTOMLTest_Invalid_Key_Newline(t *testing.T) { + input := "barekey\n = 123\n" testgenInvalid(t, input) } -func TestInvalidDatetimeMalformedNoT(t *testing.T) { +func TestTOMLTest_Invalid_Key_NoEol(t *testing.T) { + input := "a = 1 b = 2\n" + testgenInvalid(t, input) +} - input := `no-t = 1987-07-0517:45:00Z` +func TestTOMLTest_Invalid_Key_OpenBracket(t *testing.T) { + input := "[abc = 1\n" testgenInvalid(t, input) } -func TestInvalidDatetimeMalformedWithMilli(t *testing.T) { +func TestTOMLTest_Invalid_Key_PartialQuoted(t *testing.T) { + input := "partial\"quoted\" = 5\n" + testgenInvalid(t, input) +} - input := `with-milli = 1987-07-5T17:45:00.12Z` +func TestTOMLTest_Invalid_Key_SingleOpenBracket(t *testing.T) { + input := "[\n" testgenInvalid(t, input) } -func TestInvalidDuplicateKeyTable(t *testing.T) { +func TestTOMLTest_Invalid_Key_Space(t *testing.T) { + input := "a b = 1\n" + testgenInvalid(t, input) +} - input := `[fruit] -type = "apple" +func TestTOMLTest_Invalid_Key_SpecialCharacter(t *testing.T) { + input := "μ = \"greek small letter mu\"\n" + testgenInvalid(t, input) +} -[fruit.type] -apple = "yes"` +func TestTOMLTest_Invalid_Key_StartBracket(t *testing.T) { + input := "[a]\n[xyz = 5\n[b]\n" testgenInvalid(t, input) } -func TestInvalidDuplicateKeys(t *testing.T) { +func TestTOMLTest_Invalid_Key_TwoEquals(t *testing.T) { + input := "key= = 1\n" + testgenInvalid(t, input) +} - input := `dupe = false -dupe = true` +func TestTOMLTest_Invalid_Key_TwoEquals2(t *testing.T) { + input := "a==1\n" testgenInvalid(t, input) } -func TestInvalidDuplicateTables(t *testing.T) { +func TestTOMLTest_Invalid_Key_TwoEquals3(t *testing.T) { + input := "a=b=1\n" + testgenInvalid(t, input) +} - input := `[a] -[a]` +func TestTOMLTest_Invalid_Key_WithoutValue1(t *testing.T) { + input := "key\n" testgenInvalid(t, input) } -func TestInvalidEmptyImplicitTable(t *testing.T) { +func TestTOMLTest_Invalid_Key_WithoutValue2(t *testing.T) { + input := "key = \n" + testgenInvalid(t, input) +} - input := `[naughty..naughty]` +func TestTOMLTest_Invalid_String_BadByteEscape(t *testing.T) { + input := "naughty = \"\\xAg\"\n" testgenInvalid(t, input) } -func TestInvalidEmptyTable(t *testing.T) { +func TestTOMLTest_Invalid_String_BadCodepoint(t *testing.T) { + input := "invalid-codepoint = \"This string contains a non scalar unicode codepoint \\uD801\"\n" + testgenInvalid(t, input) +} - input := `[]` +func TestTOMLTest_Invalid_String_BadConcat(t *testing.T) { + input := "no_concat = \"first\" \"second\"\n" testgenInvalid(t, input) } -func TestInvalidFloatNoLeadingZero(t *testing.T) { +func TestTOMLTest_Invalid_String_BadEscape(t *testing.T) { + input := "invalid-escape = \"This string has a bad \\a escape character.\"\n" + testgenInvalid(t, input) +} - input := `answer = .12345 -neganswer = -.12345` +func TestTOMLTest_Invalid_String_BadMultiline(t *testing.T) { + input := "multi = \"first line\nsecond line\"\n" testgenInvalid(t, input) } -func TestInvalidFloatNoTrailingDigits(t *testing.T) { +func TestTOMLTest_Invalid_String_BadSlashEscape(t *testing.T) { + input := "invalid-escape = \"This string has a bad \\/ escape character.\"\n" + testgenInvalid(t, input) +} - input := `answer = 1. -neganswer = -1.` +func TestTOMLTest_Invalid_String_BadUniEsc(t *testing.T) { + input := "str = \"val\\ue\"\n" testgenInvalid(t, input) } -func TestInvalidKeyEmpty(t *testing.T) { +func TestTOMLTest_Invalid_String_BasicByteEscapes(t *testing.T) { + input := "answer = \"\\x33\"\n" + testgenInvalid(t, input) +} - input := ` = 1` +func TestTOMLTest_Invalid_String_BasicMultilineOutOfRangeUnicodeEscape1(t *testing.T) { + input := "a = \"\"\"\\UFFFFFFFF\"\"\"\n" testgenInvalid(t, input) } -func TestInvalidKeyHash(t *testing.T) { +func TestTOMLTest_Invalid_String_BasicMultilineOutOfRangeUnicodeEscape2(t *testing.T) { + input := "a = \"\"\"\\U00D80000\"\"\"\n" + testgenInvalid(t, input) +} - input := `a# = 1` +func TestTOMLTest_Invalid_String_BasicMultilineQuotes(t *testing.T) { + input := "str5 = \"\"\"Here are three quotation marks: \"\"\".\"\"\"\n" testgenInvalid(t, input) } -func TestInvalidKeyNewline(t *testing.T) { +func TestTOMLTest_Invalid_String_BasicMultilineUnknownEscape(t *testing.T) { + input := "a = \"\"\"\\@\"\"\"\n" + testgenInvalid(t, input) +} - input := `a -= 1` +func TestTOMLTest_Invalid_String_BasicOutOfRangeUnicodeEscape1(t *testing.T) { + input := "a = \"\\UFFFFFFFF\"\n" testgenInvalid(t, input) } -func TestInvalidKeyOpenBracket(t *testing.T) { +func TestTOMLTest_Invalid_String_BasicOutOfRangeUnicodeEscape2(t *testing.T) { + input := "a = \"\\U00D80000\"\n" + testgenInvalid(t, input) +} - input := `[abc = 1` +func TestTOMLTest_Invalid_String_BasicUnknownEscape(t *testing.T) { + input := "a = \"\\@\"\n" testgenInvalid(t, input) } -func TestInvalidKeySingleOpenBracket(t *testing.T) { +func TestTOMLTest_Invalid_String_LiteralMultilineQuotes1(t *testing.T) { + input := "a = '''6 apostrophes: ''''''\n\n" + testgenInvalid(t, input) +} - input := `[` +func TestTOMLTest_Invalid_String_LiteralMultilineQuotes2(t *testing.T) { + input := "a = '''15 apostrophes: ''''''''''''''''''\n" testgenInvalid(t, input) } -func TestInvalidKeySpace(t *testing.T) { +func TestTOMLTest_Invalid_String_MissingQuotes(t *testing.T) { + input := "name = value\n" + testgenInvalid(t, input) +} - input := `a b = 1` +func TestTOMLTest_Invalid_String_MultilineEscapeSpace(t *testing.T) { + input := "a = \"\"\"\n foo \\ \\n\n bar\"\"\"\n" testgenInvalid(t, input) } -func TestInvalidKeyStartBracket(t *testing.T) { +func TestTOMLTest_Invalid_String_MultilineNoClose2(t *testing.T) { + input := "x=\"\"\"\n" + testgenInvalid(t, input) +} - input := `[a] -[xyz = 5 -[b]` +func TestTOMLTest_Invalid_String_MultilineNoClose(t *testing.T) { + input := "invalid = \"\"\"\n this will fail\n" testgenInvalid(t, input) } -func TestInvalidKeyTwoEquals(t *testing.T) { +func TestTOMLTest_Invalid_String_MultilineQuotes1(t *testing.T) { + input := "a = \"\"\"6 quotes: \"\"\"\"\"\"\n" + testgenInvalid(t, input) +} - input := `key= = 1` +func TestTOMLTest_Invalid_String_MultilineQuotes2(t *testing.T) { + input := "a = \"\"\"6 quotes: \"\"\"\"\"\"\n" testgenInvalid(t, input) } -func TestInvalidStringBadByteEscape(t *testing.T) { +func TestTOMLTest_Invalid_String_NoClose(t *testing.T) { + input := "no-ending-quote = \"One time, at band camp\n" + testgenInvalid(t, input) +} - input := `naughty = "\xAg"` +func TestTOMLTest_Invalid_String_TextAfterString(t *testing.T) { + input := "string = \"Is there life after strings?\" No.\n" testgenInvalid(t, input) } -func TestInvalidStringBadEscape(t *testing.T) { +func TestTOMLTest_Invalid_String_WrongClose(t *testing.T) { + input := "bad-ending-quote = \"double and single'\n" + testgenInvalid(t, input) +} - input := `invalid-escape = "This string has a bad \a escape character."` +func TestTOMLTest_Invalid_Table_ArrayEmpty(t *testing.T) { + input := "[[]]\nname = \"Born to Run\"\n" testgenInvalid(t, input) } -func TestInvalidStringByteEscapes(t *testing.T) { +func TestTOMLTest_Invalid_Table_ArrayImplicit(t *testing.T) { + input := "# This test is a bit tricky. It should fail because the first use of\n# `[[albums.songs]]` without first declaring `albums` implies that `albums`\n# must be a table. The alternative would be quite weird. Namely, it wouldn't\n# comply with the TOML spec: \"Each double-bracketed sub-table will belong to \n# the most *recently* defined table element *above* it.\"\n#\n# This is in contrast to the *valid* test, table-array-implicit where\n# `[[albums.songs]]` works by itself, so long as `[[albums]]` isn't declared\n# later. (Although, `[albums]` could be.)\n[[albums.songs]]\nname = \"Glory Days\"\n\n[[albums]]\nname = \"Born in the USA\"\n" + testgenInvalid(t, input) +} - input := `answer = "\x33"` +func TestTOMLTest_Invalid_Table_ArrayMissingBracket(t *testing.T) { + input := "[[albums]\nname = \"Born to Run\"\n" testgenInvalid(t, input) } -func TestInvalidStringNoClose(t *testing.T) { +func TestTOMLTest_Invalid_Table_DuplicateKeyTable(t *testing.T) { + input := "[fruit]\ntype = \"apple\"\n\n[fruit.type]\napple = \"yes\"\n" + testgenInvalid(t, input) +} - input := `no-ending-quote = "One time, at band camp` +func TestTOMLTest_Invalid_Table_DuplicateTableArray(t *testing.T) { + input := "[tbl]\n[[tbl]]\n" testgenInvalid(t, input) } -func TestInvalidTableArrayImplicit(t *testing.T) { +func TestTOMLTest_Invalid_Table_DuplicateTableArray2(t *testing.T) { + input := "[[tbl]]\n[tbl]\n" + testgenInvalid(t, input) +} - input := "# This test is a bit tricky. It should fail because the first use of\n" + - "# `[[albums.songs]]` without first declaring `albums` implies that `albums`\n" + - "# must be a table. The alternative would be quite weird. Namely, it wouldn't\n" + - "# comply with the TOML spec: \"Each double-bracketed sub-table will belong to \n" + - "# the most *recently* defined table element *above* it.\"\n" + - "#\n" + - "# This is in contrast to the *valid* test, table-array-implicit where\n" + - "# `[[albums.songs]]` works by itself, so long as `[[albums]]` isn't declared\n" + - "# later. (Although, `[albums]` could be.)\n" + - "[[albums.songs]]\n" + - "name = \"Glory Days\"\n" + - "\n" + - "[[albums]]\n" + - "name = \"Born in the USA\"\n" +func TestTOMLTest_Invalid_Table_Duplicate(t *testing.T) { + input := "[a]\nb = 1\n\n[a]\nc = 2\n" testgenInvalid(t, input) } -func TestInvalidTableArrayMalformedBracket(t *testing.T) { +func TestTOMLTest_Invalid_Table_EmptyImplicitTable(t *testing.T) { + input := "[naughty..naughty]\n" + testgenInvalid(t, input) +} - input := `[[albums] -name = "Born to Run"` +func TestTOMLTest_Invalid_Table_Empty(t *testing.T) { + input := "[]\n" testgenInvalid(t, input) } -func TestInvalidTableArrayMalformedEmpty(t *testing.T) { +func TestTOMLTest_Invalid_Table_EqualsSign(t *testing.T) { + input := "[name=bad]\n" + testgenInvalid(t, input) +} - input := `[[]] -name = "Born to Run"` +func TestTOMLTest_Invalid_Table_Injection1(t *testing.T) { + input := "[a.b.c]\n z = 9\n[a]\n b.c.t = \"Using dotted keys to add to [a.b.c] after explicitly defining it above is not allowed\"\n \n# see https://github.com/toml-lang/toml/issues/846\n" testgenInvalid(t, input) } -func TestInvalidTableEmpty(t *testing.T) { +func TestTOMLTest_Invalid_Table_Injection2(t *testing.T) { + input := "[a.b.c.d]\n z = 9\n[a]\n b.c.d.k.t = \"Using dotted keys to add to [a.b.c.d] after explicitly defining it above is not allowed\"\n \n# see https://github.com/toml-lang/toml/issues/846\n" + testgenInvalid(t, input) +} - input := `[]` +func TestTOMLTest_Invalid_Table_Llbrace(t *testing.T) { + input := "[ [table]]\n" testgenInvalid(t, input) } -func TestInvalidTableNestedBracketsClose(t *testing.T) { +func TestTOMLTest_Invalid_Table_NestedBracketsClose(t *testing.T) { + input := "[a]b]\nzyx = 42\n" + testgenInvalid(t, input) +} - input := `[a]b] -zyx = 42` +func TestTOMLTest_Invalid_Table_NestedBracketsOpen(t *testing.T) { + input := "[a[b]\nzyx = 42\n" testgenInvalid(t, input) } -func TestInvalidTableNestedBracketsOpen(t *testing.T) { +func TestTOMLTest_Invalid_Table_QuotedNoClose(t *testing.T) { + input := "[\"where will it end]\nname = value\n" + testgenInvalid(t, input) +} - input := `[a[b] -zyx = 42` +func TestTOMLTest_Invalid_Table_Redefine(t *testing.T) { + input := "# Define b as int, and try to use it as a table: error\n[a]\nb = 1\n\n[a.b]\nc = 2\n" testgenInvalid(t, input) } -func TestInvalidTableWhitespace(t *testing.T) { +func TestTOMLTest_Invalid_Table_Rrbrace(t *testing.T) { + input := "[[table] ]\n" + testgenInvalid(t, input) +} - input := `[invalid key]` +func TestTOMLTest_Invalid_Table_TextAfterTable(t *testing.T) { + input := "[error] this shouldn't be here\n" testgenInvalid(t, input) } -func TestInvalidTableWithPound(t *testing.T) { +func TestTOMLTest_Invalid_Table_Whitespace(t *testing.T) { + input := "[invalid key]\n" + testgenInvalid(t, input) +} - input := `[key#group] -answer = 42` +func TestTOMLTest_Invalid_Table_WithPound(t *testing.T) { + input := "[key#group]\nanswer = 42\n" testgenInvalid(t, input) } -func TestInvalidTextAfterArrayEntries(t *testing.T) { +func TestTOMLTest_Valid_Array_Array(t *testing.T) { + input := "ints = [1, 2, 3, ]\nfloats = [1.1, 2.1, 3.1]\nstrings = [\"a\", \"b\", \"c\"]\ndates = [\n 1987-07-05T17:45:00Z,\n 1979-05-27T07:32:00Z,\n 2006-06-01T11:00:00Z,\n]\ncomments = [\n 1,\n 2, #this is ok\n]\n" + jsonRef := "{\n \"comments\": [\n {\n \"type\": \"integer\",\n \"value\": \"1\"\n },\n {\n \"type\": \"integer\",\n \"value\": \"2\"\n }\n ],\n \"dates\": [\n {\n \"type\": \"datetime\",\n \"value\": \"1987-07-05T17:45:00Z\"\n },\n {\n \"type\": \"datetime\",\n \"value\": \"1979-05-27T07:32:00Z\"\n },\n {\n \"type\": \"datetime\",\n \"value\": \"2006-06-01T11:00:00Z\"\n }\n ],\n \"floats\": [\n {\n \"type\": \"float\",\n \"value\": \"1.1\"\n },\n {\n \"type\": \"float\",\n \"value\": \"2.1\"\n },\n {\n \"type\": \"float\",\n \"value\": \"3.1\"\n }\n ],\n \"ints\": [\n {\n \"type\": \"integer\",\n \"value\": \"1\"\n },\n {\n \"type\": \"integer\",\n \"value\": \"2\"\n },\n {\n \"type\": \"integer\",\n \"value\": \"3\"\n }\n ],\n \"strings\": [\n {\n \"type\": \"string\",\n \"value\": \"a\"\n },\n {\n \"type\": \"string\",\n \"value\": \"b\"\n },\n {\n \"type\": \"string\",\n \"value\": \"c\"\n }\n ]\n}\n" + testgenValid(t, input, jsonRef) +} - input := `array = [ - "Is there life after an array separator?", No - "Entry" -]` - testgenInvalid(t, input) +func TestTOMLTest_Valid_Array_Bool(t *testing.T) { + input := "a = [true, false]\n" + jsonRef := "{\n \"a\": [\n {\n \"type\": \"bool\",\n \"value\": \"true\"\n },\n {\n \"type\": \"bool\",\n \"value\": \"false\"\n }\n ]\n}\n" + testgenValid(t, input, jsonRef) } -func TestInvalidTextAfterInteger(t *testing.T) { +func TestTOMLTest_Valid_Array_Empty(t *testing.T) { + input := "thevoid = [[[[[]]]]]\n" + jsonRef := "{\n \"thevoid\": [\n [\n [\n [\n []\n ]\n ]\n ]\n ]\n}\n" + testgenValid(t, input, jsonRef) +} - input := `answer = 42 the ultimate answer?` - testgenInvalid(t, input) +func TestTOMLTest_Valid_Array_Hetergeneous(t *testing.T) { + input := "mixed = [[1, 2], [\"a\", \"b\"], [1.1, 2.1]]\n" + jsonRef := "{\n \"mixed\": [\n [\n {\n \"type\": \"integer\",\n \"value\": \"1\"\n },\n {\n \"type\": \"integer\",\n \"value\": \"2\"\n }\n ],\n [\n {\n \"type\": \"string\",\n \"value\": \"a\"\n },\n {\n \"type\": \"string\",\n \"value\": \"b\"\n }\n ],\n [\n {\n \"type\": \"float\",\n \"value\": \"1.1\"\n },\n {\n \"type\": \"float\",\n \"value\": \"2.1\"\n }\n ]\n ]\n}\n" + testgenValid(t, input, jsonRef) } -func TestInvalidTextAfterString(t *testing.T) { +func TestTOMLTest_Valid_Array_MixedIntArray(t *testing.T) { + input := "arrays-and-ints = [1, [\"Arrays are not integers.\"]]\n" + jsonRef := "{\n \"arrays-and-ints\": [\n {\n \"type\": \"integer\",\n \"value\": \"1\"\n },\n [\n {\n \"type\": \"string\",\n \"value\": \"Arrays are not integers.\"\n }\n ]\n ]\n}\n" + testgenValid(t, input, jsonRef) +} - input := `string = "Is there life after strings?" No.` - testgenInvalid(t, input) +func TestTOMLTest_Valid_Array_MixedIntFloat(t *testing.T) { + input := "ints-and-floats = [1, 1.1]\n" + jsonRef := "{\n \"ints-and-floats\": [\n {\n \"type\": \"integer\",\n \"value\": \"1\"\n },\n {\n \"type\": \"float\",\n \"value\": \"1.1\"\n }\n ]\n}\n" + testgenValid(t, input, jsonRef) } -func TestInvalidTextAfterTable(t *testing.T) { +func TestTOMLTest_Valid_Array_MixedIntString(t *testing.T) { + input := "strings-and-ints = [\"hi\", 42]\n" + jsonRef := "{\n \"strings-and-ints\": [\n {\n \"type\": \"string\",\n \"value\": \"hi\"\n },\n {\n \"type\": \"integer\",\n \"value\": \"42\"\n }\n ]\n}\n" + testgenValid(t, input, jsonRef) +} - input := `[error] this shouldn't be here` - testgenInvalid(t, input) +func TestTOMLTest_Valid_Array_MixedStringTable(t *testing.T) { + input := "contributors = [\n \"Foo Bar \",\n { name = \"Baz Qux\", email = \"bazqux@example.com\", url = \"https://example.com/bazqux\" }\n]\n" + jsonRef := "{\n \"contributors\": [\n {\n \"type\": \"string\",\n \"value\": \"Foo Bar \\u003cfoo@example.com\\u003e\"\n },\n {\n \"email\": {\n \"type\": \"string\",\n \"value\": \"bazqux@example.com\"\n },\n \"name\": {\n \"type\": \"string\",\n \"value\": \"Baz Qux\"\n },\n \"url\": {\n \"type\": \"string\",\n \"value\": \"https://example.com/bazqux\"\n }\n }\n ]\n}\n" + testgenValid(t, input, jsonRef) } -func TestInvalidTextBeforeArraySeparator(t *testing.T) { +func TestTOMLTest_Valid_Array_NestedDouble(t *testing.T) { + input := "nest = [\n\t[\n\t\t[\"a\"],\n\t\t[1, 2, [3]]\n\t]\n]\n" + jsonRef := "{\n \"nest\": [\n [\n [\n {\n \"type\": \"string\",\n \"value\": \"a\"\n }\n ],\n [\n {\n \"type\": \"integer\",\n \"value\": \"1\"\n },\n {\n \"type\": \"integer\",\n \"value\": \"2\"\n },\n [\n {\n \"type\": \"integer\",\n \"value\": \"3\"\n }\n ]\n ]\n ]\n ]\n}\n" + testgenValid(t, input, jsonRef) +} - input := `array = [ - "Is there life before an array separator?" No, - "Entry" -]` - testgenInvalid(t, input) +func TestTOMLTest_Valid_Array_NestedInlineTable(t *testing.T) { + input := "a = [ { b = {} } ]\n" + jsonRef := "{\n \"a\": [\n {\n \"b\": {}\n }\n ]\n}\n" + testgenValid(t, input, jsonRef) } -func TestInvalidTextInArray(t *testing.T) { +func TestTOMLTest_Valid_Array_Nested(t *testing.T) { + input := "nest = [[\"a\"], [\"b\"]]\n" + jsonRef := "{\n \"nest\": [\n [\n {\n \"type\": \"string\",\n \"value\": \"a\"\n }\n ],\n [\n {\n \"type\": \"string\",\n \"value\": \"b\"\n }\n ]\n ]\n}\n" + testgenValid(t, input, jsonRef) +} - input := `array = [ - "Entry 1", - I don't belong, - "Entry 2", -]` - testgenInvalid(t, input) +func TestTOMLTest_Valid_Array_Nospaces(t *testing.T) { + input := "ints = [1,2,3]\n" + jsonRef := "{\n \"ints\": [\n {\n \"type\": \"integer\",\n \"value\": \"1\"\n },\n {\n \"type\": \"integer\",\n \"value\": \"2\"\n },\n {\n \"type\": \"integer\",\n \"value\": \"3\"\n }\n ]\n}\n" + testgenValid(t, input, jsonRef) +} + +func TestTOMLTest_Valid_Array_StringQuoteComma2(t *testing.T) { + input := "title = [ \" \\\", \",]\n" + jsonRef := "{\n \"title\": [\n {\n \"type\": \"string\",\n \"value\": \" \\\", \"\n }\n ]\n}\n" + testgenValid(t, input, jsonRef) +} + +func TestTOMLTest_Valid_Array_StringQuoteComma(t *testing.T) { + input := "title = [\n\"Client: \\\"XXXX\\\", Job: XXXX\",\n\"Code: XXXX\"\n]\n" + jsonRef := "{\n \"title\": [\n {\n \"type\": \"string\",\n \"value\": \"Client: \\\"XXXX\\\", Job: XXXX\"\n },\n {\n \"type\": \"string\",\n \"value\": \"Code: XXXX\"\n }\n ]\n}\n" + testgenValid(t, input, jsonRef) +} + +func TestTOMLTest_Valid_Array_StringWithComma(t *testing.T) { + input := "title = [\n\"Client: XXXX, Job: XXXX\",\n\"Code: XXXX\"\n]\n" + jsonRef := "{\n \"title\": [\n {\n \"type\": \"string\",\n \"value\": \"Client: XXXX, Job: XXXX\"\n },\n {\n \"type\": \"string\",\n \"value\": \"Code: XXXX\"\n }\n ]\n}\n" + testgenValid(t, input, jsonRef) +} + +func TestTOMLTest_Valid_Array_Strings(t *testing.T) { + input := "string_array = [ \"all\", 'strings', \"\"\"are the same\"\"\", '''type''']\n" + jsonRef := "{\n \"string_array\": [\n {\n \"type\": \"string\",\n \"value\": \"all\"\n },\n {\n \"type\": \"string\",\n \"value\": \"strings\"\n },\n {\n \"type\": \"string\",\n \"value\": \"are the same\"\n },\n {\n \"type\": \"string\",\n \"value\": \"type\"\n }\n ]\n}\n" + testgenValid(t, input, jsonRef) +} + +func TestTOMLTest_Valid_Array_TableArrayStringBackslash(t *testing.T) { + input := "foo = [ { bar=\"\\\"{{baz}}\\\"\"} ]\n" + jsonRef := "{\n \"foo\": [\n {\n \"bar\": {\n \"type\": \"string\",\n \"value\": \"\\\"{{baz}}\\\"\"\n }\n }\n ]\n}\n" + testgenValid(t, input, jsonRef) +} + +func TestTOMLTest_Valid_Bool_Bool(t *testing.T) { + input := "t = true\nf = false\n" + jsonRef := "{\n \"f\": {\n \"type\": \"bool\",\n \"value\": \"false\"\n },\n \"t\": {\n \"type\": \"bool\",\n \"value\": \"true\"\n }\n}\n" + testgenValid(t, input, jsonRef) +} + +func TestTOMLTest_Valid_Comment_AtEof(t *testing.T) { + input := "# This is a full-line comment\nkey = \"value\" # This is a comment at the end of a line\n" + jsonRef := "{\n \"key\": {\n \"type\": \"string\",\n \"value\": \"value\"\n }\n}\n" + testgenValid(t, input, jsonRef) +} + +func TestTOMLTest_Valid_Comment_AtEof2(t *testing.T) { + input := "# This is a full-line comment\nkey = \"value\" # This is a comment at the end of a line\n" + jsonRef := "{\n \"key\": {\n \"type\": \"string\",\n \"value\": \"value\"\n }\n}\n" + testgenValid(t, input, jsonRef) +} + +func TestTOMLTest_Valid_Comment_Everywhere(t *testing.T) { + input := "# Top comment.\n # Top comment.\n# Top comment.\n\n# [no-extraneous-groups-please]\n\n[group] # Comment\nanswer = 42 # Comment\n# no-extraneous-keys-please = 999\n# Inbetween comment.\nmore = [ # Comment\n # What about multiple # comments?\n # Can you handle it?\n #\n # Evil.\n# Evil.\n 42, 42, # Comments within arrays are fun.\n # What about multiple # comments?\n # Can you handle it?\n #\n # Evil.\n# Evil.\n# ] Did I fool you?\n] # Hopefully not.\n\n# Make sure the space between the datetime and \"#\" isn't lexed.\nd = 1979-05-27T07:32:12-07:00 # c\n" + jsonRef := "{\n \"group\": {\n \"answer\": {\n \"type\": \"integer\",\n \"value\": \"42\"\n },\n \"d\": {\n \"type\": \"datetime\",\n \"value\": \"1979-05-27T07:32:12-07:00\"\n },\n \"more\": [\n {\n \"type\": \"integer\",\n \"value\": \"42\"\n },\n {\n \"type\": \"integer\",\n \"value\": \"42\"\n }\n ]\n }\n}\n" + testgenValid(t, input, jsonRef) +} + +func TestTOMLTest_Valid_Comment_Tricky(t *testing.T) { + input := "[section]#attached comment\n#[notsection]\none = \"11\"#cmt\ntwo = \"22#\"\nthree = '#'\n\nfour = \"\"\"# no comment\n# nor this\n#also not comment\"\"\"#is_comment\n\nfive = 5.5#66\nsix = 6#7\n8 = \"eight\"\n#nine = 99\nten = 10e2#1\neleven = 1.11e1#23\n\n[\"hash#tag\"]\n\"#!\" = \"hash bang\"\narr3 = [ \"#\", '#', \"\"\"###\"\"\" ]\narr4 = [ 1,# 9, 9,\n2#,9\n,#9\n3#]\n,4]\narr5 = [[[[#[\"#\"],\n[\"#\"]]]]#]\n]\ntbl1 = { \"#\" = '}#'}#}}\n\n\n" + jsonRef := "{\n \"hash#tag\": {\n \"#!\": {\n \"type\": \"string\",\n \"value\": \"hash bang\"\n },\n \"arr3\": [\n {\n \"type\": \"string\",\n \"value\": \"#\"\n },\n {\n \"type\": \"string\",\n \"value\": \"#\"\n },\n {\n \"type\": \"string\",\n \"value\": \"###\"\n }\n ],\n \"arr4\": [\n {\n \"type\": \"integer\",\n \"value\": \"1\"\n },\n {\n \"type\": \"integer\",\n \"value\": \"2\"\n },\n {\n \"type\": \"integer\",\n \"value\": \"3\"\n },\n {\n \"type\": \"integer\",\n \"value\": \"4\"\n }\n ],\n \"arr5\": [\n [\n [\n [\n [\n {\n \"type\": \"string\",\n \"value\": \"#\"\n }\n ]\n ]\n ]\n ]\n ],\n \"tbl1\": {\n \"#\": {\n \"type\": \"string\",\n \"value\": \"}#\"\n }\n }\n },\n \"section\": {\n \"8\": {\n \"type\": \"string\",\n \"value\": \"eight\"\n },\n \"eleven\": {\n \"type\": \"float\",\n \"value\": \"11.1\"\n },\n \"five\": {\n \"type\": \"float\",\n \"value\": \"5.5\"\n },\n \"four\": {\n \"type\": \"string\",\n \"value\": \"# no comment\\n# nor this\\n#also not comment\"\n },\n \"one\": {\n \"type\": \"string\",\n \"value\": \"11\"\n },\n \"six\": {\n \"type\": \"integer\",\n \"value\": \"6\"\n },\n \"ten\": {\n \"type\": \"float\",\n \"value\": \"1000.0\"\n },\n \"three\": {\n \"type\": \"string\",\n \"value\": \"#\"\n },\n \"two\": {\n \"type\": \"string\",\n \"value\": \"22#\"\n }\n }\n}\n" + testgenValid(t, input, jsonRef) +} + +func TestTOMLTest_Valid_Datetime_Datetime(t *testing.T) { + input := "space = 1987-07-05 17:45:00Z\nlower = 1987-07-05t17:45:00z\n" + jsonRef := "{\n \"lower\": {\n \"type\": \"datetime\",\n \"value\": \"1987-07-05T17:45:00Z\"\n },\n \"space\": {\n \"type\": \"datetime\",\n \"value\": \"1987-07-05T17:45:00Z\"\n }\n}\n" + testgenValid(t, input, jsonRef) +} + +func TestTOMLTest_Valid_Datetime_LocalDate(t *testing.T) { + input := "bestdayever = 1987-07-05\n" + jsonRef := "{\n \"bestdayever\": {\n \"type\": \"date-local\",\n \"value\": \"1987-07-05\"\n }\n}\n" + testgenValid(t, input, jsonRef) +} + +func TestTOMLTest_Valid_Datetime_LocalTime(t *testing.T) { + input := "besttimeever = 17:45:00\nmilliseconds = 10:32:00.555\n" + jsonRef := "{\n \"besttimeever\": {\n \"type\": \"time-local\",\n \"value\": \"17:45:00\"\n },\n \"milliseconds\": {\n \"type\": \"time-local\",\n \"value\": \"10:32:00.555\"\n }\n}\n" + testgenValid(t, input, jsonRef) +} + +func TestTOMLTest_Valid_Datetime_Local(t *testing.T) { + input := "local = 1987-07-05T17:45:00\nmilli = 1977-12-21T10:32:00.555\nspace = 1987-07-05 17:45:00\n" + jsonRef := "{\n \"local\": {\n \"type\": \"datetime-local\",\n \"value\": \"1987-07-05T17:45:00\"\n },\n \"milli\": {\n \"type\": \"datetime-local\",\n \"value\": \"1977-12-21T10:32:00.555\"\n },\n \"space\": {\n \"type\": \"datetime-local\",\n \"value\": \"1987-07-05T17:45:00\"\n }\n}\n" + testgenValid(t, input, jsonRef) +} + +func TestTOMLTest_Valid_Datetime_Milliseconds(t *testing.T) { + input := "utc1 = 1987-07-05T17:45:56.123456Z\nutc2 = 1987-07-05T17:45:56.6Z\nwita1 = 1987-07-05T17:45:56.123456+08:00\nwita2 = 1987-07-05T17:45:56.6+08:00\n" + jsonRef := "{\n \"utc1\": {\n \"type\": \"datetime\",\n \"value\": \"1987-07-05T17:45:56.123456Z\"\n },\n \"utc2\": {\n \"type\": \"datetime\",\n \"value\": \"1987-07-05T17:45:56.600000Z\"\n },\n \"wita1\": {\n \"type\": \"datetime\",\n \"value\": \"1987-07-05T17:45:56.123456+08:00\"\n },\n \"wita2\": {\n \"type\": \"datetime\",\n \"value\": \"1987-07-05T17:45:56.600000+08:00\"\n }\n}\n" + testgenValid(t, input, jsonRef) +} + +func TestTOMLTest_Valid_Datetime_Timezone(t *testing.T) { + input := "utc = 1987-07-05T17:45:56Z\npdt = 1987-07-05T17:45:56-05:00\nnzst = 1987-07-05T17:45:56+12:00\nnzdt = 1987-07-05T17:45:56+13:00 # DST\n" + jsonRef := "{\n \"nzdt\": {\n \"type\": \"datetime\",\n \"value\": \"1987-07-05T17:45:56+13:00\"\n },\n \"nzst\": {\n \"type\": \"datetime\",\n \"value\": \"1987-07-05T17:45:56+12:00\"\n },\n \"pdt\": {\n \"type\": \"datetime\",\n \"value\": \"1987-07-05T17:45:56-05:00\"\n },\n \"utc\": {\n \"type\": \"datetime\",\n \"value\": \"1987-07-05T17:45:56Z\"\n }\n}\n" + testgenValid(t, input, jsonRef) +} + +func TestTOMLTest_Valid_EmptyFile(t *testing.T) { + input := "" + jsonRef := "{}\n" + testgenValid(t, input, jsonRef) +} + +func TestTOMLTest_Valid_Example(t *testing.T) { + input := "best-day-ever = 1987-07-05T17:45:00Z\n\n[numtheory]\nboring = false\nperfection = [6, 28, 496]\n" + jsonRef := "{\n \"best-day-ever\": {\n \"type\": \"datetime\",\n \"value\": \"1987-07-05T17:45:00Z\"\n },\n \"numtheory\": {\n \"boring\": {\n \"type\": \"bool\",\n \"value\": \"false\"\n },\n \"perfection\": [\n {\n \"type\": \"integer\",\n \"value\": \"6\"\n },\n {\n \"type\": \"integer\",\n \"value\": \"28\"\n },\n {\n \"type\": \"integer\",\n \"value\": \"496\"\n }\n ]\n }\n}\n" + testgenValid(t, input, jsonRef) +} + +func TestTOMLTest_Valid_Float_Exponent(t *testing.T) { + input := "lower = 3e2\nupper = 3E2\nneg = 3e-2\npos = 3E+2\nzero = 3e0\npointlower = 3.1e2\npointupper = 3.1E2\nminustenth = -1E-1\n" + jsonRef := "{\n \"lower\": {\n \"type\": \"float\",\n \"value\": \"300.0\"\n },\n \"minustenth\": {\n \"type\": \"float\",\n \"value\": \"-0.1\"\n },\n \"neg\": {\n \"type\": \"float\",\n \"value\": \"0.03\"\n },\n \"pointlower\": {\n \"type\": \"float\",\n \"value\": \"310.0\"\n },\n \"pointupper\": {\n \"type\": \"float\",\n \"value\": \"310.0\"\n },\n \"pos\": {\n \"type\": \"float\",\n \"value\": \"300.0\"\n },\n \"upper\": {\n \"type\": \"float\",\n \"value\": \"300.0\"\n },\n \"zero\": {\n \"type\": \"float\",\n \"value\": \"3.0\"\n }\n}\n" + testgenValid(t, input, jsonRef) +} + +func TestTOMLTest_Valid_Float_Float(t *testing.T) { + input := "pi = 3.14\npospi = +3.14\nnegpi = -3.14\nzero-intpart = 0.123\n" + jsonRef := "{\n \"negpi\": {\n \"type\": \"float\",\n \"value\": \"-3.14\"\n },\n \"pi\": {\n \"type\": \"float\",\n \"value\": \"3.14\"\n },\n \"pospi\": {\n \"type\": \"float\",\n \"value\": \"3.14\"\n },\n \"zero-intpart\": {\n \"type\": \"float\",\n \"value\": \"0.123\"\n }\n}\n" + testgenValid(t, input, jsonRef) +} + +func TestTOMLTest_Valid_Float_InfAndNan(t *testing.T) { + input := "# We don't encode +nan and -nan back with the signs; many languages don't\n# support a sign on NaN (it doesn't really make much sense).\nnan = nan\nnan_neg = -nan\nnan_plus = +nan\ninfinity = inf\ninfinity_neg = -inf\ninfinity_plus = +inf\n" + jsonRef := "{\n \"infinity\": {\n \"type\": \"float\",\n \"value\": \"inf\"\n },\n \"infinity_neg\": {\n \"type\": \"float\",\n \"value\": \"-inf\"\n },\n \"infinity_plus\": {\n \"type\": \"float\",\n \"value\": \"+inf\"\n },\n \"nan\": {\n \"type\": \"float\",\n \"value\": \"nan\"\n },\n \"nan_neg\": {\n \"type\": \"float\",\n \"value\": \"nan\"\n },\n \"nan_plus\": {\n \"type\": \"float\",\n \"value\": \"nan\"\n }\n}\n" + testgenValid(t, input, jsonRef) +} + +func TestTOMLTest_Valid_Float_Long(t *testing.T) { + input := "longpi = 3.141592653589793\nneglongpi = -3.141592653589793\n" + jsonRef := "{\n \"longpi\": {\n \"type\": \"float\",\n \"value\": \"3.141592653589793\"\n },\n \"neglongpi\": {\n \"type\": \"float\",\n \"value\": \"-3.141592653589793\"\n }\n}\n" + testgenValid(t, input, jsonRef) +} + +func TestTOMLTest_Valid_Float_Underscore(t *testing.T) { + input := "before = 3_141.5927\nafter = 3141.592_7\nexponent = 3e1_4\n" + jsonRef := "{\n \"after\": {\n \"type\": \"float\",\n \"value\": \"3141.5927\"\n },\n \"before\": {\n \"type\": \"float\",\n \"value\": \"3141.5927\"\n },\n \"exponent\": {\n \"type\": \"float\",\n \"value\": \"3.0e14\"\n }\n}\n" + testgenValid(t, input, jsonRef) +} + +func TestTOMLTest_Valid_Float_Zero(t *testing.T) { + input := "zero = 0.0\nsigned-pos = +0.0\nsigned-neg = -0.0\nexponent = 0e0\nexponent-two-0 = 0e00\nexponent-signed-pos = +0e0\nexponent-signed-neg = -0e0\n" + jsonRef := "{\n \"zero\": {\n \"type\": \"float\",\n \"value\": \"0\"\n },\n \"signed-pos\": {\n \"type\": \"float\",\n \"value\": \"0\"\n },\n \"signed-neg\": {\n \"type\": \"float\",\n \"value\": \"0\"\n },\n \"exponent\": {\n \"type\": \"float\",\n \"value\": \"0\"\n },\n \"exponent-two-0\": {\n \"type\": \"float\",\n \"value\": \"0\"\n },\n \"exponent-signed-pos\": {\n \"type\": \"float\",\n \"value\": \"0\"\n },\n \"exponent-signed-neg\": {\n \"type\": \"float\",\n \"value\": \"0\"\n }\n}\n" + testgenValid(t, input, jsonRef) } -func TestValidArrayEmpty(t *testing.T) { +func TestTOMLTest_Valid_ImplicitAndExplicitAfter(t *testing.T) { + input := "[a.b.c]\nanswer = 42\n\n[a]\nbetter = 43\n" + jsonRef := "{\n \"a\": {\n \"b\": {\n \"c\": {\n \"answer\": {\n \"type\": \"integer\",\n \"value\": \"42\"\n }\n }\n },\n \"better\": {\n \"type\": \"integer\",\n \"value\": \"43\"\n }\n }\n}\n" + testgenValid(t, input, jsonRef) +} + +func TestTOMLTest_Valid_ImplicitAndExplicitBefore(t *testing.T) { + input := "[a]\nbetter = 43\n\n[a.b.c]\nanswer = 42\n" + jsonRef := "{\n \"a\": {\n \"b\": {\n \"c\": {\n \"answer\": {\n \"type\": \"integer\",\n \"value\": \"42\"\n }\n }\n },\n \"better\": {\n \"type\": \"integer\",\n \"value\": \"43\"\n }\n }\n}\n" + testgenValid(t, input, jsonRef) +} - input := `thevoid = [[[[[]]]]]` - jsonRef := `{ - "thevoid": { "type": "array", "value": [ - {"type": "array", "value": [ - {"type": "array", "value": [ - {"type": "array", "value": [ - {"type": "array", "value": []} - ]} - ]} - ]} - ]} -}` +func TestTOMLTest_Valid_ImplicitGroups(t *testing.T) { + input := "[a.b.c]\nanswer = 42\n" + jsonRef := "{\n \"a\": {\n \"b\": {\n \"c\": {\n \"answer\": {\n \"type\": \"integer\",\n \"value\": \"42\"\n }\n }\n }\n }\n}\n" testgenValid(t, input, jsonRef) } -func TestValidArrayNospaces(t *testing.T) { +func TestTOMLTest_Valid_InlineTable_Array(t *testing.T) { + input := "people = [{first_name = \"Bruce\", last_name = \"Springsteen\"},\n {first_name = \"Eric\", last_name = \"Clapton\"},\n {first_name = \"Bob\", last_name = \"Seger\"}]\n" + jsonRef := "{\n \"people\": [\n {\n \"first_name\": {\n \"type\": \"string\",\n \"value\": \"Bruce\"\n },\n \"last_name\": {\n \"type\": \"string\",\n \"value\": \"Springsteen\"\n }\n },\n {\n \"first_name\": {\n \"type\": \"string\",\n \"value\": \"Eric\"\n },\n \"last_name\": {\n \"type\": \"string\",\n \"value\": \"Clapton\"\n }\n },\n {\n \"first_name\": {\n \"type\": \"string\",\n \"value\": \"Bob\"\n },\n \"last_name\": {\n \"type\": \"string\",\n \"value\": \"Seger\"\n }\n }\n ]\n}\n" + testgenValid(t, input, jsonRef) +} - input := `ints = [1,2,3]` - jsonRef := `{ - "ints": { - "type": "array", - "value": [ - {"type": "integer", "value": "1"}, - {"type": "integer", "value": "2"}, - {"type": "integer", "value": "3"} - ] - } -}` +func TestTOMLTest_Valid_InlineTable_Bool(t *testing.T) { + input := "a = {a = true, b = false}\n" + jsonRef := "{\n \"a\": {\n \"a\": {\n \"type\": \"bool\",\n \"value\": \"true\"\n },\n \"b\": {\n \"type\": \"bool\",\n \"value\": \"false\"\n }\n }\n}\n" testgenValid(t, input, jsonRef) } -func TestValidArraysHetergeneous(t *testing.T) { +func TestTOMLTest_Valid_InlineTable_Empty(t *testing.T) { + input := "empty1 = {}\nempty2 = { }\nempty_in_array = [ { not_empty = 1 }, {} ]\nempty_in_array2 = [{},{not_empty=1}]\nmany_empty = [{},{},{}]\nnested_empty = {\"empty\"={}}\n" + jsonRef := "{\n \"empty1\": {},\n \"empty2\": {},\n \"empty_in_array\": [\n {\n \"not_empty\": {\n \"type\": \"integer\",\n \"value\": \"1\"\n }\n },\n {}\n ],\n \"empty_in_array2\": [\n {},\n {\n \"not_empty\": {\n \"type\": \"integer\",\n \"value\": \"1\"\n }\n }\n ],\n \"many_empty\": [\n {},\n {},\n {}\n ],\n \"nested_empty\": {\n \"empty\": {}\n }\n}\n" + testgenValid(t, input, jsonRef) +} - input := `mixed = [[1, 2], ["a", "b"], [1.1, 2.1]]` - jsonRef := `{ - "mixed": { - "type": "array", - "value": [ - {"type": "array", "value": [ - {"type": "integer", "value": "1"}, - {"type": "integer", "value": "2"} - ]}, - {"type": "array", "value": [ - {"type": "string", "value": "a"}, - {"type": "string", "value": "b"} - ]}, - {"type": "array", "value": [ - {"type": "float", "value": "1.1"}, - {"type": "float", "value": "2.1"} - ]} - ] - } -}` +func TestTOMLTest_Valid_InlineTable_EndInBool(t *testing.T) { + input := "black = { python=\">3.6\", version=\">=18.9b0\", allow_prereleases=true }\n" + jsonRef := "{\n \"black\": {\n \"allow_prereleases\": {\n \"type\": \"bool\",\n \"value\": \"true\"\n },\n \"python\": {\n \"type\": \"string\",\n \"value\": \"\\u003e3.6\"\n },\n \"version\": {\n \"type\": \"string\",\n \"value\": \"\\u003e=18.9b0\"\n }\n }\n}\n" testgenValid(t, input, jsonRef) } -func TestValidArraysNested(t *testing.T) { +func TestTOMLTest_Valid_InlineTable_InlineTable(t *testing.T) { + input := "name = { first = \"Tom\", last = \"Preston-Werner\" }\npoint = { x = 1, y = 2 }\nsimple = { a = 1 }\nstr-key = { \"a\" = 1 }\ntable-array = [{ \"a\" = 1 }, { \"b\" = 2 }]\n" + jsonRef := "{\n \"name\": {\n \"first\": {\n \"type\": \"string\",\n \"value\": \"Tom\"\n },\n \"last\": {\n \"type\": \"string\",\n \"value\": \"Preston-Werner\"\n }\n },\n \"point\": {\n \"x\": {\n \"type\": \"integer\",\n \"value\": \"1\"\n },\n \"y\": {\n \"type\": \"integer\",\n \"value\": \"2\"\n }\n },\n \"simple\": {\n \"a\": {\n \"type\": \"integer\",\n \"value\": \"1\"\n }\n },\n \"str-key\": {\n \"a\": {\n \"type\": \"integer\",\n \"value\": \"1\"\n }\n },\n \"table-array\": [\n {\n \"a\": {\n \"type\": \"integer\",\n \"value\": \"1\"\n }\n },\n {\n \"b\": {\n \"type\": \"integer\",\n \"value\": \"2\"\n }\n }\n ]\n}\n" + testgenValid(t, input, jsonRef) +} - input := `nest = [["a"], ["b"]]` - jsonRef := `{ - "nest": { - "type": "array", - "value": [ - {"type": "array", "value": [ - {"type": "string", "value": "a"} - ]}, - {"type": "array", "value": [ - {"type": "string", "value": "b"} - ]} - ] - } -}` - testgenValid(t, input, jsonRef) -} - -func TestValidArrays(t *testing.T) { - - input := `ints = [1, 2, 3] -floats = [1.1, 2.1, 3.1] -strings = ["a", "b", "c"] -dates = [ - 1987-07-05T17:45:00Z, - 1979-05-27T07:32:00Z, - 2006-06-01T11:00:00Z, -]` - jsonRef := `{ - "ints": { - "type": "array", - "value": [ - {"type": "integer", "value": "1"}, - {"type": "integer", "value": "2"}, - {"type": "integer", "value": "3"} - ] - }, - "floats": { - "type": "array", - "value": [ - {"type": "float", "value": "1.1"}, - {"type": "float", "value": "2.1"}, - {"type": "float", "value": "3.1"} - ] - }, - "strings": { - "type": "array", - "value": [ - {"type": "string", "value": "a"}, - {"type": "string", "value": "b"}, - {"type": "string", "value": "c"} - ] - }, - "dates": { - "type": "array", - "value": [ - {"type": "datetime", "value": "1987-07-05T17:45:00Z"}, - {"type": "datetime", "value": "1979-05-27T07:32:00Z"}, - {"type": "datetime", "value": "2006-06-01T11:00:00Z"} - ] - } -}` - testgenValid(t, input, jsonRef) -} - -func TestValidBool(t *testing.T) { - - input := `t = true -f = false` - jsonRef := `{ - "f": {"type": "bool", "value": "false"}, - "t": {"type": "bool", "value": "true"} -}` - testgenValid(t, input, jsonRef) -} - -func TestValidCommentsEverywhere(t *testing.T) { - - input := `# Top comment. - # Top comment. -# Top comment. - -# [no-extraneous-groups-please] - -[group] # Comment -answer = 42 # Comment -# no-extraneous-keys-please = 999 -# In between comment. -more = [ # Comment - # What about multiple # comments? - # Can you handle it? - # - # Evil. -# Evil. - 42, 42, # Comments within arrays are fun. - # What about multiple # comments? - # Can you handle it? - # - # Evil. -# Evil. -# ] Did I fool you? -] # Hopefully not.` - jsonRef := `{ - "group": { - "answer": {"type": "integer", "value": "42"}, - "more": { - "type": "array", - "value": [ - {"type": "integer", "value": "42"}, - {"type": "integer", "value": "42"} - ] - } - } -}` - testgenValid(t, input, jsonRef) -} - -func TestValidDatetime(t *testing.T) { - - input := `bestdayever = 1987-07-05T17:45:00Z` - jsonRef := `{ - "bestdayever": {"type": "datetime", "value": "1987-07-05T17:45:00Z"} -}` - testgenValid(t, input, jsonRef) -} - -func TestValidEmpty(t *testing.T) { - - input := `` - jsonRef := `{}` - testgenValid(t, input, jsonRef) -} - -func TestValidExample(t *testing.T) { - - input := `best-day-ever = 1987-07-05T17:45:00Z - -[numtheory] -boring = false -perfection = [6, 28, 496]` - jsonRef := `{ - "best-day-ever": {"type": "datetime", "value": "1987-07-05T17:45:00Z"}, - "numtheory": { - "boring": {"type": "bool", "value": "false"}, - "perfection": { - "type": "array", - "value": [ - {"type": "integer", "value": "6"}, - {"type": "integer", "value": "28"}, - {"type": "integer", "value": "496"} - ] - } - } -}` +func TestTOMLTest_Valid_InlineTable_KeyDotted(t *testing.T) { + input := "inline = {a.b = 42}\n\nmany.dots.here.dot.dot.dot = {a.b.c = 1, a.b.d = 2}\n\na = { a.b = 1 }\nb = { \"a\".\"b\" = 1 }\nc = { a . b = 1 }\nd = { 'a' . \"b\" = 1 }\ne = {a.b=1}\n\n[tbl]\na.b.c = {d.e=1}\n\n[tbl.x]\na.b.c = {d.e=1}\n\n[[arr]]\nt = {a.b=1}\nT = {a.b=1}\n\n[[arr]]\nt = {a.b=2}\nT = {a.b=2}\n" + jsonRef := "{\n \"a\": {\n \"a\": {\n \"b\": {\n \"type\": \"integer\",\n \"value\": \"1\"\n }\n }\n },\n \"arr\": [\n {\n \"T\": {\n \"a\": {\n \"b\": {\n \"type\": \"integer\",\n \"value\": \"1\"\n }\n }\n },\n \"t\": {\n \"a\": {\n \"b\": {\n \"type\": \"integer\",\n \"value\": \"1\"\n }\n }\n }\n },\n {\n \"T\": {\n \"a\": {\n \"b\": {\n \"type\": \"integer\",\n \"value\": \"2\"\n }\n }\n },\n \"t\": {\n \"a\": {\n \"b\": {\n \"type\": \"integer\",\n \"value\": \"2\"\n }\n }\n }\n }\n ],\n \"b\": {\n \"a\": {\n \"b\": {\n \"type\": \"integer\",\n \"value\": \"1\"\n }\n }\n },\n \"c\": {\n \"a\": {\n \"b\": {\n \"type\": \"integer\",\n \"value\": \"1\"\n }\n }\n },\n \"d\": {\n \"a\": {\n \"b\": {\n \"type\": \"integer\",\n \"value\": \"1\"\n }\n }\n },\n \"e\": {\n \"a\": {\n \"b\": {\n \"type\": \"integer\",\n \"value\": \"1\"\n }\n }\n },\n \"inline\": {\n \"a\": {\n \"b\": {\n \"type\": \"integer\",\n \"value\": \"42\"\n }\n }\n },\n \"many\": {\n \"dots\": {\n \"here\": {\n \"dot\": {\n \"dot\": {\n \"dot\": {\n \"a\": {\n \"b\": {\n \"c\": {\n \"type\": \"integer\",\n \"value\": \"1\"\n },\n \"d\": {\n \"type\": \"integer\",\n \"value\": \"2\"\n }\n }\n }\n }\n }\n }\n }\n }\n },\n \"tbl\": {\n \"a\": {\n \"b\": {\n \"c\": {\n \"d\": {\n \"e\": {\n \"type\": \"integer\",\n \"value\": \"1\"\n }\n }\n }\n }\n },\n \"x\": {\n \"a\": {\n \"b\": {\n \"c\": {\n \"d\": {\n \"e\": {\n \"type\": \"integer\",\n \"value\": \"1\"\n }\n }\n }\n }\n }\n }\n }\n}\n" testgenValid(t, input, jsonRef) } -func TestValidFloat(t *testing.T) { +func TestTOMLTest_Valid_InlineTable_Multiline(t *testing.T) { + input := "tbl_multiline = { a = 1, b = \"\"\"\nmultiline\n\"\"\", c = \"\"\"and yet\nanother line\"\"\", d = 4 }\n" + jsonRef := "{\n \"tbl_multiline\": {\n \"a\": {\n \"type\": \"integer\",\n \"value\": \"1\"\n },\n \"b\": {\n \"type\": \"string\",\n \"value\": \"multiline\\n\"\n },\n \"c\": {\n \"type\": \"string\",\n \"value\": \"and yet\\nanother line\"\n },\n \"d\": {\n \"type\": \"integer\",\n \"value\": \"4\"\n }\n }\n}\n" + testgenValid(t, input, jsonRef) +} - input := `pi = 3.14 -negpi = -3.14` - jsonRef := `{ - "pi": {"type": "float", "value": "3.14"}, - "negpi": {"type": "float", "value": "-3.14"} -}` +func TestTOMLTest_Valid_InlineTable_Nest(t *testing.T) { + input := "tbl_tbl_empty = { tbl_0 = {} }\ntbl_tbl_val = { tbl_1 = { one = 1 } }\ntbl_arr_tbl = { arr_tbl = [ { one = 1 } ] }\narr_tbl_tbl = [ { tbl = { one = 1 } } ]\n\n# Array-of-array-of-table is interesting because it can only\n# be represented in inline form.\narr_arr_tbl_empty = [ [ {} ] ]\narr_arr_tbl_val = [ [ { one = 1 } ] ]\narr_arr_tbls = [ [ { one = 1 }, { two = 2 } ] ]\n" + jsonRef := "{\n \"arr_arr_tbl_empty\": [\n [\n {}\n ]\n ],\n \"arr_arr_tbl_val\": [\n [\n {\n \"one\": {\n \"type\": \"integer\",\n \"value\": \"1\"\n }\n }\n ]\n ],\n \"arr_arr_tbls\": [\n [\n {\n \"one\": {\n \"type\": \"integer\",\n \"value\": \"1\"\n }\n },\n {\n \"two\": {\n \"type\": \"integer\",\n \"value\": \"2\"\n }\n }\n ]\n ],\n \"arr_tbl_tbl\": [\n {\n \"tbl\": {\n \"one\": {\n \"type\": \"integer\",\n \"value\": \"1\"\n }\n }\n }\n ],\n \"tbl_arr_tbl\": {\n \"arr_tbl\": [\n {\n \"one\": {\n \"type\": \"integer\",\n \"value\": \"1\"\n }\n }\n ]\n },\n \"tbl_tbl_empty\": {\n \"tbl_0\": {}\n },\n \"tbl_tbl_val\": {\n \"tbl_1\": {\n \"one\": {\n \"type\": \"integer\",\n \"value\": \"1\"\n }\n }\n }\n}\n" testgenValid(t, input, jsonRef) } -func TestValidImplicitAndExplicitAfter(t *testing.T) { +func TestTOMLTest_Valid_Integer_Integer(t *testing.T) { + input := "answer = 42\nposanswer = +42\nneganswer = -42\nzero = 0\n" + jsonRef := "{\n \"answer\": {\n \"type\": \"integer\",\n \"value\": \"42\"\n },\n \"neganswer\": {\n \"type\": \"integer\",\n \"value\": \"-42\"\n },\n \"posanswer\": {\n \"type\": \"integer\",\n \"value\": \"42\"\n },\n \"zero\": {\n \"type\": \"integer\",\n \"value\": \"0\"\n }\n}\n" + testgenValid(t, input, jsonRef) +} - input := `[a.b.c] -answer = 42 +func TestTOMLTest_Valid_Integer_Literals(t *testing.T) { + input := "bin1 = 0b11010110\nbin2 = 0b1_0_1\n\noct1 = 0o01234567\noct2 = 0o755\noct3 = 0o7_6_5\n\nhex1 = 0xDEADBEEF\nhex2 = 0xdeadbeef\nhex3 = 0xdead_beef\nhex4 = 0x00987\n" + jsonRef := "{\n \"bin1\": {\n \"type\": \"integer\",\n \"value\": \"214\"\n },\n \"bin2\": {\n \"type\": \"integer\",\n \"value\": \"5\"\n },\n \"hex1\": {\n \"type\": \"integer\",\n \"value\": \"3735928559\"\n },\n \"hex2\": {\n \"type\": \"integer\",\n \"value\": \"3735928559\"\n },\n \"hex3\": {\n \"type\": \"integer\",\n \"value\": \"3735928559\"\n },\n \"hex4\": {\n \"type\": \"integer\",\n \"value\": \"2439\"\n },\n \"oct1\": {\n \"type\": \"integer\",\n \"value\": \"342391\"\n },\n \"oct2\": {\n \"type\": \"integer\",\n \"value\": \"493\"\n },\n \"oct3\": {\n \"type\": \"integer\",\n \"value\": \"501\"\n }\n}\n" + testgenValid(t, input, jsonRef) +} -[a] -better = 43` - jsonRef := `{ - "a": { - "better": {"type": "integer", "value": "43"}, - "b": { - "c": { - "answer": {"type": "integer", "value": "42"} - } - } - } -}` +func TestTOMLTest_Valid_Integer_Long(t *testing.T) { + input := "int64-max = 9223372036854775807\nint64-max-neg = -9223372036854775808\n" + jsonRef := "{\n \"int64-max\": {\n \"type\": \"integer\",\n \"value\": \"9223372036854775807\"\n },\n \"int64-max-neg\": {\n \"type\": \"integer\",\n \"value\": \"-9223372036854775808\"\n }\n}\n" testgenValid(t, input, jsonRef) } -func TestValidImplicitAndExplicitBefore(t *testing.T) { +func TestTOMLTest_Valid_Integer_Underscore(t *testing.T) { + input := "kilo = 1_000\nx = 1_1_1_1\n" + jsonRef := "{\n \"kilo\": {\n \"type\": \"integer\",\n \"value\": \"1000\"\n },\n \"x\": {\n \"type\": \"integer\",\n \"value\": \"1111\"\n }\n}\n" + testgenValid(t, input, jsonRef) +} - input := `[a] -better = 43 +func TestTOMLTest_Valid_Integer_Zero(t *testing.T) { + input := "d1 = 0\nd2 = +0\nd3 = -0\n\nh1 = 0x0\nh2 = 0x00\nh3 = 0x00000\n\no1 = 0o0\na2 = 0o00\na3 = 0o00000\n\nb1 = 0b0\nb2 = 0b00\nb3 = 0b00000\n" + jsonRef := "{\n \"a2\": {\n \"type\": \"integer\",\n \"value\": \"0\"\n },\n \"a3\": {\n \"type\": \"integer\",\n \"value\": \"0\"\n },\n \"b1\": {\n \"type\": \"integer\",\n \"value\": \"0\"\n },\n \"b2\": {\n \"type\": \"integer\",\n \"value\": \"0\"\n },\n \"b3\": {\n \"type\": \"integer\",\n \"value\": \"0\"\n },\n \"d1\": {\n \"type\": \"integer\",\n \"value\": \"0\"\n },\n \"d2\": {\n \"type\": \"integer\",\n \"value\": \"0\"\n },\n \"d3\": {\n \"type\": \"integer\",\n \"value\": \"0\"\n },\n \"h1\": {\n \"type\": \"integer\",\n \"value\": \"0\"\n },\n \"h2\": {\n \"type\": \"integer\",\n \"value\": \"0\"\n },\n \"h3\": {\n \"type\": \"integer\",\n \"value\": \"0\"\n },\n \"o1\": {\n \"type\": \"integer\",\n \"value\": \"0\"\n }\n}\n" + testgenValid(t, input, jsonRef) +} -[a.b.c] -answer = 42` - jsonRef := `{ - "a": { - "better": {"type": "integer", "value": "43"}, - "b": { - "c": { - "answer": {"type": "integer", "value": "42"} - } - } - } -}` +func TestTOMLTest_Valid_Key_Alphanum(t *testing.T) { + input := "alpha = \"a\"\n123 = \"num\"\n000111 = \"leading\"\n10e3 = \"false float\"\none1two2 = \"mixed\"\nwith-dash = \"dashed\"\nunder_score = \"___\"\n34-11 = 23\n\n[2018_10]\n001 = 1\n\n[a-a-a]\n_ = false\n" + jsonRef := "{\n \"000111\": {\n \"type\": \"string\",\n \"value\": \"leading\"\n },\n \"10e3\": {\n \"type\": \"string\",\n \"value\": \"false float\"\n },\n \"123\": {\n \"type\": \"string\",\n \"value\": \"num\"\n },\n \"2018_10\": {\n \"001\": {\n \"type\": \"integer\",\n \"value\": \"1\"\n }\n },\n \"34-11\": {\n \"type\": \"integer\",\n \"value\": \"23\"\n },\n \"a-a-a\": {\n \"_\": {\n \"type\": \"bool\",\n \"value\": \"false\"\n }\n },\n \"alpha\": {\n \"type\": \"string\",\n \"value\": \"a\"\n },\n \"one1two2\": {\n \"type\": \"string\",\n \"value\": \"mixed\"\n },\n \"under_score\": {\n \"type\": \"string\",\n \"value\": \"___\"\n },\n \"with-dash\": {\n \"type\": \"string\",\n \"value\": \"dashed\"\n }\n}\n" testgenValid(t, input, jsonRef) } -func TestValidImplicitGroups(t *testing.T) { +func TestTOMLTest_Valid_Key_CaseSensitive(t *testing.T) { + input := "sectioN = \"NN\"\n\n[section]\nname = \"lower\"\nNAME = \"upper\"\nName = \"capitalized\"\n\n[Section]\nname = \"different section!!\"\n\"μ\" = \"greek small letter mu\"\n\"Μ\" = \"greek capital letter MU\"\nM = \"latin letter M\"\n\n" + jsonRef := "{\n \"Section\": {\n \"M\": {\n \"type\": \"string\",\n \"value\": \"latin letter M\"\n },\n \"name\": {\n \"type\": \"string\",\n \"value\": \"different section!!\"\n },\n \"Μ\": {\n \"type\": \"string\",\n \"value\": \"greek capital letter MU\"\n },\n \"μ\": {\n \"type\": \"string\",\n \"value\": \"greek small letter mu\"\n }\n },\n \"sectioN\": {\n \"type\": \"string\",\n \"value\": \"NN\"\n },\n \"section\": {\n \"NAME\": {\n \"type\": \"string\",\n \"value\": \"upper\"\n },\n \"Name\": {\n \"type\": \"string\",\n \"value\": \"capitalized\"\n },\n \"name\": {\n \"type\": \"string\",\n \"value\": \"lower\"\n }\n }\n}\n" + testgenValid(t, input, jsonRef) +} - input := `[a.b.c] -answer = 42` - jsonRef := `{ - "a": { - "b": { - "c": { - "answer": {"type": "integer", "value": "42"} - } - } - } -}` +func TestTOMLTest_Valid_Key_Dotted(t *testing.T) { + input := "# Note: this file contains literal tab characters.\n\nname.first = \"Arthur\"\n\"name\".'last' = \"Dent\"\n\nmany.dots.here.dot.dot.dot = 42\n\n# Space are ignored, and key parts can be quoted.\ncount.a = 1\ncount . b = 2\n\"count\".\"c\" = 3\n\"count\" . \"d\" = 4\n'count'.'e' = 5\n'count' . 'f' = 6\n\"count\".'g' = 7\n\"count\" . 'h' = 8\ncount.'i' = 9\ncount \t.\t 'j'\t = 10\n\"count\".k = 11\n\"count\" . l = 12\n\n[tbl]\na.b.c = 42.666\n\n[a.few.dots]\npolka.dot = \"again?\"\npolka.dance-with = \"Dot\"\n\n[[arr]]\na.b.c=1\na.b.d=2\n\n[[arr]]\na.b.c=3\na.b.d=4\n" + jsonRef := "{\n \"a\": {\n \"few\": {\n \"dots\": {\n \"polka\": {\n \"dance-with\": {\n \"type\": \"string\",\n \"value\": \"Dot\"\n },\n \"dot\": {\n \"type\": \"string\",\n \"value\": \"again?\"\n }\n }\n }\n }\n },\n \"arr\": [\n {\n \"a\": {\n \"b\": {\n \"c\": {\n \"type\": \"integer\",\n \"value\": \"1\"\n },\n \"d\": {\n \"type\": \"integer\",\n \"value\": \"2\"\n }\n }\n }\n },\n {\n \"a\": {\n \"b\": {\n \"c\": {\n \"type\": \"integer\",\n \"value\": \"3\"\n },\n \"d\": {\n \"type\": \"integer\",\n \"value\": \"4\"\n }\n }\n }\n }\n ],\n \"count\": {\n \"a\": {\n \"type\": \"integer\",\n \"value\": \"1\"\n },\n \"b\": {\n \"type\": \"integer\",\n \"value\": \"2\"\n },\n \"c\": {\n \"type\": \"integer\",\n \"value\": \"3\"\n },\n \"d\": {\n \"type\": \"integer\",\n \"value\": \"4\"\n },\n \"e\": {\n \"type\": \"integer\",\n \"value\": \"5\"\n },\n \"f\": {\n \"type\": \"integer\",\n \"value\": \"6\"\n },\n \"g\": {\n \"type\": \"integer\",\n \"value\": \"7\"\n },\n \"h\": {\n \"type\": \"integer\",\n \"value\": \"8\"\n },\n \"i\": {\n \"type\": \"integer\",\n \"value\": \"9\"\n },\n \"j\": {\n \"type\": \"integer\",\n \"value\": \"10\"\n },\n \"k\": {\n \"type\": \"integer\",\n \"value\": \"11\"\n },\n \"l\": {\n \"type\": \"integer\",\n \"value\": \"12\"\n }\n },\n \"many\": {\n \"dots\": {\n \"here\": {\n \"dot\": {\n \"dot\": {\n \"dot\": {\n \"type\": \"integer\",\n \"value\": \"42\"\n }\n }\n }\n }\n }\n },\n \"name\": {\n \"first\": {\n \"type\": \"string\",\n \"value\": \"Arthur\"\n },\n \"last\": {\n \"type\": \"string\",\n \"value\": \"Dent\"\n }\n },\n \"tbl\": {\n \"a\": {\n \"b\": {\n \"c\": {\n \"type\": \"float\",\n \"value\": \"42.666\"\n }\n }\n }\n }\n}\n" testgenValid(t, input, jsonRef) } -func TestValidInteger(t *testing.T) { +func TestTOMLTest_Valid_Key_Empty(t *testing.T) { + input := "\"\" = \"blank\"\n" + jsonRef := "{\n \"\": {\n \"type\": \"string\",\n \"value\": \"blank\"\n }\n}\n" + testgenValid(t, input, jsonRef) +} - input := `answer = 42 -neganswer = -42` - jsonRef := `{ - "answer": {"type": "integer", "value": "42"}, - "neganswer": {"type": "integer", "value": "-42"} -}` +func TestTOMLTest_Valid_Key_EqualsNospace(t *testing.T) { + input := "answer=42\n" + jsonRef := "{\n \"answer\": {\n \"type\": \"integer\",\n \"value\": \"42\"\n }\n}\n" testgenValid(t, input, jsonRef) } -func TestValidKeyEqualsNospace(t *testing.T) { +func TestTOMLTest_Valid_Key_Escapes(t *testing.T) { + input := "\"\\n\" = \"newline\"\n\"\\u00c0\" = \"latin capital letter A with grave\"\n\"\\\"\" = \"just a quote\"\n\n[\"backsp\\b\\b\"]\n\n[\"\\\"quoted\\\"\"]\nquote = true\n\n[\"a.b\".\"\\u00c0\"]\n" + jsonRef := "{\n \"\\n\": {\n \"type\": \"string\",\n \"value\": \"newline\"\n },\n \"\\\"\": {\n \"type\": \"string\",\n \"value\": \"just a quote\"\n },\n \"\\\"quoted\\\"\": {\n \"quote\": {\n \"type\": \"bool\",\n \"value\": \"true\"\n }\n },\n \"a.b\": {\n \"À\": {}\n },\n \"backsp\\u0008\\u0008\": {},\n \"À\": {\n \"type\": \"string\",\n \"value\": \"latin capital letter A with grave\"\n }\n}\n" + testgenValid(t, input, jsonRef) +} - input := `answer=42` - jsonRef := `{ - "answer": {"type": "integer", "value": "42"} -}` +func TestTOMLTest_Valid_Key_NumericDotted(t *testing.T) { + input := "1.2 = 3\n" + jsonRef := "{\n \"1\": {\n \"2\": {\n \"type\": \"integer\",\n \"value\": \"3\"\n }\n }\n}\n" testgenValid(t, input, jsonRef) } -func TestValidKeySpace(t *testing.T) { +func TestTOMLTest_Valid_Key_Numeric(t *testing.T) { + input := "1 = 1\n" + jsonRef := "{\n \"1\": {\n \"type\": \"integer\",\n \"value\": \"1\"\n }\n}\n" + testgenValid(t, input, jsonRef) +} - input := `"a b" = 1` - jsonRef := `{ - "a b": {"type": "integer", "value": "1"} -}` +func TestTOMLTest_Valid_Key_QuotedDots(t *testing.T) { + input := "plain = 1\n\"with.dot\" = 2\n\n[plain_table]\nplain = 3\n\"with.dot\" = 4\n\n[table.withdot]\nplain = 5\n\"key.with.dots\" = 6\n" + jsonRef := "{\n \"plain\": {\n \"type\": \"integer\",\n \"value\": \"1\"\n },\n \"plain_table\": {\n \"plain\": {\n \"type\": \"integer\",\n \"value\": \"3\"\n },\n \"with.dot\": {\n \"type\": \"integer\",\n \"value\": \"4\"\n }\n },\n \"table\": {\n \"withdot\": {\n \"key.with.dots\": {\n \"type\": \"integer\",\n \"value\": \"6\"\n },\n \"plain\": {\n \"type\": \"integer\",\n \"value\": \"5\"\n }\n }\n },\n \"with.dot\": {\n \"type\": \"integer\",\n \"value\": \"2\"\n }\n}\n" testgenValid(t, input, jsonRef) } -func TestValidKeySpecialChars(t *testing.T) { +func TestTOMLTest_Valid_Key_Space(t *testing.T) { + input := "\"a b\" = 1\n" + jsonRef := "{\n \"a b\": {\n \"type\": \"integer\",\n \"value\": \"1\"\n }\n}\n" + testgenValid(t, input, jsonRef) +} +func TestTOMLTest_Valid_Key_SpecialChars(t *testing.T) { input := "\"~!@$^&*()_+-`1234567890[]|/?><.,;:'\" = 1\n" - jsonRef := "{\n" + - " \"~!@$^&*()_+-`1234567890[]|/?><.,;:'\": {\n" + - " \"type\": \"integer\", \"value\": \"1\"\n" + - " }\n" + - "}\n" - testgenValid(t, input, jsonRef) -} - -func TestValidLongFloat(t *testing.T) { - - input := `longpi = 3.141592653589793 -neglongpi = -3.141592653589793` - jsonRef := `{ - "longpi": {"type": "float", "value": "3.141592653589793"}, - "neglongpi": {"type": "float", "value": "-3.141592653589793"} -}` - testgenValid(t, input, jsonRef) -} - -func TestValidLongInteger(t *testing.T) { - - input := `answer = 9223372036854775807 -neganswer = -9223372036854775808` - jsonRef := `{ - "answer": {"type": "integer", "value": "9223372036854775807"}, - "neganswer": {"type": "integer", "value": "-9223372036854775808"} -}` - testgenValid(t, input, jsonRef) -} - -func TestValidMultilineString(t *testing.T) { - - input := `multiline_empty_one = """""" -multiline_empty_two = """ -""" -multiline_empty_three = """\ - """ -multiline_empty_four = """\ - \ - \ - """ - -equivalent_one = "The quick brown fox jumps over the lazy dog." -equivalent_two = """ -The quick brown \ - - - fox jumps over \ - the lazy dog.""" - -equivalent_three = """\ - The quick brown \ - fox jumps over \ - the lazy dog.\ - """` - jsonRef := `{ - "multiline_empty_one": { - "type": "string", - "value": "" - }, - "multiline_empty_two": { - "type": "string", - "value": "" - }, - "multiline_empty_three": { - "type": "string", - "value": "" - }, - "multiline_empty_four": { - "type": "string", - "value": "" - }, - "equivalent_one": { - "type": "string", - "value": "The quick brown fox jumps over the lazy dog." - }, - "equivalent_two": { - "type": "string", - "value": "The quick brown fox jumps over the lazy dog." - }, - "equivalent_three": { - "type": "string", - "value": "The quick brown fox jumps over the lazy dog." - } -}` - testgenValid(t, input, jsonRef) -} - -func TestValidRawMultilineString(t *testing.T) { - - input := `oneline = '''This string has a ' quote character.''' -firstnl = ''' -This string has a ' quote character.''' -multiline = ''' -This string -has ' a quote character -and more than -one newline -in it.'''` - jsonRef := `{ - "oneline": { - "type": "string", - "value": "This string has a ' quote character." - }, - "firstnl": { - "type": "string", - "value": "This string has a ' quote character." - }, - "multiline": { - "type": "string", - "value": "This string\nhas ' a quote character\nand more than\none newline\nin it." - } -}` - testgenValid(t, input, jsonRef) -} - -func TestValidRawString(t *testing.T) { - - input := `backspace = 'This string has a \b backspace character.' -tab = 'This string has a \t tab character.' -newline = 'This string has a \n new line character.' -formfeed = 'This string has a \f form feed character.' -carriage = 'This string has a \r carriage return character.' -slash = 'This string has a \/ slash character.' -backslash = 'This string has a \\ backslash character.'` - jsonRef := `{ - "backspace": { - "type": "string", - "value": "This string has a \\b backspace character." - }, - "tab": { - "type": "string", - "value": "This string has a \\t tab character." - }, - "newline": { - "type": "string", - "value": "This string has a \\n new line character." - }, - "formfeed": { - "type": "string", - "value": "This string has a \\f form feed character." - }, - "carriage": { - "type": "string", - "value": "This string has a \\r carriage return character." - }, - "slash": { - "type": "string", - "value": "This string has a \\/ slash character." - }, - "backslash": { - "type": "string", - "value": "This string has a \\\\ backslash character." - } -}` - testgenValid(t, input, jsonRef) -} - -func TestValidStringEmpty(t *testing.T) { - - input := `answer = ""` - jsonRef := `{ - "answer": { - "type": "string", - "value": "" - } -}` - testgenValid(t, input, jsonRef) -} - -func TestValidStringEscapes(t *testing.T) { - - input := `backspace = "This string has a \b backspace character." -tab = "This string has a \t tab character." -newline = "This string has a \n new line character." -formfeed = "This string has a \f form feed character." -carriage = "This string has a \r carriage return character." -quote = "This string has a \" quote character." -backslash = "This string has a \\ backslash character." -notunicode1 = "This string does not have a unicode \\u escape." -notunicode2 = "This string does not have a unicode \u005Cu escape." -notunicode3 = "This string does not have a unicode \\u0075 escape." -notunicode4 = "This string does not have a unicode \\\u0075 escape."` - jsonRef := `{ - "backspace": { - "type": "string", - "value": "This string has a \u0008 backspace character." - }, - "tab": { - "type": "string", - "value": "This string has a \u0009 tab character." - }, - "newline": { - "type": "string", - "value": "This string has a \u000A new line character." - }, - "formfeed": { - "type": "string", - "value": "This string has a \u000C form feed character." - }, - "carriage": { - "type": "string", - "value": "This string has a \u000D carriage return character." - }, - "quote": { - "type": "string", - "value": "This string has a \u0022 quote character." - }, - "backslash": { - "type": "string", - "value": "This string has a \u005C backslash character." - }, - "notunicode1": { - "type": "string", - "value": "This string does not have a unicode \\u escape." - }, - "notunicode2": { - "type": "string", - "value": "This string does not have a unicode \u005Cu escape." - }, - "notunicode3": { - "type": "string", - "value": "This string does not have a unicode \\u0075 escape." - }, - "notunicode4": { - "type": "string", - "value": "This string does not have a unicode \\\u0075 escape." - } -}` - testgenValid(t, input, jsonRef) -} - -func TestValidStringSimple(t *testing.T) { - - input := `answer = "You are not drinking enough whisky."` - jsonRef := `{ - "answer": { - "type": "string", - "value": "You are not drinking enough whisky." - } -}` - testgenValid(t, input, jsonRef) -} - -func TestValidStringWithPound(t *testing.T) { - - input := `pound = "We see no # comments here." -poundcomment = "But there are # some comments here." # Did I # mess you up?` - jsonRef := `{ - "pound": {"type": "string", "value": "We see no # comments here."}, - "poundcomment": { - "type": "string", - "value": "But there are # some comments here." - } -}` - testgenValid(t, input, jsonRef) -} - -func TestValidTableArrayImplicit(t *testing.T) { - - input := `[[albums.songs]] -name = "Glory Days"` - jsonRef := `{ - "albums": { - "songs": [ - {"name": {"type": "string", "value": "Glory Days"}} - ] - } -}` - testgenValid(t, input, jsonRef) -} - -func TestValidTableArrayMany(t *testing.T) { - - input := `[[people]] -first_name = "Bruce" -last_name = "Springsteen" - -[[people]] -first_name = "Eric" -last_name = "Clapton" - -[[people]] -first_name = "Bob" -last_name = "Seger"` - jsonRef := `{ - "people": [ - { - "first_name": {"type": "string", "value": "Bruce"}, - "last_name": {"type": "string", "value": "Springsteen"} - }, - { - "first_name": {"type": "string", "value": "Eric"}, - "last_name": {"type": "string", "value": "Clapton"} - }, - { - "first_name": {"type": "string", "value": "Bob"}, - "last_name": {"type": "string", "value": "Seger"} - } - ] -}` + jsonRef := "{\n \"~!@$^\\u0026*()_+-`1234567890[]|/?\\u003e\\u003c.,;:'\": {\n \"type\": \"integer\",\n \"value\": \"1\"\n }\n}\n" + testgenValid(t, input, jsonRef) +} + +func TestTOMLTest_Valid_Key_SpecialWord(t *testing.T) { + input := "false = false\ntrue = 1\ninf = 100000000\nnan = \"ceci n'est pas un nombre\"\n\n" + jsonRef := "{\n \"false\": {\n \"type\": \"bool\",\n \"value\": \"false\"\n },\n \"inf\": {\n \"type\": \"integer\",\n \"value\": \"100000000\"\n },\n \"nan\": {\n \"type\": \"string\",\n \"value\": \"ceci n'est pas un nombre\"\n },\n \"true\": {\n \"type\": \"integer\",\n \"value\": \"1\"\n }\n}\n" + testgenValid(t, input, jsonRef) +} + +func TestTOMLTest_Valid_NewlineCrlf(t *testing.T) { + input := "os = \"DOS\"\r\nnewline = \"crlf\"\r\n" + jsonRef := "{\n \"newline\": {\n \"type\": \"string\",\n \"value\": \"crlf\"\n },\n \"os\": {\n \"type\": \"string\",\n \"value\": \"DOS\"\n }\n}\n" + testgenValid(t, input, jsonRef) +} + +func TestTOMLTest_Valid_NewlineLf(t *testing.T) { + input := "os = \"unix\"\nnewline = \"lf\"\n" + jsonRef := "{\n \"newline\": {\n \"type\": \"string\",\n \"value\": \"lf\"\n },\n \"os\": {\n \"type\": \"string\",\n \"value\": \"unix\"\n }\n}\n" + testgenValid(t, input, jsonRef) +} + +func TestTOMLTest_Valid_SpecExample1Compact(t *testing.T) { + input := "#Useless spaces eliminated.\ntitle=\"TOML Example\"\n[owner]\nname=\"Lance Uppercut\"\ndob=1979-05-27T07:32:00-08:00#First class dates\n[database]\nserver=\"192.168.1.1\"\nports=[8001,8001,8002]\nconnection_max=5000\nenabled=true\n[servers]\n[servers.alpha]\nip=\"10.0.0.1\"\ndc=\"eqdc10\"\n[servers.beta]\nip=\"10.0.0.2\"\ndc=\"eqdc10\"\n[clients]\ndata=[[\"gamma\",\"delta\"],[1,2]]\nhosts=[\n\"alpha\",\n\"omega\"\n]\n" + jsonRef := "{\n \"clients\": {\n \"data\": [\n [\n {\n \"type\": \"string\",\n \"value\": \"gamma\"\n },\n {\n \"type\": \"string\",\n \"value\": \"delta\"\n }\n ],\n [\n {\n \"type\": \"integer\",\n \"value\": \"1\"\n },\n {\n \"type\": \"integer\",\n \"value\": \"2\"\n }\n ]\n ],\n \"hosts\": [\n {\n \"type\": \"string\",\n \"value\": \"alpha\"\n },\n {\n \"type\": \"string\",\n \"value\": \"omega\"\n }\n ]\n },\n \"database\": {\n \"connection_max\": {\n \"type\": \"integer\",\n \"value\": \"5000\"\n },\n \"enabled\": {\n \"type\": \"bool\",\n \"value\": \"true\"\n },\n \"ports\": [\n {\n \"type\": \"integer\",\n \"value\": \"8001\"\n },\n {\n \"type\": \"integer\",\n \"value\": \"8001\"\n },\n {\n \"type\": \"integer\",\n \"value\": \"8002\"\n }\n ],\n \"server\": {\n \"type\": \"string\",\n \"value\": \"192.168.1.1\"\n }\n },\n \"owner\": {\n \"dob\": {\n \"type\": \"datetime\",\n \"value\": \"1979-05-27T07:32:00-08:00\"\n },\n \"name\": {\n \"type\": \"string\",\n \"value\": \"Lance Uppercut\"\n }\n },\n \"servers\": {\n \"alpha\": {\n \"dc\": {\n \"type\": \"string\",\n \"value\": \"eqdc10\"\n },\n \"ip\": {\n \"type\": \"string\",\n \"value\": \"10.0.0.1\"\n }\n },\n \"beta\": {\n \"dc\": {\n \"type\": \"string\",\n \"value\": \"eqdc10\"\n },\n \"ip\": {\n \"type\": \"string\",\n \"value\": \"10.0.0.2\"\n }\n }\n },\n \"title\": {\n \"type\": \"string\",\n \"value\": \"TOML Example\"\n }\n}\n" + testgenValid(t, input, jsonRef) +} + +func TestTOMLTest_Valid_SpecExample1(t *testing.T) { + input := "# This is a TOML document. Boom.\n\ntitle = \"TOML Example\"\n\n[owner]\nname = \"Lance Uppercut\"\ndob = 1979-05-27T07:32:00-08:00 # First class dates? Why not?\n\n[database]\nserver = \"192.168.1.1\"\nports = [ 8001, 8001, 8002 ]\nconnection_max = 5000\nenabled = true\n\n[servers]\n\n # You can indent as you please. Tabs or spaces. TOML don't care.\n [servers.alpha]\n ip = \"10.0.0.1\"\n dc = \"eqdc10\"\n\n [servers.beta]\n ip = \"10.0.0.2\"\n dc = \"eqdc10\"\n\n[clients]\ndata = [ [\"gamma\", \"delta\"], [1, 2] ]\n\n# Line breaks are OK when inside arrays\nhosts = [\n \"alpha\",\n \"omega\"\n]\n" + jsonRef := "{\n \"clients\": {\n \"data\": [\n [\n {\n \"type\": \"string\",\n \"value\": \"gamma\"\n },\n {\n \"type\": \"string\",\n \"value\": \"delta\"\n }\n ],\n [\n {\n \"type\": \"integer\",\n \"value\": \"1\"\n },\n {\n \"type\": \"integer\",\n \"value\": \"2\"\n }\n ]\n ],\n \"hosts\": [\n {\n \"type\": \"string\",\n \"value\": \"alpha\"\n },\n {\n \"type\": \"string\",\n \"value\": \"omega\"\n }\n ]\n },\n \"database\": {\n \"connection_max\": {\n \"type\": \"integer\",\n \"value\": \"5000\"\n },\n \"enabled\": {\n \"type\": \"bool\",\n \"value\": \"true\"\n },\n \"ports\": [\n {\n \"type\": \"integer\",\n \"value\": \"8001\"\n },\n {\n \"type\": \"integer\",\n \"value\": \"8001\"\n },\n {\n \"type\": \"integer\",\n \"value\": \"8002\"\n }\n ],\n \"server\": {\n \"type\": \"string\",\n \"value\": \"192.168.1.1\"\n }\n },\n \"owner\": {\n \"dob\": {\n \"type\": \"datetime\",\n \"value\": \"1979-05-27T07:32:00-08:00\"\n },\n \"name\": {\n \"type\": \"string\",\n \"value\": \"Lance Uppercut\"\n }\n },\n \"servers\": {\n \"alpha\": {\n \"dc\": {\n \"type\": \"string\",\n \"value\": \"eqdc10\"\n },\n \"ip\": {\n \"type\": \"string\",\n \"value\": \"10.0.0.1\"\n }\n },\n \"beta\": {\n \"dc\": {\n \"type\": \"string\",\n \"value\": \"eqdc10\"\n },\n \"ip\": {\n \"type\": \"string\",\n \"value\": \"10.0.0.2\"\n }\n }\n },\n \"title\": {\n \"type\": \"string\",\n \"value\": \"TOML Example\"\n }\n}\n" + testgenValid(t, input, jsonRef) +} + +func TestTOMLTest_Valid_String_DoubleQuoteEscape(t *testing.T) { + input := "test = \"\\\"one\\\"\"\n" + jsonRef := "{\n \"test\": {\n \"type\": \"string\",\n \"value\": \"\\\"one\\\"\"\n }\n}\n" + testgenValid(t, input, jsonRef) +} + +func TestTOMLTest_Valid_String_Empty(t *testing.T) { + input := "answer = \"\"\n" + jsonRef := "{\n \"answer\": {\n \"type\": \"string\",\n \"value\": \"\"\n }\n}\n" + testgenValid(t, input, jsonRef) +} + +func TestTOMLTest_Valid_String_EscapeTricky(t *testing.T) { + input := "end_esc = \"String does not end here\\\" but ends here\\\\\"\nlit_end_esc = 'String ends here\\'\n\nmultiline_unicode = \"\"\"\n\\u00a0\"\"\"\n\nmultiline_not_unicode = \"\"\"\n\\\\u0041\"\"\"\n\nmultiline_end_esc = \"\"\"When will it end? \\\"\"\"...\"\"\\\" should be here\\\"\"\"\"\n\nlit_multiline_not_unicode = '''\n\\u007f'''\n\nlit_multiline_end = '''There is no escape\\'''\n" + jsonRef := "{\n \"end_esc\": {\n \"type\": \"string\",\n \"value\": \"String does not end here\\\" but ends here\\\\\"\n },\n \"lit_end_esc\": {\n \"type\": \"string\",\n \"value\": \"String ends here\\\\\"\n },\n \"lit_multiline_end\": {\n \"type\": \"string\",\n \"value\": \"There is no escape\\\\\"\n },\n \"lit_multiline_not_unicode\": {\n \"type\": \"string\",\n \"value\": \"\\\\u007f\"\n },\n \"multiline_end_esc\": {\n \"type\": \"string\",\n \"value\": \"When will it end? \\\"\\\"\\\"...\\\"\\\"\\\" should be here\\\"\"\n },\n \"multiline_not_unicode\": {\n \"type\": \"string\",\n \"value\": \"\\\\u0041\"\n },\n \"multiline_unicode\": {\n \"type\": \"string\",\n \"value\": \"\u00a0\"\n }\n}\n" + testgenValid(t, input, jsonRef) +} + +func TestTOMLTest_Valid_String_EscapedEscape(t *testing.T) { + input := "answer = \"\\\\x64\"\n" + jsonRef := "{\n \"answer\": {\n \"type\": \"string\",\n \"value\": \"\\\\x64\"\n }\n}\n" + testgenValid(t, input, jsonRef) +} + +func TestTOMLTest_Valid_String_Escapes(t *testing.T) { + input := "backspace = \"This string has a \\b backspace character.\"\ntab = \"This string has a \\t tab character.\"\nnewline = \"This string has a \\n new line character.\"\nformfeed = \"This string has a \\f form feed character.\"\ncarriage = \"This string has a \\r carriage return character.\"\nquote = \"This string has a \\\" quote character.\"\nbackslash = \"This string has a \\\\ backslash character.\"\nnotunicode1 = \"This string does not have a unicode \\\\u escape.\"\nnotunicode2 = \"This string does not have a unicode \\u005Cu escape.\"\nnotunicode3 = \"This string does not have a unicode \\\\u0075 escape.\"\nnotunicode4 = \"This string does not have a unicode \\\\\\u0075 escape.\"\ndelete = \"This string has a \\u007F delete control code.\"\nunitseparator = \"This string has a \\u001F unit separator control code.\"\n" + jsonRef := "{\n \"backslash\": {\n \"type\": \"string\",\n \"value\": \"This string has a \\\\ backslash character.\"\n },\n \"backspace\": {\n \"type\": \"string\",\n \"value\": \"This string has a \\u0008 backspace character.\"\n },\n \"carriage\": {\n \"type\": \"string\",\n \"value\": \"This string has a \\r carriage return character.\"\n },\n \"delete\": {\n \"type\": \"string\",\n \"value\": \"This string has a \u007f delete control code.\"\n },\n \"formfeed\": {\n \"type\": \"string\",\n \"value\": \"This string has a \\u000c form feed character.\"\n },\n \"newline\": {\n \"type\": \"string\",\n \"value\": \"This string has a \\n new line character.\"\n },\n \"notunicode1\": {\n \"type\": \"string\",\n \"value\": \"This string does not have a unicode \\\\u escape.\"\n },\n \"notunicode2\": {\n \"type\": \"string\",\n \"value\": \"This string does not have a unicode \\\\u escape.\"\n },\n \"notunicode3\": {\n \"type\": \"string\",\n \"value\": \"This string does not have a unicode \\\\u0075 escape.\"\n },\n \"notunicode4\": {\n \"type\": \"string\",\n \"value\": \"This string does not have a unicode \\\\u escape.\"\n },\n \"quote\": {\n \"type\": \"string\",\n \"value\": \"This string has a \\\" quote character.\"\n },\n \"tab\": {\n \"type\": \"string\",\n \"value\": \"This string has a \\t tab character.\"\n },\n \"unitseparator\": {\n \"type\": \"string\",\n \"value\": \"This string has a \\u001f unit separator control code.\"\n }\n}\n" + testgenValid(t, input, jsonRef) +} + +func TestTOMLTest_Valid_String_MultilineQuotes(t *testing.T) { + input := "# Make sure that quotes inside multiline strings are allowed, including right\n# after the opening '''/\"\"\" and before the closing '''/\"\"\"\n\nlit_one = ''''one quote''''\nlit_two = '''''two quotes'''''\nlit_one_space = ''' 'one quote' '''\nlit_two_space = ''' ''two quotes'' '''\n\none = \"\"\"\"one quote\"\"\"\"\ntwo = \"\"\"\"\"two quotes\"\"\"\"\"\none_space = \"\"\" \"one quote\" \"\"\"\ntwo_space = \"\"\" \"\"two quotes\"\" \"\"\"\n\nmismatch1 = \"\"\"aaa'''bbb\"\"\"\nmismatch2 = '''aaa\"\"\"bbb'''\n" + jsonRef := "{\n \"lit_one\": {\n \"type\": \"string\",\n \"value\": \"'one quote'\"\n },\n \"lit_one_space\": {\n \"type\": \"string\",\n \"value\": \" 'one quote' \"\n },\n \"lit_two\": {\n \"type\": \"string\",\n \"value\": \"''two quotes''\"\n },\n \"lit_two_space\": {\n \"type\": \"string\",\n \"value\": \" ''two quotes'' \"\n },\n \"mismatch1\": {\n \"type\": \"string\",\n \"value\": \"aaa'''bbb\"\n },\n \"mismatch2\": {\n \"type\": \"string\",\n \"value\": \"aaa\\\"\\\"\\\"bbb\"\n },\n \"one\": {\n \"type\": \"string\",\n \"value\": \"\\\"one quote\\\"\"\n },\n \"one_space\": {\n \"type\": \"string\",\n \"value\": \" \\\"one quote\\\" \"\n },\n \"two\": {\n \"type\": \"string\",\n \"value\": \"\\\"\\\"two quotes\\\"\\\"\"\n },\n \"two_space\": {\n \"type\": \"string\",\n \"value\": \" \\\"\\\"two quotes\\\"\\\" \"\n }\n}\n" testgenValid(t, input, jsonRef) } -func TestValidTableArrayNest(t *testing.T) { +func TestTOMLTest_Valid_String_Multiline(t *testing.T) { + input := "# NOTE: this file includes some literal tab characters.\n\nmultiline_empty_one = \"\"\"\"\"\"\nmultiline_empty_two = \"\"\"\n\"\"\"\nmultiline_empty_three = \"\"\"\\\n \"\"\"\nmultiline_empty_four = \"\"\"\\\n \\\n \\ \n \"\"\"\n\nequivalent_one = \"The quick brown fox jumps over the lazy dog.\"\nequivalent_two = \"\"\"\nThe quick brown \\\n\n\n fox jumps over \\\n the lazy dog.\"\"\"\n\nequivalent_three = \"\"\"\\\n The quick brown \\\n fox jumps over \\\n the lazy dog.\\\n \"\"\"\n\nwhitespace-after-bs = \"\"\"\\\n The quick brown \\\n fox jumps over \\ \n the lazy dog.\\\t\n \"\"\"\n\nno-space = \"\"\"a\\\n b\"\"\"\n\nkeep-ws-before = \"\"\"a \t\\\n b\"\"\"\n\nescape-bs-1 = \"\"\"a \\\\\nb\"\"\"\n\nescape-bs-2 = \"\"\"a \\\\\\\nb\"\"\"\n\nescape-bs-3 = \"\"\"a \\\\\\\\\n b\"\"\"\n" + jsonRef := "{\n \"equivalent_one\": {\n \"type\": \"string\",\n \"value\": \"The quick brown fox jumps over the lazy dog.\"\n },\n \"equivalent_three\": {\n \"type\": \"string\",\n \"value\": \"The quick brown fox jumps over the lazy dog.\"\n },\n \"equivalent_two\": {\n \"type\": \"string\",\n \"value\": \"The quick brown fox jumps over the lazy dog.\"\n },\n \"escape-bs-1\": {\n \"type\": \"string\",\n \"value\": \"a \\\\\\nb\"\n },\n \"escape-bs-2\": {\n \"type\": \"string\",\n \"value\": \"a \\\\b\"\n },\n \"escape-bs-3\": {\n \"type\": \"string\",\n \"value\": \"a \\\\\\\\\\n b\"\n },\n \"keep-ws-before\": {\n \"type\": \"string\",\n \"value\": \"a \\tb\"\n },\n \"multiline_empty_four\": {\n \"type\": \"string\",\n \"value\": \"\"\n },\n \"multiline_empty_one\": {\n \"type\": \"string\",\n \"value\": \"\"\n },\n \"multiline_empty_three\": {\n \"type\": \"string\",\n \"value\": \"\"\n },\n \"multiline_empty_two\": {\n \"type\": \"string\",\n \"value\": \"\"\n },\n \"no-space\": {\n \"type\": \"string\",\n \"value\": \"ab\"\n },\n \"whitespace-after-bs\": {\n \"type\": \"string\",\n \"value\": \"The quick brown fox jumps over the lazy dog.\"\n }\n}\n" + testgenValid(t, input, jsonRef) +} + +func TestTOMLTest_Valid_String_Nl(t *testing.T) { + input := "nl_mid = \"val\\nue\"\nnl_end = \"\"\"value\\n\"\"\"\n\nlit_nl_end = '''value\\n'''\nlit_nl_mid = 'val\\nue'\nlit_nl_uni = 'val\\ue'\n" + jsonRef := "{\n \"lit_nl_end\": {\n \"type\": \"string\",\n \"value\": \"value\\\\n\"\n },\n \"lit_nl_mid\": {\n \"type\": \"string\",\n \"value\": \"val\\\\nue\"\n },\n \"lit_nl_uni\": {\n \"type\": \"string\",\n \"value\": \"val\\\\ue\"\n },\n \"nl_end\": {\n \"type\": \"string\",\n \"value\": \"value\\n\"\n },\n \"nl_mid\": {\n \"type\": \"string\",\n \"value\": \"val\\nue\"\n }\n}\n" + testgenValid(t, input, jsonRef) +} + +func TestTOMLTest_Valid_String_RawMultiline(t *testing.T) { + input := "oneline = '''This string has a ' quote character.'''\nfirstnl = '''\nThis string has a ' quote character.'''\nmultiline = '''\nThis string\nhas ' a quote character\nand more than\none newline\nin it.'''\n" + jsonRef := "{\n \"firstnl\": {\n \"type\": \"string\",\n \"value\": \"This string has a ' quote character.\"\n },\n \"multiline\": {\n \"type\": \"string\",\n \"value\": \"This string\\nhas ' a quote character\\nand more than\\none newline\\nin it.\"\n },\n \"oneline\": {\n \"type\": \"string\",\n \"value\": \"This string has a ' quote character.\"\n }\n}\n" + testgenValid(t, input, jsonRef) +} - input := `[[albums]] -name = "Born to Run" +func TestTOMLTest_Valid_String_Raw(t *testing.T) { + input := "backspace = 'This string has a \\b backspace character.'\ntab = 'This string has a \\t tab character.'\nnewline = 'This string has a \\n new line character.'\nformfeed = 'This string has a \\f form feed character.'\ncarriage = 'This string has a \\r carriage return character.'\nslash = 'This string has a \\/ slash character.'\nbackslash = 'This string has a \\\\ backslash character.'\n" + jsonRef := "{\n \"backslash\": {\n \"type\": \"string\",\n \"value\": \"This string has a \\\\\\\\ backslash character.\"\n },\n \"backspace\": {\n \"type\": \"string\",\n \"value\": \"This string has a \\\\b backspace character.\"\n },\n \"carriage\": {\n \"type\": \"string\",\n \"value\": \"This string has a \\\\r carriage return character.\"\n },\n \"formfeed\": {\n \"type\": \"string\",\n \"value\": \"This string has a \\\\f form feed character.\"\n },\n \"newline\": {\n \"type\": \"string\",\n \"value\": \"This string has a \\\\n new line character.\"\n },\n \"slash\": {\n \"type\": \"string\",\n \"value\": \"This string has a \\\\/ slash character.\"\n },\n \"tab\": {\n \"type\": \"string\",\n \"value\": \"This string has a \\\\t tab character.\"\n }\n}\n" + testgenValid(t, input, jsonRef) +} - [[albums.songs]] - name = "Jungleland" +func TestTOMLTest_Valid_String_Simple(t *testing.T) { + input := "answer = \"You are not drinking enough whisky.\"\n" + jsonRef := "{\n \"answer\": {\n \"type\": \"string\",\n \"value\": \"You are not drinking enough whisky.\"\n }\n}\n" + testgenValid(t, input, jsonRef) +} - [[albums.songs]] - name = "Meeting Across the River" +func TestTOMLTest_Valid_String_UnicodeEscape(t *testing.T) { + input := "answer4 = \"\\u03B4\"\nanswer8 = \"\\U000003B4\"\n" + jsonRef := "{\n \"answer4\": {\n \"type\": \"string\",\n \"value\": \"δ\"\n },\n \"answer8\": {\n \"type\": \"string\",\n \"value\": \"δ\"\n }\n}\n" + testgenValid(t, input, jsonRef) +} -[[albums]] -name = "Born in the USA" +func TestTOMLTest_Valid_String_UnicodeLiteral(t *testing.T) { + input := "answer = \"δ\"\n" + jsonRef := "{\n \"answer\": {\n \"type\": \"string\",\n \"value\": \"δ\"\n }\n}\n" + testgenValid(t, input, jsonRef) +} - [[albums.songs]] - name = "Glory Days" +func TestTOMLTest_Valid_String_WithPound(t *testing.T) { + input := "pound = \"We see no # comments here.\"\npoundcomment = \"But there are # some comments here.\" # Did I # mess you up?\n" + jsonRef := "{\n \"pound\": {\n \"type\": \"string\",\n \"value\": \"We see no # comments here.\"\n },\n \"poundcomment\": {\n \"type\": \"string\",\n \"value\": \"But there are # some comments here.\"\n }\n}\n" + testgenValid(t, input, jsonRef) +} - [[albums.songs]] - name = "Dancing in the Dark"` - jsonRef := `{ - "albums": [ - { - "name": {"type": "string", "value": "Born to Run"}, - "songs": [ - {"name": {"type": "string", "value": "Jungleland"}}, - {"name": {"type": "string", "value": "Meeting Across the River"}} - ] - }, - { - "name": {"type": "string", "value": "Born in the USA"}, - "songs": [ - {"name": {"type": "string", "value": "Glory Days"}}, - {"name": {"type": "string", "value": "Dancing in the Dark"}} - ] - } - ] -}` +func TestTOMLTest_Valid_Table_ArrayImplicit(t *testing.T) { + input := "[[albums.songs]]\nname = \"Glory Days\"\n" + jsonRef := "{\n \"albums\": {\n \"songs\": [\n {\n \"name\": {\n \"type\": \"string\",\n \"value\": \"Glory Days\"\n }\n }\n ]\n }\n}\n" testgenValid(t, input, jsonRef) } -func TestValidTableArrayOne(t *testing.T) { +func TestTOMLTest_Valid_Table_ArrayMany(t *testing.T) { + input := "[[people]]\nfirst_name = \"Bruce\"\nlast_name = \"Springsteen\"\n\n[[people]]\nfirst_name = \"Eric\"\nlast_name = \"Clapton\"\n\n[[people]]\nfirst_name = \"Bob\"\nlast_name = \"Seger\"\n" + jsonRef := "{\n \"people\": [\n {\n \"first_name\": {\n \"type\": \"string\",\n \"value\": \"Bruce\"\n },\n \"last_name\": {\n \"type\": \"string\",\n \"value\": \"Springsteen\"\n }\n },\n {\n \"first_name\": {\n \"type\": \"string\",\n \"value\": \"Eric\"\n },\n \"last_name\": {\n \"type\": \"string\",\n \"value\": \"Clapton\"\n }\n },\n {\n \"first_name\": {\n \"type\": \"string\",\n \"value\": \"Bob\"\n },\n \"last_name\": {\n \"type\": \"string\",\n \"value\": \"Seger\"\n }\n }\n ]\n}\n" + testgenValid(t, input, jsonRef) +} - input := `[[people]] -first_name = "Bruce" -last_name = "Springsteen"` - jsonRef := `{ - "people": [ - { - "first_name": {"type": "string", "value": "Bruce"}, - "last_name": {"type": "string", "value": "Springsteen"} - } - ] -}` +func TestTOMLTest_Valid_Table_ArrayNest(t *testing.T) { + input := "[[albums]]\nname = \"Born to Run\"\n\n [[albums.songs]]\n name = \"Jungleland\"\n\n [[albums.songs]]\n name = \"Meeting Across the River\"\n\n[[albums]]\nname = \"Born in the USA\"\n \n [[albums.songs]]\n name = \"Glory Days\"\n\n [[albums.songs]]\n name = \"Dancing in the Dark\"\n" + jsonRef := "{\n \"albums\": [\n {\n \"name\": {\n \"type\": \"string\",\n \"value\": \"Born to Run\"\n },\n \"songs\": [\n {\n \"name\": {\n \"type\": \"string\",\n \"value\": \"Jungleland\"\n }\n },\n {\n \"name\": {\n \"type\": \"string\",\n \"value\": \"Meeting Across the River\"\n }\n }\n ]\n },\n {\n \"name\": {\n \"type\": \"string\",\n \"value\": \"Born in the USA\"\n },\n \"songs\": [\n {\n \"name\": {\n \"type\": \"string\",\n \"value\": \"Glory Days\"\n }\n },\n {\n \"name\": {\n \"type\": \"string\",\n \"value\": \"Dancing in the Dark\"\n }\n }\n ]\n }\n ]\n}\n" testgenValid(t, input, jsonRef) } -func TestValidTableEmpty(t *testing.T) { +func TestTOMLTest_Valid_Table_ArrayOne(t *testing.T) { + input := "[[people]]\nfirst_name = \"Bruce\"\nlast_name = \"Springsteen\"\n" + jsonRef := "{\n \"people\": [\n {\n \"first_name\": {\n \"type\": \"string\",\n \"value\": \"Bruce\"\n },\n \"last_name\": {\n \"type\": \"string\",\n \"value\": \"Springsteen\"\n }\n }\n ]\n}\n" + testgenValid(t, input, jsonRef) +} - input := `[a]` - jsonRef := `{ - "a": {} -}` +func TestTOMLTest_Valid_Table_ArrayTableArray(t *testing.T) { + input := "[[a]]\n [[a.b]]\n [a.b.c]\n d = \"val0\"\n [[a.b]]\n [a.b.c]\n d = \"val1\"\n" + jsonRef := "{\n \"a\": [\n {\n \"b\": [\n {\n \"c\": {\n \"d\": {\n \"type\": \"string\",\n \"value\": \"val0\"\n }\n }\n },\n {\n \"c\": {\n \"d\": {\n \"type\": \"string\",\n \"value\": \"val1\"\n }\n }\n }\n ]\n }\n ]\n}\n" testgenValid(t, input, jsonRef) } -func TestValidTableSubEmpty(t *testing.T) { +func TestTOMLTest_Valid_Table_Empty(t *testing.T) { + input := "[a]\n" + jsonRef := "{\n \"a\": {}\n}\n" + testgenValid(t, input, jsonRef) +} - input := `[a] -[a.b]` - jsonRef := `{ - "a": { "b": {} } -}` +func TestTOMLTest_Valid_Table_Keyword(t *testing.T) { + input := "[true]\n\n[false]\n\n[inf]\n\n[nan]\n\n\n" + jsonRef := "{\n \"true\": {},\n \"false\": {},\n \"inf\": {},\n \"nan\": {}\n}\n" testgenValid(t, input, jsonRef) } -func TestValidTableWhitespace(t *testing.T) { +func TestTOMLTest_Valid_Table_Names(t *testing.T) { + input := "[a.b.c]\n[a.\"b.c\"]\n[a.'d.e']\n[a.' x ']\n[ d.e.f ]\n[ g . h . i ]\n[ j . \"ʞ\" . 'l' ]\n\n[x.1.2]\n" + jsonRef := "{\n \"a\": {\n \" x \": {},\n \"b\": {\n \"c\": {}\n },\n \"b.c\": {},\n \"d.e\": {}\n },\n \"d\": {\n \"e\": {\n \"f\": {}\n }\n },\n \"g\": {\n \"h\": {\n \"i\": {}\n }\n },\n \"j\": {\n \"ʞ\": {\n \"l\": {}\n }\n },\n \"x\": {\n \"1\": {\n \"2\": {}\n }\n }\n}\n" + testgenValid(t, input, jsonRef) +} - input := `["valid key"]` - jsonRef := `{ - "valid key": {} -}` +func TestTOMLTest_Valid_Table_NoEol(t *testing.T) { + input := "[table]\n" + jsonRef := "{\n \"table\": {}\n}\n" testgenValid(t, input, jsonRef) } -func TestValidTableWithPound(t *testing.T) { +func TestTOMLTest_Valid_Table_SubEmpty(t *testing.T) { + input := "[a]\n[a.b]\n" + jsonRef := "{\n \"a\": {\n \"b\": {}\n }\n}\n" + testgenValid(t, input, jsonRef) +} - input := `["key#group"] -answer = 42` - jsonRef := `{ - "key#group": { - "answer": {"type": "integer", "value": "42"} - } -}` +func TestTOMLTest_Valid_Table_Whitespace(t *testing.T) { + input := "[\"valid key\"]\n" + jsonRef := "{\n \"valid key\": {}\n}\n" testgenValid(t, input, jsonRef) } -func TestValidUnicodeEscape(t *testing.T) { +func TestTOMLTest_Valid_Table_WithLiteralString(t *testing.T) { + input := "['a']\n[a.'\"b\"']\n[a.'\"b\"'.c]\nanswer = 42 \n" + jsonRef := "{\n \"a\": {\n \"\\\"b\\\"\": {\n \"c\": {\n \"answer\": {\n \"type\": \"integer\",\n \"value\": \"42\"\n }\n }\n }\n }\n}\n" + testgenValid(t, input, jsonRef) +} - input := `answer4 = "\u03B4" -answer8 = "\U000003B4"` - jsonRef := `{ - "answer4": {"type": "string", "value": "\u03B4"}, - "answer8": {"type": "string", "value": "\u03B4"} -}` +func TestTOMLTest_Valid_Table_WithPound(t *testing.T) { + input := "[\"key#group\"]\nanswer = 42\n" + jsonRef := "{\n \"key#group\": {\n \"answer\": {\n \"type\": \"integer\",\n \"value\": \"42\"\n }\n }\n}\n" testgenValid(t, input, jsonRef) } -func TestValidUnicodeLiteral(t *testing.T) { +func TestTOMLTest_Valid_Table_WithSingleQuotes(t *testing.T) { + input := "['a']\n[a.'b']\n[a.'b'.c]\nanswer = 42 \n" + jsonRef := "{\n \"a\": {\n \"b\": {\n \"c\": {\n \"answer\": {\n \"type\": \"integer\",\n \"value\": \"42\"\n }\n }\n }\n }\n}\n" + testgenValid(t, input, jsonRef) +} - input := `answer = "δ"` - jsonRef := `{ - "answer": {"type": "string", "value": "δ"} -}` +func TestTOMLTest_Valid_Table_WithoutSuper(t *testing.T) { + input := "# [x] you\n# [x.y] don't\n# [x.y.z] need these\n[x.y.z.w] # for this to work\n[x] # defining a super-table afterwards is ok\n" + jsonRef := "{\n \"x\": {\n \"y\": {\n \"z\": {\n \"w\": {}\n }\n }\n }\n}\n" testgenValid(t, input, jsonRef) } From 91027d8a1921ccf76bd1d33f5743852bd7d3bb23 Mon Sep 17 00:00:00 2001 From: jidicula Date: Mon, 4 Oct 2021 21:15:32 -0400 Subject: [PATCH 224/228] feat(scanComment): Add error return value When scanning comments, it makes better sense to halt scanning and immediately return if an illegal character is encountered while scanning. This can save on performance in the perverse case of an extremely long comment that has an early offending character. Related to: pelletier/go-toml#613 --- parser.go | 15 ++++++++++++--- scanner.go | 6 +++--- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/parser.go b/parser.go index b86904ae..61d035dd 100644 --- a/parser.go +++ b/parser.go @@ -106,7 +106,10 @@ func (p *parser) parseExpression(b []byte) (ast.Reference, []byte, error) { } if b[0] == '#' { - _, rest := scanComment(b) + _, rest, err := scanComment(b) + if err != nil { + return ref, rest, err + } return ref, rest, nil } @@ -129,7 +132,10 @@ func (p *parser) parseExpression(b []byte) (ast.Reference, []byte, error) { b = p.parseWhitespace(b) if len(b) > 0 && b[0] == '#' { - _, rest := scanComment(b) + _, rest, err := scanComment(b) + if err != nil { + return ref, rest, err + } return ref, rest, nil } @@ -478,7 +484,10 @@ func (p *parser) parseOptionalWhitespaceCommentNewline(b []byte) ([]byte, error) b = p.parseWhitespace(b) if len(b) > 0 && b[0] == '#' { - _, b = scanComment(b) + _, b, err = scanComment(b) + if err != nil { + return nil, err + } } if len(b) == 0 { diff --git a/scanner.go b/scanner.go index 043adc3d..495b719d 100644 --- a/scanner.go +++ b/scanner.go @@ -106,7 +106,7 @@ func scanWhitespace(b []byte) ([]byte, []byte) { } //nolint:unparam -func scanComment(b []byte) ([]byte, []byte) { +func scanComment(b []byte) ([]byte, []byte, error) { // comment-start-symbol = %x23 ; # // non-ascii = %x80-D7FF / %xE000-10FFFF // non-eol = %x09 / %x20-7F / non-ascii @@ -114,11 +114,11 @@ func scanComment(b []byte) ([]byte, []byte) { // comment = comment-start-symbol *non-eol for i := 1; i < len(b); i++ { if b[i] == '\n' { - return b[:i], b[i:] + return b[:i], b[i:], nil } } - return b, b[len(b):] + return b, b[len(b):], nil } func scanBasicString(b []byte) ([]byte, []byte, error) { From f7f02b0d99c60644571631092b9a4520b40484ac Mon Sep 17 00:00:00 2001 From: jidicula Date: Mon, 4 Oct 2021 21:18:00 -0400 Subject: [PATCH 225/228] fix(comment test): Fail if DEL 0x7f char is found Partially resolves: pelletier/go-toml#613 --- scanner.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scanner.go b/scanner.go index 495b719d..7a562fb9 100644 --- a/scanner.go +++ b/scanner.go @@ -113,6 +113,9 @@ func scanComment(b []byte) ([]byte, []byte, error) { // // comment = comment-start-symbol *non-eol for i := 1; i < len(b); i++ { + if b[i] == 0x7f { + return b[:i], b[i:], newDecodeError(b[:i], "comments cannot include the 0x7f DEL character") + } if b[i] == '\n' { return b[:i], b[i:], nil } From 851d9c4e3a91b5f7451019e1a5dcd9e69e9402a0 Mon Sep 17 00:00:00 2001 From: jidicula Date: Mon, 4 Oct 2021 21:20:05 -0400 Subject: [PATCH 226/228] fix(comment test): Fail if LF 0x0a char is found Partially resolves: pelletier/go-toml#613 --- scanner.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scanner.go b/scanner.go index 7a562fb9..ab3b5bcc 100644 --- a/scanner.go +++ b/scanner.go @@ -116,6 +116,10 @@ func scanComment(b []byte) ([]byte, []byte, error) { if b[i] == 0x7f { return b[:i], b[i:], newDecodeError(b[:i], "comments cannot include the 0x7f DEL character") } + if b[i] == 0x0a { + return b[:i], b[i:], newDecodeError(b[:i], "comments cannot include the 0x0a LF character") + return b[:i], b[i:], newDecodeError(b[:i], "comments cannot include the 0x0A LF character") + } if b[i] == '\n' { return b[:i], b[i:], nil } From 6bc6702be7b4c5f274aedb23af0d4e63c4270117 Mon Sep 17 00:00:00 2001 From: jidicula Date: Mon, 4 Oct 2021 21:24:32 -0400 Subject: [PATCH 227/228] fix(comment test): Fail if NUL 0x00 char is found Oddly enough, the test passes when it shouldn't. Partially resolves: pelletier/go-toml#613 --- scanner.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scanner.go b/scanner.go index ab3b5bcc..d0b4886a 100644 --- a/scanner.go +++ b/scanner.go @@ -118,7 +118,9 @@ func scanComment(b []byte) ([]byte, []byte, error) { } if b[i] == 0x0a { return b[:i], b[i:], newDecodeError(b[:i], "comments cannot include the 0x0a LF character") - return b[:i], b[i:], newDecodeError(b[:i], "comments cannot include the 0x0A LF character") + } + if b[i] == 0x00 { + return b[:i], b[i:], newDecodeError(b[:i], "comments cannot include the 0x00 NUL character") } if b[i] == '\n' { return b[:i], b[i:], nil From 1c407d1d564c771bc8c8910a6bc4d7c886c76b65 Mon Sep 17 00:00:00 2001 From: jidicula Date: Mon, 4 Oct 2021 21:29:58 -0400 Subject: [PATCH 228/228] fix(comment test): Fail if US 0x1f char is found Oddly enough, the test passes when it shouldn't. Partially resolves: pelletier/go-toml#613 --- scanner.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scanner.go b/scanner.go index d0b4886a..bd36c11d 100644 --- a/scanner.go +++ b/scanner.go @@ -122,6 +122,9 @@ func scanComment(b []byte) ([]byte, []byte, error) { if b[i] == 0x00 { return b[:i], b[i:], newDecodeError(b[:i], "comments cannot include the 0x00 NUL character") } + if b[i] == 0x1f { + return b[:i], b[i:], newDecodeError(b[:i], "comments cannot include the 0x1f US character") + } if b[i] == '\n' { return b[:i], b[i:], nil }