diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cd09073..e771de4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,12 +12,33 @@ on: pull_request: jobs: - test: + + linux: runs-on: ubuntu-latest steps: - name: Checkout Sources uses: actions/checkout@v3 - name: Setup V uses: prantlf/setup-v-action@v1 - - name: Test - run: v test . + - name: Build and Test + run: make + + macos: + runs-on: macos-latest + steps: + - name: Checkout Sources + uses: actions/checkout@v3 + - name: Setup V + uses: prantlf/setup-v-action@v1 + - name: Build and Test + run: make + + windows: + runs-on: windows-latest + steps: + - name: Checkout Sources + uses: actions/checkout@v3 + - name: Setup V + uses: prantlf/setup-v-action@v1 + - name: Build + run: make diff --git a/.gitignore b/.gitignore index e3080ec..d61f33b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ .DS_Store -unmarshal_test -unmarshal_test.dSYM +*_test +*.dSYM diff --git a/.vscode/settings.json b/.vscode/settings.json index d4ee546..7f1db49 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,9 +1,8 @@ { "files.exclude": { "**/.git": true, - "**/.DS_Store": true - }, - "search.exclude": { - "**/node_modules": true + "**/.DS_Store": true, + "**/*_test": true, + "**/*.dSYM": true } } \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3c201ba --- /dev/null +++ b/Makefile @@ -0,0 +1,14 @@ +all: check test + +check: + v fmt -w . + v vet . + +test: + NO_COLOR=1 v test . + +clean: + rm -rf src/*_test src/*.dSYM + +version: + npx conventional-changelog-cli -p angular -i CHANGELOG.md -s diff --git a/README.md b/README.md index 15859d5..4531f5f 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ Parses an input string in the YAML format to an `Any` value. See [jany] for more input := r' answer: 42 ' -any := parse_text(input) +any := parse_text(input)! ``` ### parse_file(path string) !Any @@ -69,12 +69,12 @@ any := parse_text(input) Loads the contents of a text file in the YAML format and parses it to an `Any` value. See [jany] for more information about the `Any` type. ```go -any := parse_file('config.yaml') +any := parse_file('config.yaml')! ``` -### unmarshal_text[T](input string, opts UnmarshalOpts) !T +### unmarshal_text[T](input string, opts &UnmarshalOpts) !T -Unmarshals an input string in the YAML format to an instance of `T`. See [jany] for more information about the `Any` type and the `UnmarshalOpts` struct. +Unmarshals an input string in the YAML format to a new instance of `T`. See [jany] for more information about the `Any` type and the `UnmarshalOpts` struct. ```go struct Config { @@ -84,19 +84,48 @@ struct Config { input := r' answer: 42 ' -config := unmarshal_text[Config](input) +config := unmarshal_text[Config](input, UnmarshalOpts{})! ``` -### unmarshal_file[T](path string, opts UnmarshalOpts) !T +### unmarshal_text_to[T](input string, mut obj T, opts &UnmarshalOpts) ! -Loads the contents of a text file in the YAML format and unmarshals it to an instance of `T`. See [jany] for more information about the `Any` type and the `UnmarshalOpts` struct. +Unmarshals an input string in the YAML format to an existing instance of `T`. See [jany] for more information about the `Any` type and the `UnmarshalOpts` struct. ```go struct Config { answer int } -config := unmarshal_file[Config]('config.yaml') +input := r' + answer: 42 +' +mut config := Config{} +config := unmarshal_text_to(input, mut config, UnmarshalOpts{})! +``` + +### unmarshal_file[T](path string, opts &UnmarshalOpts) !T + +Loads the contents of a text file in the YAML format and unmarshals it to a new instance of `T`. See [jany] for more information about the `Any` type and the `UnmarshalOpts` struct. + +```go +struct Config { + answer int +} + +config := unmarshal_file[Config]('config.yaml', UnmarshalOpts{})! +``` + +### unmarshal_file_to[T](path string, mut obj T, opts UnmarshalOpts) ! + +Loads the contents of a text file in the YAML format and unmarshals it to an existing instance of `T`. See [jany] for more information about the `Any` type and the `UnmarshalOpts` struct. + +```go +struct Config { + answer int +} + +mut config := Config{} +config := unmarshal_file_to('config.yaml', mut config, UnmarshalOpts{})! ``` ## Performance diff --git a/bench/bench-float.vsh b/bench/bench-float.vsh new file mode 100755 index 0000000..625e5ea --- /dev/null +++ b/bench/bench-float.vsh @@ -0,0 +1,76 @@ +#!/usr/bin/env -S v -prod run + +import benchmark +import rand +import strconv +import time + +fn C.strtod(charptr, &charptr) f64 +fn C.strtol(charptr, &charptr, int) int + +fn strtod(s string) !f64 { + end := unsafe { nil } + C.errno = 0 + n := C.strtod(s.str, &end) + if C.errno != 0 { + return error('out of range') + } + if unsafe { s.str + s.len != end } { + return error('not a number') + } + return n +} + +fn strtol(s string) !int { + end := unsafe { nil } + C.errno = 0 + n := C.strtol(s.str, &end, 10) + if C.errno != 0 { + return error('out of range') + } + if unsafe { s.str + s.len != end } { + return error('not a number') + } + return n +} + +fn str_to_num(s string) !f64 { + if s.contains_any('.eE') { + return strtod(s)! + } + return f64(strconv.atoi(s) or { strtod(s)! }) +} + +rand.seed([u32(time.now().unix), 0]) + +sample_size := 1500 +mut strings := []string{} +for _ in 0 .. sample_size { + n := rand.int_in_range(0, 10000000) or { 0 } + strings << '${n}' + d := rand.int_in_range(0, 10000000) or { 0 } + f := rand.int_in_range(0, 10000000) or { 0 } + strings << '${d}.${f}' +} + +mut b := benchmark.start() + +for s in strings { + _ := strconv.atof64(s)! +} +b.measure('parsing floats') + +for s in strings { + _ := strtod(s)! +} +b.measure('parsing floats with C') + +for s in strings { + _ := f64(strconv.atoi(s) or { strtod(s)! }) +} +b.measure('parsing integers and floats') + +for s in strings { + _ := str_to_num(s)! +} +b.measure('parsing integers and floats with check') diff --git a/bench/bench-int.vsh b/bench/bench-int.vsh new file mode 100755 index 0000000..ae97dd9 --- /dev/null +++ b/bench/bench-int.vsh @@ -0,0 +1,80 @@ +#!/usr/bin/env -S v -prod run + +import benchmark +import rand +import strconv +import time + +fn C.strtod(charptr, &charptr) f64 +fn C.strtol(charptr, &charptr, int) int + +fn strtod(s string) !f64 { + C.errno = 0 + n := C.strtod(s.str, 0) + if C.errno != 0 { + return error('range overflow') + } + return n +} + +fn strtol(s string) !int { + C.errno = 0 + n := C.strtol(s.str, 0, 10) + if C.errno != 0 { + return error('range overflow') + } + return n +} + +rand.seed([u32(time.now().unix), 0]) + +sample_size := 3000 +mut strings := []string{} +for _ in 0 .. sample_size { + n := rand.int_in_range(0, 10000000) or { 0 } + strings << '${n}' +} + +mut b := benchmark.start() +for s in strings { + _ := f64(strconv.atoi(s)!) +} +b.measure('parsing integers') + +for s in strings { + C.errno = 0 + _ := f64(C.strtol(s.str, 0, 10)) + if C.errno != 0 { + panic('range overflow') + } +} +b.measure('parsing integers with C') + +for s in strings { + _ := f64(strtol(s)!) +} +b.measure('parsing integers with C wrapped') + +for s in strings { + _ := strconv.atof64(s)! +} +b.measure('parsing floats') + +for s in strings { + _ := strconv.atof_quick(s) +} +b.measure('parsing floats quickly') + +for s in strings { + C.errno = 0 + _ := C.strtod(s.str, 0) + if C.errno != 0 { + panic('range overflow') + } +} +b.measure('parsing floats with C') + +for s in strings { + _ := strtod(s)! +} +b.measure('parsing floats with C wrapped') diff --git a/parse_bench.vsh b/bench/parse_bench.vsh similarity index 75% rename from parse_bench.vsh rename to bench/parse_bench.vsh index 02d8b5c..2005334 100755 --- a/parse_bench.vsh +++ b/bench/parse_bench.vsh @@ -7,8 +7,8 @@ import prantlf.yaml const repeats = 20 -json_in := os.read_file('vlang.io.har.json')! -yaml_in := os.read_file('vlang.io.har.yaml')! +json_in := os.read_file('src/testdata/vlang.io.har.json')! +yaml_in := os.read_file('src/testdata/vlang.io.har.yaml')! opts := json.ParseOpts{} diff --git a/errors.v b/src/errors.v similarity index 98% rename from errors.v rename to src/errors.v index d9a0775..5006412 100644 --- a/errors.v +++ b/src/errors.v @@ -2,6 +2,7 @@ module yaml import prantlf.jany { Any } +[noinit] pub struct YamlError { Error message string diff --git a/parse.v b/src/parse.v similarity index 100% rename from parse.v rename to src/parse.v diff --git a/vlang.io.har.json b/src/testdata/vlang.io.har.json similarity index 100% rename from vlang.io.har.json rename to src/testdata/vlang.io.har.json diff --git a/vlang.io.har.yaml b/src/testdata/vlang.io.har.yaml similarity index 100% rename from vlang.io.har.yaml rename to src/testdata/vlang.io.har.yaml diff --git a/src/unmarshal.v b/src/unmarshal.v new file mode 100644 index 0000000..ea544c8 --- /dev/null +++ b/src/unmarshal.v @@ -0,0 +1,25 @@ +module yaml + +import prantlf.jany { UnmarshalOpts } + +pub fn unmarshal_text[T](input string, opts &UnmarshalOpts) !T { + mut obj := T{} + unmarshal_text_to[T](input, mut obj, opts)! + return obj +} + +pub fn unmarshal_text_to[T](input string, mut obj T, opts &UnmarshalOpts) ! { + a := parse_text(input)! + jany.unmarshal_to[T](a, mut obj, opts)! +} + +pub fn unmarshal_file[T](path string, opts &UnmarshalOpts) !T { + mut obj := T{} + unmarshal_file_to[T](path, mut obj, opts)! + return obj +} + +pub fn unmarshal_file_to[T](path string, mut obj T, opts &UnmarshalOpts) ! { + a := parse_file(path)! + jany.unmarshal_to[T](a, mut obj, opts)! +} diff --git a/unmarshal_test.v b/src/unmarshal_test.v similarity index 100% rename from unmarshal_test.v rename to src/unmarshal_test.v diff --git a/c-yaml.v b/src/yaml.c.v similarity index 100% rename from c-yaml.v rename to src/yaml.c.v diff --git a/unmarshal.v b/unmarshal.v deleted file mode 100644 index 2fffa7b..0000000 --- a/unmarshal.v +++ /dev/null @@ -1,13 +0,0 @@ -module yaml - -import prantlf.jany { UnmarshalOpts, unmarshal } - -pub fn unmarshal_file[T](path string, opts UnmarshalOpts) !T { - a := parse_file(path)! - return unmarshal[T](a, opts)! -} - -pub fn unmarshal_text[T](input string, opts UnmarshalOpts) !T { - a := parse_text(input)! - return unmarshal[T](a, opts)! -}