From 40313f2c053472a788a7ae70853a19bdac79fac4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Jun 2025 17:51:36 +0000 Subject: [PATCH] Bump github.com/alicebob/miniredis/v2 from 2.22.0 to 2.35.0 Bumps [github.com/alicebob/miniredis/v2](https://github.com/alicebob/miniredis) from 2.22.0 to 2.35.0. - [Release notes](https://github.com/alicebob/miniredis/releases) - [Changelog](https://github.com/alicebob/miniredis/blob/master/CHANGELOG.md) - [Commits](https://github.com/alicebob/miniredis/compare/v2.22.0...v2.35.0) --- updated-dependencies: - dependency-name: github.com/alicebob/miniredis/v2 dependency-version: 2.35.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 5 +- go.sum | 16 +- .../github.com/alicebob/gopher-json/README.md | 7 - vendor/github.com/alicebob/gopher-json/doc.go | 33 - .../alicebob/miniredis/v2/.gitignore | 2 + .../alicebob/miniredis/v2/CHANGELOG.md | 114 +- .../github.com/alicebob/miniredis/v2/Makefile | 37 +- .../alicebob/miniredis/v2/README.md | 27 +- .../alicebob/miniredis/v2/cmd_client.go | 68 + .../alicebob/miniredis/v2/cmd_cluster.go | 9 +- .../alicebob/miniredis/v2/cmd_command.go | 6 +- .../alicebob/miniredis/v2/cmd_connection.go | 86 +- .../alicebob/miniredis/v2/cmd_generic.go | 400 ++++-- .../alicebob/miniredis/v2/cmd_geo.go | 186 +-- .../alicebob/miniredis/v2/cmd_hash.go | 240 +++- .../alicebob/miniredis/v2/cmd_hll.go | 4 +- .../alicebob/miniredis/v2/cmd_info.go | 40 + .../alicebob/miniredis/v2/cmd_list.go | 477 ++++-- .../alicebob/miniredis/v2/cmd_object.go | 58 + .../alicebob/miniredis/v2/cmd_pubsub.go | 24 +- .../alicebob/miniredis/v2/cmd_scripting.go | 150 +- .../alicebob/miniredis/v2/cmd_server.go | 71 +- .../alicebob/miniredis/v2/cmd_set.go | 206 ++- .../alicebob/miniredis/v2/cmd_sorted_set.go | 645 ++++++--- .../alicebob/miniredis/v2/cmd_stream.go | 791 ++++++++-- .../alicebob/miniredis/v2/cmd_string.go | 281 ++-- .../alicebob/miniredis/v2/cmd_transactions.go | 6 +- vendor/github.com/alicebob/miniredis/v2/db.go | 202 ++- .../alicebob/miniredis/v2/direct.go | 65 +- .../alicebob/miniredis/v2/fpconv/LICENSE.txt | 26 + .../alicebob/miniredis/v2/fpconv/Makefile | 6 + .../alicebob/miniredis/v2/fpconv/README.md | 3 + .../alicebob/miniredis/v2/fpconv/dtoa.go | 286 ++++ .../alicebob/miniredis/v2/fpconv/fp.go | 96 ++ .../alicebob/miniredis/v2/fpconv/powers.go | 82 ++ .../github.com/alicebob/miniredis/v2/geo.go | 8 +- .../{ => miniredis/v2}/gopher-json/LICENSE | 0 .../miniredis/v2/gopher-json/README.md | 1 + .../{ => miniredis/v2}/gopher-json/json.go | 0 .../github.com/alicebob/miniredis/v2/lua.go | 53 +- .../alicebob/miniredis/v2/miniredis.go | 97 +- .../github.com/alicebob/miniredis/v2/opts.go | 41 +- .../alicebob/miniredis/v2/proto/Makefile | 2 + .../alicebob/miniredis/v2/proto/client.go | 60 + .../alicebob/miniredis/v2/proto/proto.go | 288 ++++ .../alicebob/miniredis/v2/proto/types.go | 102 ++ .../github.com/alicebob/miniredis/v2/redis.go | 52 +- .../alicebob/miniredis/v2/server/server.go | 69 +- .../alicebob/miniredis/v2/size/readme.md | 2 + .../alicebob/miniredis/v2/size/size.go | 138 ++ .../alicebob/miniredis/v2/stream.go | 156 +- vendor/github.com/yuin/gopher-lua/.gitignore | 1 + vendor/github.com/yuin/gopher-lua/.travis.yml | 18 - vendor/github.com/yuin/gopher-lua/README.rst | 17 +- vendor/github.com/yuin/gopher-lua/_state.go | 54 +- vendor/github.com/yuin/gopher-lua/_vm.go | 132 +- vendor/github.com/yuin/gopher-lua/ast/expr.go | 1 + vendor/github.com/yuin/gopher-lua/ast/stmt.go | 12 + vendor/github.com/yuin/gopher-lua/auxlib.go | 7 +- vendor/github.com/yuin/gopher-lua/baselib.go | 13 +- vendor/github.com/yuin/gopher-lua/compile.go | 297 +++- vendor/github.com/yuin/gopher-lua/config.go | 7 + vendor/github.com/yuin/gopher-lua/debuglib.go | 4 +- vendor/github.com/yuin/gopher-lua/iolib.go | 5 +- vendor/github.com/yuin/gopher-lua/loadlib.go | 3 + vendor/github.com/yuin/gopher-lua/oslib.go | 45 +- .../github.com/yuin/gopher-lua/parse/Makefile | 3 + .../github.com/yuin/gopher-lua/parse/lexer.go | 30 +- .../yuin/gopher-lua/parse/parser.go | 1280 ++++++++++------- .../yuin/gopher-lua/parse/parser.go.y | 15 +- vendor/github.com/yuin/gopher-lua/pm/pm.go | 2 +- vendor/github.com/yuin/gopher-lua/state.go | 108 +- vendor/github.com/yuin/gopher-lua/table.go | 2 +- vendor/github.com/yuin/gopher-lua/value.go | 66 +- vendor/github.com/yuin/gopher-lua/vm.go | 855 ++++++++++- vendor/modules.txt | 15 +- 76 files changed, 6734 insertions(+), 2082 deletions(-) delete mode 100644 vendor/github.com/alicebob/gopher-json/README.md delete mode 100644 vendor/github.com/alicebob/gopher-json/doc.go create mode 100644 vendor/github.com/alicebob/miniredis/v2/cmd_client.go create mode 100644 vendor/github.com/alicebob/miniredis/v2/cmd_info.go create mode 100644 vendor/github.com/alicebob/miniredis/v2/cmd_object.go create mode 100644 vendor/github.com/alicebob/miniredis/v2/fpconv/LICENSE.txt create mode 100644 vendor/github.com/alicebob/miniredis/v2/fpconv/Makefile create mode 100644 vendor/github.com/alicebob/miniredis/v2/fpconv/README.md create mode 100644 vendor/github.com/alicebob/miniredis/v2/fpconv/dtoa.go create mode 100644 vendor/github.com/alicebob/miniredis/v2/fpconv/fp.go create mode 100644 vendor/github.com/alicebob/miniredis/v2/fpconv/powers.go rename vendor/github.com/alicebob/{ => miniredis/v2}/gopher-json/LICENSE (100%) create mode 100644 vendor/github.com/alicebob/miniredis/v2/gopher-json/README.md rename vendor/github.com/alicebob/{ => miniredis/v2}/gopher-json/json.go (100%) create mode 100644 vendor/github.com/alicebob/miniredis/v2/proto/Makefile create mode 100644 vendor/github.com/alicebob/miniredis/v2/proto/client.go create mode 100644 vendor/github.com/alicebob/miniredis/v2/proto/proto.go create mode 100644 vendor/github.com/alicebob/miniredis/v2/proto/types.go create mode 100644 vendor/github.com/alicebob/miniredis/v2/size/readme.md create mode 100644 vendor/github.com/alicebob/miniredis/v2/size/size.go create mode 100644 vendor/github.com/yuin/gopher-lua/.gitignore delete mode 100644 vendor/github.com/yuin/gopher-lua/.travis.yml diff --git a/go.mod b/go.mod index 02cdb06e3..4e7a9fd05 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ toolchain go1.22.1 require ( github.com/JaderDias/movingmedian v0.0.0-20220813210630-d8c6b6de8835 - github.com/alicebob/miniredis/v2 v2.22.0 + github.com/alicebob/miniredis/v2 v2.35.0 github.com/ansel1/merry v1.8.0 github.com/ansel1/merry/v2 v2.2.1 github.com/barkimedes/go-deepcopy v0.0.0-20220514131651-17c30cfc62df @@ -47,7 +47,6 @@ require ( require ( github.com/aclements/go-moremath v0.0.0-20210112150236-f10218a38794 // indirect - github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect @@ -68,7 +67,7 @@ require ( github.com/spf13/cast v1.6.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.6.0 // indirect - github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 // indirect + github.com/yuin/gopher-lua v1.1.1 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect golang.org/x/sync v0.7.0 // indirect diff --git a/go.sum b/go.sum index 518d2969b..c16666b33 100644 --- a/go.sum +++ b/go.sum @@ -6,10 +6,8 @@ github.com/Shopify/sarama v1.29.0/go.mod h1:2QpgD79wpdAESqNQMxNc0KYMkycd4slxGdV3 github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/aclements/go-moremath v0.0.0-20210112150236-f10218a38794 h1:xlwdaKcTNVW4PtpQb8aKA4Pjy0CdJHEqvFbAnvR5m2g= github.com/aclements/go-moremath v0.0.0-20210112150236-f10218a38794/go.mod h1:7e+I0LQFUI9AXWxOfsQROs9xPhoJtbsyWcjJqDd4KPY= -github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk= -github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= -github.com/alicebob/miniredis/v2 v2.22.0 h1:lIHHiSkEyS1MkKHCHzN+0mWrA4YdbGdimE5iZ2sHSzo= -github.com/alicebob/miniredis/v2 v2.22.0/go.mod h1:XNqvJdQJv5mSuVMc0ynneafpnL/zv52acZ6kqeS0t88= +github.com/alicebob/miniredis/v2 v2.35.0 h1:QwLphYqCEAo1eu1TqPRN2jgVMPBweeQcR21jeqDCONI= +github.com/alicebob/miniredis/v2 v2.35.0/go.mod h1:TcL7YfarKPGDAthEtl5NBeHZfeUQj6OXMm/+iu5cLMM= github.com/ansel1/merry v1.8.0 h1:3RddCV1ubXegKphsodbkmZ4QuROep/ZaPCuwlKuCfFg= github.com/ansel1/merry v1.8.0/go.mod h1:wJVu1mHEtEUWq5zTTX9RiWjcE+xL8y7BGYl2VTYdP7M= github.com/ansel1/merry/v2 v2.2.1 h1:PJpynLFvIpJkn8ZGgNHLq332zIyBc/wTqp3o42ZpWdU= @@ -21,9 +19,6 @@ github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xu github.com/cactus/go-statsd-client/v5 v5.1.0 h1:sbbdfIl9PgisjEoXzvXI1lwUKWElngsjJKaZeC021P4= github.com/cactus/go-statsd-client/v5 v5.1.0/go.mod h1:COEvJ1E+/E2L4q6QE5CkjWPi4eeDw9maJBMIuMPBZbY= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/circonus-labs/gosnowth v1.14.0 h1:+1CF+GByjr2MN6F//eD18LWeppS6BD1h/iaTVaG/shM= github.com/circonus-labs/gosnowth v1.14.0/go.mod h1:s5B8V5q88dmaIs5WerOY51NOU67xhfCMeyk7+I6Mumw= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= @@ -49,8 +44,6 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/evmar/gocairo v0.0.0-20160222165215-ddd30f837497 h1:DIQ8EvZ8OjuPNfcV4NgsyBeZho7WsTD0JEkDM5napMI= -github.com/evmar/gocairo v0.0.0-20160222165215-ddd30f837497/go.mod h1:YXKUYPSqs+jDG8mvexHN2uTik4PKwg2B0WK9itQ0VrE= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= @@ -210,8 +203,8 @@ github.com/xdg/stringprep v1.0.3/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0 github.com/xhhuango/json v1.19.0/go.mod h1:ynZo8WeuBMtTh7LMR1ljdu/8QxceUVbYEcAsPJ7iUb8= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 h1:k/gmLsJDWwWqbLCur2yWnJzwQEKRcAHXo6seXGuSwWw= -github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA= +github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M= +github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= @@ -256,7 +249,6 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/vendor/github.com/alicebob/gopher-json/README.md b/vendor/github.com/alicebob/gopher-json/README.md deleted file mode 100644 index 84fe1bdf9..000000000 --- a/vendor/github.com/alicebob/gopher-json/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# gopher-json [![GoDoc](https://godoc.org/layeh.com/gopher-json?status.svg)](https://godoc.org/layeh.com/gopher-json) - -Package json is a simple JSON encoder/decoder for [gopher-lua](https://github.com/yuin/gopher-lua). - -## License - -Public domain diff --git a/vendor/github.com/alicebob/gopher-json/doc.go b/vendor/github.com/alicebob/gopher-json/doc.go deleted file mode 100644 index b73aeafd5..000000000 --- a/vendor/github.com/alicebob/gopher-json/doc.go +++ /dev/null @@ -1,33 +0,0 @@ -// Package json is a simple JSON encoder/decoder for gopher-lua. -// -// Documentation -// -// The following functions are exposed by the library: -// decode(string): Decodes a JSON string. Returns nil and an error string if -// the string could not be decoded. -// encode(value): Encodes a value into a JSON string. Returns nil and an error -// string if the value could not be encoded. -// -// The following types are supported: -// -// Lua | JSON -// ---------+----- -// nil | null -// number | number -// string | string -// table | object: when table is non-empty and has only string keys -// | array: when table is empty, or has only sequential numeric keys -// | starting from 1 -// -// Attempting to encode any other Lua type will result in an error. -// -// Example -// -// Below is an example usage of the library: -// import ( -// luajson "layeh.com/gopher-json" -// ) -// -// L := lua.NewState() -// luajson.Preload(s) -package json diff --git a/vendor/github.com/alicebob/miniredis/v2/.gitignore b/vendor/github.com/alicebob/miniredis/v2/.gitignore index 7ba06b06c..8016b4be3 100644 --- a/vendor/github.com/alicebob/miniredis/v2/.gitignore +++ b/vendor/github.com/alicebob/miniredis/v2/.gitignore @@ -2,3 +2,5 @@ /integration/dump.rdb *.swp /integration/nodes.conf +.idea/ +miniredis.iml diff --git a/vendor/github.com/alicebob/miniredis/v2/CHANGELOG.md b/vendor/github.com/alicebob/miniredis/v2/CHANGELOG.md index 0c1fb87eb..a475c1bb0 100644 --- a/vendor/github.com/alicebob/miniredis/v2/CHANGELOG.md +++ b/vendor/github.com/alicebob/miniredis/v2/CHANGELOG.md @@ -1,6 +1,118 @@ ## Changelog +## v2.35.0 + +- add Lua redis.setresp({2,3}) +- embed gopher-json package +- fix XAUTOCLAIM (thanks @kgunning) +- fix writeXpending (thanks @gnpaone) +- fix BLMOVE TTL special case +- constants for key types @alyssaruth + + +### v2.34.0 + +- fix ZINTERSTORE where target is one of the source sets +- added support for ZRank and ZRevRank with score (thanks Jeff Howell) +- fix MEMORY subcommand casing (thanks @joshaber) +- use streamCmp in Xtrim (thanks @daniel-cohere) + + +### v2.33.0 + +- minimum Go version is now 1.17 +- fix integer overflow (thanks @wszaranski) +- test against the last BSD redis (7.2.4) +- ignore 'redis.set_repl()' call (thanks @TingluoHuang) +- various build fixes (thanks @wszaranski) +- add StartAddrTLS function (thanks @agriffaut) +- support for the NOMKSTREAM option for XADD (thanks @Jahaja) +- return empty array for SRANDMEMBER on nonexistent key (thanks @WKBae) + + +### v2.32.1 + +- support for SINTERCARD (thanks @s-barr-fetch) +- support for EXPIRETIME and PEXPIRETIME (thanks @wszaranski) +- fix GEO* units to be case insensitive + + +### v2.31.1 + +- support COUNT in SCAN and ZSCAN (thanks @BarakSilverfort) +- support for OBJECT IDLETIME (thanks @nerd2) +- support for HRANDFIELD (thanks @sejin-P) + + +### v2.31.0 + +- support for MEMORY USAGE (thanks @davidroman0O) +- test against Redis 7.2.0 +- support for CLIENT SETNAME/GETNAME (thanks @mr-karan) +- fix very small numbers (thanks @zsh1995) +- use the same float-to-string logic real Redis uses + + +### v2.30.5 + +- support SMISMEMBER (thanks @sandyharvie) + + +### v2.30.4 + +- fix ZADD LT/LG (thanks @sejin-P) +- fix COPY (thanks @jerargus) +- quicker SPOP + + +### v2.30.3 + +- fix lua error_reply (thanks @pkierski) +- fix use of blocking functions in lua +- support for ZMSCORE (thanks @lsgndln) +- lua cache (thanks @tonyhb) + + +### v2.30.2 + +- support MINID in XADD (thanks @nathan-cormier) +- support BLMOVE (thanks @sevein) +- fix COMMAND (thanks @pje) +- fix 'XREAD ... $' on a non-existing stream + + +### v2.30.1 + +- support SET NX GET special case + + +### v2.30.0 + +- implement redis 7.0.x (from 6.X). Main changes: + - test against 7.0.7 + - update error messages + - support nx|xx|gt|lt options in [P]EXPIRE[AT] + - update how deleted items are processed in pending queues in streams + + +### v2.23.1 + +- resolve $ to latest ID in XREAD (thanks @josh-hook) +- handle disconnect in blocking functions (thanks @jgirtakovskis) +- fix type conversion bug in redisToLua (thanks Sandy Harvie) +- BRPOP{LPUSH} timeout can be float since 6.0 + + +### v2.23.0 + +- basic INFO support (thanks @kirill-a-belov) +- support COUNT in SSCAN (thanks @Abdi-dd) +- test and support Go 1.19 +- support LPOS (thanks @ianstarz) +- support XPENDING, XGROUP {CREATECONSUMER,DESTROY,DELCONSUMER}, XINFO {CONSUMERS,GROUPS}, XCLAIM (thanks @sandyharvie) + + ### v2.22.0 - set miniredis.DumpMaxLineLen to get more Dump() info (thanks @afjoseph) @@ -43,7 +155,7 @@ ### v2.16.1 -- fix ZINTERSTORE with wets (thanks @lingjl2010 and @okhowang) +- fix ZINTERSTORE with sets (thanks @lingjl2010 and @okhowang) - fix exclusive ranges in XRANGE (thanks @joseotoro) diff --git a/vendor/github.com/alicebob/miniredis/v2/Makefile b/vendor/github.com/alicebob/miniredis/v2/Makefile index 2aa4cd2c5..2b5ec3eca 100644 --- a/vendor/github.com/alicebob/miniredis/v2/Makefile +++ b/vendor/github.com/alicebob/miniredis/v2/Makefile @@ -1,12 +1,33 @@ -.PHONY: all test testrace int - -all: test - -test: +.PHONY: test +test: ### Run unit tests go test ./... -testrace: +.PHONY: testrace +testrace: ### Run unit tests with race detector go test -race ./... -int: - ${MAKE} -C integration all +.PHONY: int +int: ### Run integration tests (doesn't download redis server) + ${MAKE} -C integration int + +.PHONY: ci +ci: ### Run full tests suite (including download and compilation of proper redis server) + ${MAKE} test + ${MAKE} -C integration redis_src/redis-server int + ${MAKE} testrace + +.PHONY: clean +clean: ### Clean integration test files and remove compiled redis from integration/redis_src + ${MAKE} -C integration clean + +.PHONY: help +help: +ifeq ($(UNAME), Linux) + @grep -P '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | \ + awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' +else + @# this is not tested, but prepared in advance for you, Mac drivers + @awk -F ':.*###' '$$0 ~ FS {printf "%15s%s\n", $$1 ":", $$2}' \ + $(MAKEFILE_LIST) | grep -v '@awk' | sort +endif + diff --git a/vendor/github.com/alicebob/miniredis/v2/README.md b/vendor/github.com/alicebob/miniredis/v2/README.md index 60072f4e0..272362e0e 100644 --- a/vendor/github.com/alicebob/miniredis/v2/README.md +++ b/vendor/github.com/alicebob/miniredis/v2/README.md @@ -39,15 +39,17 @@ Implemented commands: - EXISTS - EXPIRE - EXPIREAT + - EXPIRETIME - KEYS - MOVE - PERSIST - PEXPIRE - PEXPIREAT + - PEXPIRETIME - PTTL + - RANDOMKEY -- see m.Seed(...) - RENAME - RENAMENX - - RANDOMKEY -- see m.Seed(...) - SCAN - TOUCH - TTL @@ -64,6 +66,8 @@ Implemented commands: - FLUSHALL - FLUSHDB - TIME -- returns time.Now() or value set by SetTime() + - COMMAND -- partly + - INFO -- partly, returns only "clients" section with one field "connected_clients" - String keys (complete) - APPEND - BITCOUNT @@ -101,6 +105,7 @@ Implemented commands: - HLEN - HMGET - HMSET + - HRANDFIELD - HSET - HSETNX - HSTRLEN @@ -125,6 +130,7 @@ Implemented commands: - RPUSH - RPUSHX - LMOVE + - BLMOVE - Pub/Sub (complete) - PSUBSCRIBE - PUBLISH @@ -139,20 +145,23 @@ Implemented commands: - SDIFFSTORE - SINTER - SINTERSTORE + - SINTERCARD - SISMEMBER - SMEMBERS + - SMISMEMBER - SMOVE - SPOP -- see m.Seed(...) - SRANDMEMBER -- see m.Seed(...) - SREM + - SSCAN - SUNION - SUNIONSTORE - - SSCAN - Sorted Set keys (complete) - ZADD - ZCARD - ZCOUNT - ZINCRBY + - ZINTER - ZINTERSTORE - ZLEXCOUNT - ZPOPMIN @@ -178,9 +187,15 @@ Implemented commands: - XACK - XADD - XAUTOCLAIM + - XCLAIM - XDEL - XGROUP CREATE + - XGROUP CREATECONSUMER + - XGROUP DESTROY + - XGROUP DELCONSUMER - XINFO STREAM -- partly + - XINFO GROUPS + - XINFO CONSUMERS -- partly - XLEN - XRANGE - XREAD @@ -203,8 +218,6 @@ Implemented commands: - GEORADIUS_RO - GEORADIUSBYMEMBER - GEORADIUSBYMEMBER_RO - - Server - - COMMAND -- partly - Cluster - CLUSTER SLOTS - CLUSTER KEYSLOT @@ -294,6 +307,8 @@ Commands which will probably not be implemented: - ~~RESTORE~~ - ~~WAIT~~ - Scripting + - ~~FCALL / FCALL_RO *~~ + - ~~FUNCTION *~~ - ~~SCRIPT DEBUG~~ - ~~SCRIPT KILL~~ - Server @@ -302,7 +317,6 @@ Commands which will probably not be implemented: - ~~CLIENT *~~ - ~~CONFIG *~~ - ~~DEBUG *~~ - - ~~INFO~~ - ~~LASTSAVE~~ - ~~MONITOR~~ - ~~ROLE~~ @@ -315,7 +329,7 @@ Commands which will probably not be implemented: ## &c. -Integration tests are run against Redis 6.2.6. The [./integration](./integration/) subdir +Integration tests are run against Redis 7.2.4. The [./integration](./integration/) subdir compares miniredis against a real redis instance. The Redis 6 RESP3 protocol is supported. If there are problems, please open @@ -325,5 +339,4 @@ If you want to test Redis Sentinel have a look at [minisentinel](https://github. A changelog is kept at [CHANGELOG.md](https://github.com/alicebob/miniredis/blob/master/CHANGELOG.md). -[![Build Status](https://travis-ci.com/alicebob/miniredis.svg?branch=master)](https://travis-ci.com/alicebob/miniredis) [![Go Reference](https://pkg.go.dev/badge/github.com/alicebob/miniredis/v2.svg)](https://pkg.go.dev/github.com/alicebob/miniredis/v2) diff --git a/vendor/github.com/alicebob/miniredis/v2/cmd_client.go b/vendor/github.com/alicebob/miniredis/v2/cmd_client.go new file mode 100644 index 000000000..ca9fcd9a4 --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/v2/cmd_client.go @@ -0,0 +1,68 @@ +package miniredis + +import ( + "fmt" + "strings" + + "github.com/alicebob/miniredis/v2/server" +) + +// commandsClient handles client operations. +func commandsClient(m *Miniredis) { + m.srv.Register("CLIENT", m.cmdClient) +} + +// CLIENT +func (m *Miniredis) cmdClient(c *server.Peer, cmd string, args []string) { + if len(args) == 0 { + setDirty(c) + c.WriteError("ERR wrong number of arguments for 'client' command") + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + switch cmd := strings.ToUpper(args[0]); cmd { + case "SETNAME": + m.cmdClientSetName(c, args[1:]) + case "GETNAME": + m.cmdClientGetName(c, args[1:]) + default: + setDirty(c) + c.WriteError(fmt.Sprintf("ERR unknown subcommand '%s'. Try CLIENT HELP.", cmd)) + } + }) +} + +// CLIENT SETNAME +func (m *Miniredis) cmdClientSetName(c *server.Peer, args []string) { + if len(args) != 1 { + setDirty(c) + c.WriteError("ERR wrong number of arguments for 'client setname' command") + return + } + + name := args[0] + if strings.ContainsAny(name, " \n") { + setDirty(c) + c.WriteError("ERR Client names cannot contain spaces, newlines or special characters.") + return + + } + c.ClientName = name + c.WriteOK() +} + +// CLIENT GETNAME +func (m *Miniredis) cmdClientGetName(c *server.Peer, args []string) { + if len(args) > 0 { + setDirty(c) + c.WriteError("ERR wrong number of arguments for 'client getname' command") + return + } + + if c.ClientName == "" { + c.WriteNull() + } else { + c.WriteBulk(c.ClientName) + } +} diff --git a/vendor/github.com/alicebob/miniredis/v2/cmd_cluster.go b/vendor/github.com/alicebob/miniredis/v2/cmd_cluster.go index 083c4ecf7..9951f3dd3 100644 --- a/vendor/github.com/alicebob/miniredis/v2/cmd_cluster.go +++ b/vendor/github.com/alicebob/miniredis/v2/cmd_cluster.go @@ -4,13 +4,14 @@ package miniredis import ( "fmt" - "github.com/alicebob/miniredis/v2/server" "strings" + + "github.com/alicebob/miniredis/v2/server" ) // commandsCluster handles some cluster operations. func commandsCluster(m *Miniredis) { - _ = m.srv.Register("CLUSTER", m.cmdCluster) + m.srv.Register("CLUSTER", m.cmdCluster) } func (m *Miniredis) cmdCluster(c *server.Peer, cmd string, args []string) { @@ -51,14 +52,14 @@ func (m *Miniredis) cmdClusterSlots(c *server.Peer, cmd string, args []string) { }) } -//CLUSTER KEYSLOT +// CLUSTER KEYSLOT func (m *Miniredis) cmdClusterKeySlot(c *server.Peer, cmd string, args []string) { withTx(m, c, func(c *server.Peer, ctx *connCtx) { c.WriteInt(163) }) } -//CLUSTER NODES +// CLUSTER NODES func (m *Miniredis) cmdClusterNodes(c *server.Peer, cmd string, args []string) { withTx(m, c, func(c *server.Peer, ctx *connCtx) { c.WriteBulk("e7d1eecce10fd6bb5eb35b9f99a514335d9ba9ca 127.0.0.1:7000@7000 myself,master - 0 0 1 connected 0-16383") diff --git a/vendor/github.com/alicebob/miniredis/v2/cmd_command.go b/vendor/github.com/alicebob/miniredis/v2/cmd_command.go index 59abefd38..8f73b2bac 100644 --- a/vendor/github.com/alicebob/miniredis/v2/cmd_command.go +++ b/vendor/github.com/alicebob/miniredis/v2/cmd_command.go @@ -4,14 +4,10 @@ package miniredis import "github.com/alicebob/miniredis/v2/server" -func commandsCommand(m *Miniredis) { - _ = m.srv.Register("COMMAND", m.cmdCommand) -} - func (m *Miniredis) cmdCommand(c *server.Peer, cmd string, args []string) { // Got from redis 5.0.7 with // echo 'COMMAND' | nc redis_addr redis_port - // + res := "*200\r\n*6\r\n$12\r\nhincrbyfloat\r\n:4\r\n*3\r\n+write\r\n+denyoom\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$10\r\nxreadgroup\r\n:-7\r\n*3\r\n+write\r\n+noscript\r\n+movablekeys\r\n:1\r\n:1\r\n:1\r\n*6\r\n$10\r\nsdiffstore\r\n:-3\r\n*2\r\n+write\r\n+denyoom\r\n:1\r\n:-1\r\n:1\r\n*6\r\n$8\r\nlastsave\r\n:1\r\n*2\r\n+random\r\n+fast\r\n:0\r\n:0\r\n:0\r\n*6\r\n$5\r\nsetnx\r\n:3\r\n*3\r\n+write\r\n+denyoom\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$8\r\nbzpopmax\r\n:-3\r\n*3\r\n+write\r\n+noscript\r\n+fast\r\n:1\r\n:-2\r\n:1\r\n*6\r\n$12\r\npunsubscribe\r\n:-1\r\n*4\r\n+pubsub\r\n+noscript\r\n+loading\r\n+stale\r\n:0\r\n:0\r\n:0\r\n*6\r\n$4\r\nxack\r\n:-4\r\n*2\r\n+write\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$10\r\npfselftest\r\n:1\r\n*1\r\n+admin\r\n:0\r\n:0\r\n:0\r\n*6\r\n$6\r\nsubstr\r\n:4\r\n*1\r\n+readonly\r\n:1\r\n:1\r\n:1\r\n*6\r\n$8\r\nsmembers\r\n:2\r\n*2\r\n+readonly\r\n+sort_for_script\r\n:1\r\n:1\r\n:1\r\n*6\r\n$11\r\nunsubscribe\r\n:-1\r\n*4\r\n+pubsub\r\n+noscript\r\n+loading\r\n+stale\r\n:0\r\n:0\r\n:0\r\n*6\r\n$11\r\nzinterstore\r\n:-4\r\n*3\r\n+write\r\n+denyoom\r\n+movablekeys\r\n:0\r\n:0\r\n:0\r\n*6\r\n$6\r\nstrlen\r\n:2\r\n*2\r\n+readonly\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$7\r\npfmerge\r\n:-2\r\n*2\r\n+write\r\n+denyoom\r\n:1\r\n:-1\r\n:1\r\n*6\r\n$9\r\nrandomkey\r\n:1\r\n*2\r\n+readonly\r\n+random\r\n:0\r\n:0\r\n:0\r\n*6\r\n$6\r\nlolwut\r\n:-1\r\n*1\r\n+readonly\r\n:0\r\n:0\r\n:0\r\n*6\r\n$4\r\nrpop\r\n:2\r\n*2\r\n+write\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$5\r\nhkeys\r\n:2\r\n*2\r\n+readonly\r\n+sort_for_script\r\n:1\r\n:1\r\n:1\r\n*6\r\n$6\r\nclient\r\n:-2\r\n*2\r\n+admin\r\n+noscript\r\n:0\r\n:0\r\n:0\r\n*6\r\n$6\r\nmodule\r\n:-2\r\n*2\r\n+admin\r\n+noscript\r\n:0\r\n:0\r\n:0\r\n*6\r\n$7\r\nslowlog\r\n:-2\r\n*2\r\n+admin\r\n+random\r\n:0\r\n:0\r\n:0\r\n*6\r\n$7\r\ngeohash\r\n:-2\r\n*1\r\n+readonly\r\n:1\r\n:1\r\n:1\r\n*6\r\n$6\r\nlrange\r\n:4\r\n*1\r\n+readonly\r\n:1\r\n:1\r\n:1\r\n*6\r\n$4\r\nping\r\n:-1\r\n*2\r\n+stale\r\n+fast\r\n:0\r\n:0\r\n:0\r\n*6\r\n$8\r\nbitcount\r\n:-2\r\n*1\r\n+readonly\r\n:1\r\n:1\r\n:1\r\n*6\r\n$6\r\npubsub\r\n:-2\r\n*4\r\n+pubsub\r\n+random\r\n+loading\r\n+stale\r\n:0\r\n:0\r\n:0\r\n*6\r\n$4\r\nrole\r\n:1\r\n*3\r\n+noscript\r\n+loading\r\n+stale\r\n:0\r\n:0\r\n:0\r\n*6\r\n$4\r\nhget\r\n:3\r\n*2\r\n+readonly\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$6\r\nobject\r\n:-2\r\n*2\r\n+readonly\r\n+random\r\n:2\r\n:2\r\n:1\r\n*6\r\n$9\r\nzrevrange\r\n:-4\r\n*1\r\n+readonly\r\n:1\r\n:1\r\n:1\r\n*6\r\n$7\r\nhincrby\r\n:4\r\n*3\r\n+write\r\n+denyoom\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$9\r\nzlexcount\r\n:4\r\n*2\r\n+readonly\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$5\r\nscard\r\n:2\r\n*2\r\n+readonly\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$6\r\nappend\r\n:3\r\n*2\r\n+write\r\n+denyoom\r\n:1\r\n:1\r\n:1\r\n*6\r\n$7\r\nhstrlen\r\n:3\r\n*2\r\n+readonly\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$6\r\nconfig\r\n:-2\r\n*4\r\n+admin\r\n+noscript\r\n+loading\r\n+stale\r\n:0\r\n:0\r\n:0\r\n*6\r\n$4\r\nhset\r\n:-4\r\n*3\r\n+write\r\n+denyoom\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$16\r\nzrevrangebyscore\r\n:-4\r\n*1\r\n+readonly\r\n:1\r\n:1\r\n:1\r\n*6\r\n$4\r\nincr\r\n:2\r\n*3\r\n+write\r\n+denyoom\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$6\r\nsetbit\r\n:4\r\n*2\r\n+write\r\n+denyoom\r\n:1\r\n:1\r\n:1\r\n*6\r\n$9\r\nrpoplpush\r\n:3\r\n*2\r\n+write\r\n+denyoom\r\n:1\r\n:2\r\n:1\r\n*6\r\n$6\r\nxclaim\r\n:-6\r\n*3\r\n+write\r\n+random\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$11\r\nsinterstore\r\n:-3\r\n*2\r\n+write\r\n+denyoom\r\n:1\r\n:-1\r\n:1\r\n*6\r\n$7\r\npublish\r\n:3\r\n*4\r\n+pubsub\r\n+loading\r\n+stale\r\n+fast\r\n:0\r\n:0\r\n:0\r\n*6\r\n$5\r\nhscan\r\n:-3\r\n*2\r\n+readonly\r\n+random\r\n:1\r\n:1\r\n:1\r\n*6\r\n$5\r\nmulti\r\n:1\r\n*2\r\n+noscript\r\n+fast\r\n:0\r\n:0\r\n:0\r\n*6\r\n$3\r\nset\r\n:-3\r\n*2\r\n+write\r\n+denyoom\r\n:1\r\n:1\r\n:1\r\n*6\r\n$6\r\nlpushx\r\n:-3\r\n*3\r\n+write\r\n+denyoom\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$16\r\nzremrangebyscore\r\n:4\r\n*1\r\n+write\r\n:1\r\n:1\r\n:1\r\n*6\r\n$9\r\npexpireat\r\n:3\r\n*2\r\n+write\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$4\r\nhdel\r\n:-3\r\n*2\r\n+write\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$12\r\nbgrewriteaof\r\n:1\r\n*2\r\n+admin\r\n+noscript\r\n:0\r\n:0\r\n:0\r\n*6\r\n$7\r\nmigrate\r\n:-6\r\n*3\r\n+write\r\n+random\r\n+movablekeys\r\n:0\r\n:0\r\n:0\r\n*6\r\n$9\r\nreplicaof\r\n:3\r\n*3\r\n+admin\r\n+noscript\r\n+stale\r\n:0\r\n:0\r\n:0\r\n*6\r\n$5\r\ntouch\r\n:-2\r\n*2\r\n+readonly\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$6\r\nxsetid\r\n:3\r\n*3\r\n+write\r\n+denyoom\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$5\r\nbitop\r\n:-4\r\n*2\r\n+write\r\n+denyoom\r\n:2\r\n:-1\r\n:1\r\n*6\r\n$6\r\nswapdb\r\n:3\r\n*2\r\n+write\r\n+fast\r\n:0\r\n:0\r\n:0\r\n*6\r\n$5\r\nsdiff\r\n:-2\r\n*2\r\n+readonly\r\n+sort_for_script\r\n:1\r\n:-1\r\n:1\r\n*6\r\n$6\r\nlindex\r\n:3\r\n*1\r\n+readonly\r\n:1\r\n:1\r\n:1\r\n*6\r\n$4\r\nwait\r\n:3\r\n*1\r\n+noscript\r\n:0\r\n:0\r\n:0\r\n*6\r\n$4\r\nlrem\r\n:4\r\n*1\r\n+write\r\n:1\r\n:1\r\n:1\r\n*6\r\n$6\r\nhsetnx\r\n:4\r\n*3\r\n+write\r\n+denyoom\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$8\r\ngetrange\r\n:4\r\n*1\r\n+readonly\r\n:1\r\n:1\r\n:1\r\n*6\r\n$4\r\nhlen\r\n:2\r\n*2\r\n+readonly\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$4\r\npost\r\n:-1\r\n*2\r\n+loading\r\n+stale\r\n:0\r\n:0\r\n:0\r\n*6\r\n$9\r\nsismember\r\n:3\r\n*2\r\n+readonly\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$7\r\nunwatch\r\n:1\r\n*2\r\n+noscript\r\n+fast\r\n:0\r\n:0\r\n:0\r\n*6\r\n$5\r\nlpush\r\n:-3\r\n*3\r\n+write\r\n+denyoom\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$4\r\nscan\r\n:-2\r\n*2\r\n+readonly\r\n+random\r\n:0\r\n:0\r\n:0\r\n*6\r\n$5\r\nsmove\r\n:4\r\n*2\r\n+write\r\n+fast\r\n:1\r\n:2\r\n:1\r\n*6\r\n$7\r\ncluster\r\n:-2\r\n*1\r\n+admin\r\n:0\r\n:0\r\n:0\r\n*6\r\n$6\r\nbgsave\r\n:-1\r\n*2\r\n+admin\r\n+noscript\r\n:0\r\n:0\r\n:0\r\n*6\r\n$4\r\ndump\r\n:2\r\n*2\r\n+readonly\r\n+random\r\n:1\r\n:1\r\n:1\r\n*6\r\n$7\r\nlatency\r\n:-2\r\n*4\r\n+admin\r\n+noscript\r\n+loading\r\n+stale\r\n:0\r\n:0\r\n:0\r\n*6\r\n$8\r\nbzpopmin\r\n:-3\r\n*3\r\n+write\r\n+noscript\r\n+fast\r\n:1\r\n:-2\r\n:1\r\n*6\r\n$6\r\ngetbit\r\n:3\r\n*2\r\n+readonly\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$7\r\nhgetall\r\n:2\r\n*2\r\n+readonly\r\n+random\r\n:1\r\n:1\r\n:1\r\n*6\r\n$6\r\nrename\r\n:3\r\n*1\r\n+write\r\n:1\r\n:2\r\n:1\r\n*6\r\n$9\r\nsubscribe\r\n:-2\r\n*4\r\n+pubsub\r\n+noscript\r\n+loading\r\n+stale\r\n:0\r\n:0\r\n:0\r\n*6\r\n$4\r\nxdel\r\n:-3\r\n*2\r\n+write\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$15\r\nzremrangebyrank\r\n:4\r\n*1\r\n+write\r\n:1\r\n:1\r\n:1\r\n*6\r\n$4\r\ntype\r\n:2\r\n*2\r\n+readonly\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$6\r\nscript\r\n:-2\r\n*1\r\n+noscript\r\n:0\r\n:0\r\n:0\r\n*6\r\n$5\r\nhmset\r\n:-4\r\n*3\r\n+write\r\n+denyoom\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$6\r\nsunion\r\n:-2\r\n*2\r\n+readonly\r\n+sort_for_script\r\n:1\r\n:-1\r\n:1\r\n*6\r\n$4\r\nmget\r\n:-2\r\n*2\r\n+readonly\r\n+fast\r\n:1\r\n:-1\r\n:1\r\n*6\r\n$10\r\nbrpoplpush\r\n:4\r\n*3\r\n+write\r\n+denyoom\r\n+noscript\r\n:1\r\n:2\r\n:1\r\n*6\r\n$6\r\ngeoadd\r\n:-5\r\n*2\r\n+write\r\n+denyoom\r\n:1\r\n:1\r\n:1\r\n*6\r\n$6\r\ndecrby\r\n:3\r\n*3\r\n+write\r\n+denyoom\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$4\r\necho\r\n:2\r\n*1\r\n+fast\r\n:0\r\n:0\r\n:0\r\n*6\r\n$6\r\ndbsize\r\n:1\r\n*2\r\n+readonly\r\n+fast\r\n:0\r\n:0\r\n:0\r\n*6\r\n$5\r\nzcard\r\n:2\r\n*2\r\n+readonly\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$6\r\nselect\r\n:2\r\n*2\r\n+loading\r\n+fast\r\n:0\r\n:0\r\n:0\r\n*6\r\n$4\r\nsadd\r\n:-3\r\n*3\r\n+write\r\n+denyoom\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$5\r\nhost:\r\n:-1\r\n*2\r\n+loading\r\n+stale\r\n:0\r\n:0\r\n:0\r\n*6\r\n$5\r\nsscan\r\n:-3\r\n*2\r\n+readonly\r\n+random\r\n:1\r\n:1\r\n:1\r\n*6\r\n$12\r\ngeoradius_ro\r\n:-6\r\n*2\r\n+readonly\r\n+movablekeys\r\n:1\r\n:1\r\n:1\r\n*6\r\n$7\r\nmonitor\r\n:1\r\n*2\r\n+admin\r\n+noscript\r\n:0\r\n:0\r\n:0\r\n*6\r\n$14\r\nzremrangebylex\r\n:4\r\n*1\r\n+write\r\n:1\r\n:1\r\n:1\r\n*6\r\n$11\r\nsunionstore\r\n:-3\r\n*2\r\n+write\r\n+denyoom\r\n:1\r\n:-1\r\n:1\r\n*6\r\n$5\r\nzscan\r\n:-3\r\n*2\r\n+readonly\r\n+random\r\n:1\r\n:1\r\n:1\r\n*6\r\n$9\r\nreadwrite\r\n:1\r\n*1\r\n+fast\r\n:0\r\n:0\r\n:0\r\n*6\r\n$6\r\nxgroup\r\n:-2\r\n*2\r\n+write\r\n+denyoom\r\n:2\r\n:2\r\n:1\r\n*6\r\n$5\r\nsetex\r\n:4\r\n*2\r\n+write\r\n+denyoom\r\n:1\r\n:1\r\n:1\r\n*6\r\n$4\r\nsave\r\n:1\r\n*2\r\n+admin\r\n+noscript\r\n:0\r\n:0\r\n:0\r\n*6\r\n$5\r\nhvals\r\n:2\r\n*2\r\n+readonly\r\n+sort_for_script\r\n:1\r\n:1\r\n:1\r\n*6\r\n$5\r\nwatch\r\n:-2\r\n*2\r\n+noscript\r\n+fast\r\n:1\r\n:-1\r\n:1\r\n*6\r\n$7\r\nhexists\r\n:3\r\n*2\r\n+readonly\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$4\r\ninfo\r\n:-1\r\n*3\r\n+random\r\n+loading\r\n+stale\r\n:0\r\n:0\r\n:0\r\n*6\r\n$5\r\npsync\r\n:3\r\n*3\r\n+readonly\r\n+admin\r\n+noscript\r\n:0\r\n:0\r\n:0\r\n*6\r\n$11\r\nzrangebylex\r\n:-4\r\n*1\r\n+readonly\r\n:1\r\n:1\r\n:1\r\n*6\r\n$4\r\nzadd\r\n:-4\r\n*3\r\n+write\r\n+denyoom\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$4\r\nxlen\r\n:2\r\n*2\r\n+readonly\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$4\r\nauth\r\n:2\r\n*4\r\n+noscript\r\n+loading\r\n+stale\r\n+fast\r\n:0\r\n:0\r\n:0\r\n*6\r\n$4\r\nsrem\r\n:-3\r\n*2\r\n+write\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$9\r\ngeoradius\r\n:-6\r\n*2\r\n+write\r\n+movablekeys\r\n:1\r\n:1\r\n:1\r\n*6\r\n$4\r\nexec\r\n:1\r\n*2\r\n+noscript\r\n+skip_monitor\r\n:0\r\n:0\r\n:0\r\n*6\r\n$7\r\npfcount\r\n:-2\r\n*1\r\n+readonly\r\n:1\r\n:-1\r\n:1\r\n*6\r\n$7\r\nzpopmin\r\n:-2\r\n*2\r\n+write\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$4\r\nmove\r\n:3\r\n*2\r\n+write\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$5\r\nxtrim\r\n:-2\r\n*3\r\n+write\r\n+random\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$6\r\nasking\r\n:1\r\n*1\r\n+fast\r\n:0\r\n:0\r\n:0\r\n*6\r\n$4\r\npttl\r\n:2\r\n*3\r\n+readonly\r\n+random\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$11\r\nsrandmember\r\n:-2\r\n*2\r\n+readonly\r\n+random\r\n:1\r\n:1\r\n:1\r\n*6\r\n$8\r\nflushall\r\n:-1\r\n*1\r\n+write\r\n:0\r\n:0\r\n:0\r\n*6\r\n$4\r\nsort\r\n:-2\r\n*3\r\n+write\r\n+denyoom\r\n+movablekeys\r\n:1\r\n:1\r\n:1\r\n*6\r\n$3\r\ndel\r\n:-2\r\n*1\r\n+write\r\n:1\r\n:-1\r\n:1\r\n*6\r\n$14\r\nrestore-asking\r\n:-4\r\n*3\r\n+write\r\n+denyoom\r\n+asking\r\n:1\r\n:1\r\n:1\r\n*6\r\n$10\r\npsubscribe\r\n:-2\r\n*4\r\n+pubsub\r\n+noscript\r\n+loading\r\n+stale\r\n:0\r\n:0\r\n:0\r\n*6\r\n$4\r\ndecr\r\n:2\r\n*3\r\n+write\r\n+denyoom\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$6\r\nincrby\r\n:3\r\n*3\r\n+write\r\n+denyoom\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$14\r\nzrevrangebylex\r\n:-4\r\n*1\r\n+readonly\r\n:1\r\n:1\r\n:1\r\n*6\r\n$8\r\nbitfield\r\n:-2\r\n*2\r\n+write\r\n+denyoom\r\n:1\r\n:1\r\n:1\r\n*6\r\n$6\r\nexists\r\n:-2\r\n*2\r\n+readonly\r\n+fast\r\n:1\r\n:-1\r\n:1\r\n*6\r\n$8\r\nreplconf\r\n:-1\r\n*4\r\n+admin\r\n+noscript\r\n+loading\r\n+stale\r\n:0\r\n:0\r\n:0\r\n*6\r\n$7\r\nzincrby\r\n:4\r\n*3\r\n+write\r\n+denyoom\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$5\r\nblpop\r\n:-3\r\n*2\r\n+write\r\n+noscript\r\n:1\r\n:-2\r\n:1\r\n*6\r\n$4\r\nlpop\r\n:2\r\n*2\r\n+write\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$3\r\nttl\r\n:2\r\n*3\r\n+readonly\r\n+random\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$5\r\nxread\r\n:-4\r\n*3\r\n+readonly\r\n+noscript\r\n+movablekeys\r\n:1\r\n:1\r\n:1\r\n*6\r\n$5\r\nrpush\r\n:-3\r\n*3\r\n+write\r\n+denyoom\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$8\r\nzrevrank\r\n:3\r\n*2\r\n+readonly\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$11\r\nincrbyfloat\r\n:3\r\n*3\r\n+write\r\n+denyoom\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$5\r\nbrpop\r\n:-3\r\n*2\r\n+write\r\n+noscript\r\n:1\r\n:-2\r\n:1\r\n*6\r\n$4\r\nxadd\r\n:-5\r\n*4\r\n+write\r\n+denyoom\r\n+random\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$8\r\nsetrange\r\n:4\r\n*2\r\n+write\r\n+denyoom\r\n:1\r\n:1\r\n:1\r\n*6\r\n$17\r\ngeoradiusbymember\r\n:-5\r\n*2\r\n+write\r\n+movablekeys\r\n:1\r\n:1\r\n:1\r\n*6\r\n$6\r\nunlink\r\n:-2\r\n*2\r\n+write\r\n+fast\r\n:1\r\n:-1\r\n:1\r\n*6\r\n$8\r\nexpireat\r\n:3\r\n*2\r\n+write\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$5\r\ndebug\r\n:-2\r\n*2\r\n+admin\r\n+noscript\r\n:0\r\n:0\r\n:0\r\n*6\r\n$20\r\ngeoradiusbymember_ro\r\n:-5\r\n*2\r\n+readonly\r\n+movablekeys\r\n:1\r\n:1\r\n:1\r\n*6\r\n$4\r\nlset\r\n:4\r\n*2\r\n+write\r\n+denyoom\r\n:1\r\n:1\r\n:1\r\n*6\r\n$6\r\nzscore\r\n:3\r\n*2\r\n+readonly\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$4\r\nllen\r\n:2\r\n*2\r\n+readonly\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$4\r\ntime\r\n:1\r\n*2\r\n+random\r\n+fast\r\n:0\r\n:0\r\n:0\r\n*6\r\n$8\r\nshutdown\r\n:-1\r\n*4\r\n+admin\r\n+noscript\r\n+loading\r\n+stale\r\n:0\r\n:0\r\n:0\r\n*6\r\n$7\r\nevalsha\r\n:-3\r\n*2\r\n+noscript\r\n+movablekeys\r\n:0\r\n:0\r\n:0\r\n*6\r\n$6\r\nzcount\r\n:4\r\n*2\r\n+readonly\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$6\r\nmemory\r\n:-2\r\n*2\r\n+readonly\r\n+random\r\n:0\r\n:0\r\n:0\r\n*6\r\n$5\r\nxinfo\r\n:-2\r\n*2\r\n+readonly\r\n+random\r\n:2\r\n:2\r\n:1\r\n*6\r\n$8\r\nxpending\r\n:-3\r\n*2\r\n+readonly\r\n+random\r\n:1\r\n:1\r\n:1\r\n*6\r\n$4\r\neval\r\n:-3\r\n*2\r\n+noscript\r\n+movablekeys\r\n:0\r\n:0\r\n:0\r\n*6\r\n$6\r\nxrange\r\n:-4\r\n*1\r\n+readonly\r\n:1\r\n:1\r\n:1\r\n*6\r\n$7\r\nrestore\r\n:-4\r\n*2\r\n+write\r\n+denyoom\r\n:1\r\n:1\r\n:1\r\n*6\r\n$7\r\nzpopmax\r\n:-2\r\n*2\r\n+write\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$4\r\nmset\r\n:-3\r\n*2\r\n+write\r\n+denyoom\r\n:1\r\n:-1\r\n:2\r\n*6\r\n$4\r\nspop\r\n:-2\r\n*3\r\n+write\r\n+random\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$5\r\nltrim\r\n:4\r\n*1\r\n+write\r\n:1\r\n:1\r\n:1\r\n*6\r\n$5\r\nzrank\r\n:3\r\n*2\r\n+readonly\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$9\r\nxrevrange\r\n:-4\r\n*1\r\n+readonly\r\n:1\r\n:1\r\n:1\r\n*6\r\n$3\r\nget\r\n:2\r\n*2\r\n+readonly\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$7\r\nflushdb\r\n:-1\r\n*1\r\n+write\r\n:0\r\n:0\r\n:0\r\n*6\r\n$5\r\nhmget\r\n:-3\r\n*2\r\n+readonly\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$6\r\nmsetnx\r\n:-3\r\n*2\r\n+write\r\n+denyoom\r\n:1\r\n:-1\r\n:2\r\n*6\r\n$7\r\npersist\r\n:2\r\n*2\r\n+write\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$11\r\nzunionstore\r\n:-4\r\n*3\r\n+write\r\n+denyoom\r\n+movablekeys\r\n:0\r\n:0\r\n:0\r\n*6\r\n$7\r\ncommand\r\n:0\r\n*3\r\n+random\r\n+loading\r\n+stale\r\n:0\r\n:0\r\n:0\r\n*6\r\n$8\r\nrenamenx\r\n:3\r\n*2\r\n+write\r\n+fast\r\n:1\r\n:2\r\n:1\r\n*6\r\n$6\r\nzrange\r\n:-4\r\n*1\r\n+readonly\r\n:1\r\n:1\r\n:1\r\n*6\r\n$7\r\npexpire\r\n:3\r\n*2\r\n+write\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$4\r\nkeys\r\n:2\r\n*2\r\n+readonly\r\n+sort_for_script\r\n:0\r\n:0\r\n:0\r\n*6\r\n$4\r\nzrem\r\n:-3\r\n*2\r\n+write\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$5\r\npfadd\r\n:-2\r\n*3\r\n+write\r\n+denyoom\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$6\r\npsetex\r\n:4\r\n*2\r\n+write\r\n+denyoom\r\n:1\r\n:1\r\n:1\r\n*6\r\n$13\r\nzrangebyscore\r\n:-4\r\n*1\r\n+readonly\r\n:1\r\n:1\r\n:1\r\n*6\r\n$4\r\nsync\r\n:1\r\n*3\r\n+readonly\r\n+admin\r\n+noscript\r\n:0\r\n:0\r\n:0\r\n*6\r\n$7\r\npfdebug\r\n:-3\r\n*1\r\n+write\r\n:0\r\n:0\r\n:0\r\n*6\r\n$7\r\ndiscard\r\n:1\r\n*2\r\n+noscript\r\n+fast\r\n:0\r\n:0\r\n:0\r\n*6\r\n$8\r\nreadonly\r\n:1\r\n*1\r\n+fast\r\n:0\r\n:0\r\n:0\r\n*6\r\n$7\r\ngeodist\r\n:-4\r\n*1\r\n+readonly\r\n:1\r\n:1\r\n:1\r\n*6\r\n$6\r\ngeopos\r\n:-2\r\n*1\r\n+readonly\r\n:1\r\n:1\r\n:1\r\n*6\r\n$6\r\nbitpos\r\n:-3\r\n*1\r\n+readonly\r\n:1\r\n:1\r\n:1\r\n*6\r\n$6\r\nsinter\r\n:-2\r\n*2\r\n+readonly\r\n+sort_for_script\r\n:1\r\n:-1\r\n:1\r\n*6\r\n$6\r\ngetset\r\n:3\r\n*2\r\n+write\r\n+denyoom\r\n:1\r\n:1\r\n:1\r\n*6\r\n$7\r\nslaveof\r\n:3\r\n*3\r\n+admin\r\n+noscript\r\n+stale\r\n:0\r\n:0\r\n:0\r\n*6\r\n$6\r\nrpushx\r\n:-3\r\n*3\r\n+write\r\n+denyoom\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*6\r\n$7\r\nlinsert\r\n:5\r\n*2\r\n+write\r\n+denyoom\r\n:1\r\n:1\r\n:1\r\n*6\r\n$6\r\nexpire\r\n:3\r\n*2\r\n+write\r\n+fast\r\n:1\r\n:1\r\n:1\r\n" c.WriteRaw(res) diff --git a/vendor/github.com/alicebob/miniredis/v2/cmd_connection.go b/vendor/github.com/alicebob/miniredis/v2/cmd_connection.go index defbbccab..1afb5cea1 100644 --- a/vendor/github.com/alicebob/miniredis/v2/cmd_connection.go +++ b/vendor/github.com/alicebob/miniredis/v2/cmd_connection.go @@ -4,7 +4,6 @@ package miniredis import ( "fmt" - "strconv" "strings" "github.com/alicebob/miniredis/v2/server" @@ -71,27 +70,34 @@ func (m *Miniredis) cmdAuth(c *server.Peer, cmd string, args []string) { if m.checkPubsub(c, cmd) { return } - if getCtx(c).nested { - c.WriteError(msgNotFromScripts) + ctx := getCtx(c) + if ctx.nested { + c.WriteError(msgNotFromScripts(ctx.nestedSHA)) return } - username := "default" - pw := args[0] + + var opts = struct { + username string + password string + }{ + username: "default", + password: args[0], + } if len(args) == 2 { - username, pw = args[0], args[1] + opts.username, opts.password = args[0], args[1] } withTx(m, c, func(c *server.Peer, ctx *connCtx) { - if len(m.passwords) == 0 && username == "default" { + if len(m.passwords) == 0 && opts.username == "default" { c.WriteError("ERR AUTH called without any password configured for the default user. Are you sure your configuration is correct?") return } - setPW, ok := m.passwords[username] + setPW, ok := m.passwords[opts.username] if !ok { c.WriteError("WRONGPASS invalid username-password pair") return } - if setPW != pw { + if setPW != opts.password { c.WriteError("WRONGPASS invalid username-password pair") return } @@ -109,17 +115,16 @@ func (m *Miniredis) cmdHello(c *server.Peer, cmd string, args []string) { } var opts struct { - version int - username, password string + version int + username string + password string } - versionArg, args := args[0], args[1:] - var err error - opts.version, err = strconv.Atoi(versionArg) - if err != nil { - c.WriteError("ERR Protocol version is not an integer or out of range") + if ok := optIntErr(c, args[0], &opts.version, "ERR Protocol version is not an integer or out of range"); !ok { return } + args = args[1:] + switch opts.version { case 2, 3: default: @@ -199,8 +204,9 @@ func (m *Miniredis) cmdEcho(c *server.Peer, cmd string, args []string) { return } + msg := args[0] + withTx(m, c, func(c *server.Peer, ctx *connCtx) { - msg := args[0] c.WriteBulk(msg) }) } @@ -212,27 +218,25 @@ func (m *Miniredis) cmdSelect(c *server.Peer, cmd string, args []string) { c.WriteError(errWrongNumber(cmd)) return } - if !m.handleAuth(c) { + if !m.isValidCMD(c, cmd) { return } - if m.checkPubsub(c, cmd) { + + var opts struct { + id int + } + if ok := optInt(c, args[0], &opts.id); !ok { return } withTx(m, c, func(c *server.Peer, ctx *connCtx) { - id, err := strconv.Atoi(args[0]) - if err != nil { - c.WriteError(msgInvalidInt) - setDirty(c) - return - } - if id < 0 { + if opts.id < 0 { c.WriteError(msgDBIndexOutOfRange) setDirty(c) return } - ctx.selectedDB = id + ctx.selectedDB = opts.id c.WriteOK() }) } @@ -248,26 +252,26 @@ func (m *Miniredis) cmdSwapdb(c *server.Peer, cmd string, args []string) { return } + var opts struct { + id1 int + id2 int + } + + if ok := optIntErr(c, args[0], &opts.id1, "ERR invalid first DB index"); !ok { + return + } + if ok := optIntErr(c, args[1], &opts.id2, "ERR invalid second DB index"); !ok { + return + } + withTx(m, c, func(c *server.Peer, ctx *connCtx) { - id1, err := strconv.Atoi(args[0]) - if err != nil { - c.WriteError("ERR invalid first DB index") - setDirty(c) - return - } - id2, err := strconv.Atoi(args[1]) - if err != nil { - c.WriteError("ERR invalid second DB index") - setDirty(c) - return - } - if id1 < 0 || id2 < 0 { + if opts.id1 < 0 || opts.id2 < 0 { c.WriteError(msgDBIndexOutOfRange) setDirty(c) return } - m.swapDB(id1, id2) + m.swapDB(opts.id1, opts.id2) c.WriteOK() }) diff --git a/vendor/github.com/alicebob/miniredis/v2/cmd_generic.go b/vendor/github.com/alicebob/miniredis/v2/cmd_generic.go index f9f06bfb9..721ad2fab 100644 --- a/vendor/github.com/alicebob/miniredis/v2/cmd_generic.go +++ b/vendor/github.com/alicebob/miniredis/v2/cmd_generic.go @@ -3,6 +3,8 @@ package miniredis import ( + "errors" + "fmt" "sort" "strconv" "strings" @@ -11,14 +13,31 @@ import ( "github.com/alicebob/miniredis/v2/server" ) +const ( + // expiretimeReplyNoExpiration is return value for EXPIRETIME and PEXPIRETIME if the key exists but has no associated expiration time + expiretimeReplyNoExpiration = -1 + // expiretimeReplyMissingKey is return value for EXPIRETIME and PEXPIRETIME if the key does not exist + expiretimeReplyMissingKey = -2 +) + +func inSeconds(t time.Time) int { + return int(t.Unix()) +} + +func inMilliSeconds(t time.Time) int { + return int(t.UnixMilli()) +} + // commandsGeneric handles EXPIRE, TTL, PERSIST, &c. func commandsGeneric(m *Miniredis) { + m.srv.Register("COPY", m.cmdCopy) m.srv.Register("DEL", m.cmdDel) - m.srv.Register("UNLINK", m.cmdDel) // DUMP m.srv.Register("EXISTS", m.cmdExists) m.srv.Register("EXPIRE", makeCmdExpire(m, false, time.Second)) m.srv.Register("EXPIREAT", makeCmdExpire(m, true, time.Second)) + m.srv.Register("EXPIRETIME", m.makeCmdExpireTime(inSeconds)) + m.srv.Register("PEXPIRETIME", m.makeCmdExpireTime(inMilliSeconds)) m.srv.Register("KEYS", m.cmdKeys) // MIGRATE m.srv.Register("MOVE", m.cmdMove) @@ -31,12 +50,53 @@ func commandsGeneric(m *Miniredis) { m.srv.Register("RENAME", m.cmdRename) m.srv.Register("RENAMENX", m.cmdRenamenx) // RESTORE - // SORT m.srv.Register("TOUCH", m.cmdTouch) m.srv.Register("TTL", m.cmdTTL) m.srv.Register("TYPE", m.cmdType) m.srv.Register("SCAN", m.cmdScan) - m.srv.Register("COPY", m.cmdCopy) + // SORT + m.srv.Register("UNLINK", m.cmdDel) +} + +type expireOpts struct { + key string + value int + nx bool + xx bool + gt bool + lt bool +} + +func expireParse(cmd string, args []string) (*expireOpts, error) { + var opts expireOpts + + opts.key = args[0] + if err := optIntSimple(args[1], &opts.value); err != nil { + return nil, err + } + args = args[2:] + for len(args) > 0 { + switch strings.ToLower(args[0]) { + case "nx": + opts.nx = true + case "xx": + opts.xx = true + case "gt": + opts.gt = true + case "lt": + opts.lt = true + default: + return nil, fmt.Errorf("ERR Unsupported option %s", args[0]) + } + args = args[1:] + } + if opts.gt && opts.lt { + return nil, errors.New("ERR GT and LT options at the same time are not compatible") + } + if opts.nx && (opts.xx || opts.gt || opts.lt) { + return nil, errors.New("ERR NX and XX, GT or LT options at the same time are not compatible") + } + return &opts, nil } // generic expire command for EXPIRE, PEXPIRE, EXPIREAT, PEXPIREAT @@ -44,7 +104,7 @@ func commandsGeneric(m *Miniredis) { // converted to a duration. func makeCmdExpire(m *Miniredis, unix bool, d time.Duration) func(*server.Peer, string, []string) { return func(c *server.Peer, cmd string, args []string) { - if len(args) != 2 { + if len(args) < 2 { setDirty(c) c.WriteError(errWrongNumber(cmd)) return @@ -56,12 +116,10 @@ func makeCmdExpire(m *Miniredis, unix bool, d time.Duration) func(*server.Peer, return } - key := args[0] - value := args[1] - i, err := strconv.Atoi(value) + opts, err := expireParse(cmd, args) if err != nil { setDirty(c) - c.WriteError(msgInvalidInt) + c.WriteError(err.Error()) return } @@ -69,22 +127,90 @@ func makeCmdExpire(m *Miniredis, unix bool, d time.Duration) func(*server.Peer, db := m.db(ctx.selectedDB) // Key must be present. - if _, ok := db.keys[key]; !ok { + if _, ok := db.keys[opts.key]; !ok { c.WriteInt(0) return } + + oldTTL, ok := db.ttl[opts.key] + + var newTTL time.Duration if unix { - db.ttl[key] = m.at(i, d) + newTTL = m.at(opts.value, d) } else { - db.ttl[key] = time.Duration(i) * d + newTTL = time.Duration(opts.value) * d + } + + // > NX -- Set expiry only when the key has no expiry + if opts.nx && ok { + c.WriteInt(0) + return } - db.keyVersion[key]++ - db.checkTTL(key) + // > XX -- Set expiry only when the key has an existing expiry + if opts.xx && !ok { + c.WriteInt(0) + return + } + // > GT -- Set expiry only when the new expiry is greater than current one + // (no exp == infinity) + if opts.gt && (!ok || newTTL <= oldTTL) { + c.WriteInt(0) + return + } + // > LT -- Set expiry only when the new expiry is less than current one + if opts.lt && ok && newTTL > oldTTL { + c.WriteInt(0) + return + } + db.ttl[opts.key] = newTTL + db.incr(opts.key) + db.checkTTL(opts.key) c.WriteInt(1) }) } } +// makeCmdExpireTime creates server command function that returns the absolute Unix timestamp (since January 1, 1970) +// at which the given key will expire, in unit selected by time result strategy (e.g. seconds, milliseconds). +// For more information see redis documentation for [expiretime] and [pexpiretime]. +// +// [expiretime]: https://redis.io/commands/expiretime/ +// [pexpiretime]: https://redis.io/commands/pexpiretime/ +func (m *Miniredis) makeCmdExpireTime(timeResultStrategy func(time.Time) int) server.Cmd { + return func(c *server.Peer, cmd string, args []string) { + if len(args) != 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + + if !m.handleAuth(c) { + return + } + if m.checkPubsub(c, cmd) { + return + } + + key := args[0] + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if _, ok := db.keys[key]; !ok { + c.WriteInt(expiretimeReplyMissingKey) + return + } + + ttl, ok := db.ttl[key] + if !ok { + c.WriteInt(expiretimeReplyNoExpiration) + return + } + + c.WriteInt(timeResultStrategy(m.effectiveNow().Add(ttl))) + }) + } +} + // TOUCH func (m *Miniredis) cmdTouch(c *server.Peer, cmd string, args []string) { if !m.handleAuth(c) { @@ -214,7 +340,7 @@ func (m *Miniredis) cmdPersist(c *server.Peer, cmd string, args []string) { return } delete(db.ttl, key) - db.keyVersion[key]++ + db.incr(key) c.WriteInt(1) }) } @@ -318,21 +444,23 @@ func (m *Miniredis) cmdMove(c *server.Peer, cmd string, args []string) { return } - key := args[0] - targetDB, err := strconv.Atoi(args[1]) - if err != nil { - targetDB = 0 + var opts struct { + key string + targetDB int } + opts.key = args[0] + opts.targetDB, _ = strconv.Atoi(args[1]) + withTx(m, c, func(c *server.Peer, ctx *connCtx) { - if ctx.selectedDB == targetDB { + if ctx.selectedDB == opts.targetDB { c.WriteError("ERR source and destination objects are the same") return } db := m.db(ctx.selectedDB) - targetDB := m.db(targetDB) + targetDB := m.db(opts.targetDB) - if !db.move(key, targetDB) { + if !db.move(opts.key, targetDB) { c.WriteInt(0) return } @@ -413,17 +541,23 @@ func (m *Miniredis) cmdRename(c *server.Peer, cmd string, args []string) { return } - from, to := args[0], args[1] + opts := struct { + from string + to string + }{ + from: args[0], + to: args[1], + } withTx(m, c, func(c *server.Peer, ctx *connCtx) { db := m.db(ctx.selectedDB) - if !db.exists(from) { + if !db.exists(opts.from) { c.WriteError(msgKeyNotFound) return } - db.rename(from, to) + db.rename(opts.from, opts.to) c.WriteOK() }) } @@ -442,130 +576,151 @@ func (m *Miniredis) cmdRenamenx(c *server.Peer, cmd string, args []string) { return } - from, to := args[0], args[1] + opts := struct { + from string + to string + }{ + from: args[0], + to: args[1], + } withTx(m, c, func(c *server.Peer, ctx *connCtx) { db := m.db(ctx.selectedDB) - if !db.exists(from) { + if !db.exists(opts.from) { c.WriteError(msgKeyNotFound) return } - if db.exists(to) { + if db.exists(opts.to) { c.WriteInt(0) return } - db.rename(from, to) + db.rename(opts.from, opts.to) c.WriteInt(1) }) } -// SCAN -func (m *Miniredis) cmdScan(c *server.Peer, cmd string, args []string) { - if len(args) < 1 { - setDirty(c) - c.WriteError(errWrongNumber(cmd)) - return - } - if !m.handleAuth(c) { - return - } - if m.checkPubsub(c, cmd) { - return - } +type scanOpts struct { + cursor int + count int + withMatch bool + match string + withType bool + _type string +} - cursor, err := strconv.Atoi(args[0]) - if err != nil { - setDirty(c) - c.WriteError(msgInvalidCursor) - return +func scanParse(cmd string, args []string) (*scanOpts, error) { + var opts scanOpts + if err := optIntSimple(args[0], &opts.cursor); err != nil { + return nil, errors.New(msgInvalidCursor) } args = args[1:] // MATCH, COUNT and TYPE options - var ( - withMatch bool - match string - withType bool - _type string - ) - for len(args) > 0 { if strings.ToLower(args[0]) == "count" { - // we do nothing with count if len(args) < 2 { - setDirty(c) - c.WriteError(msgSyntaxError) - return + return nil, errors.New(msgSyntaxError) } - if _, err := strconv.Atoi(args[1]); err != nil { - setDirty(c) - c.WriteError(msgInvalidInt) - return + count, err := strconv.Atoi(args[1]) + if err != nil || count < 0 { + return nil, errors.New(msgInvalidInt) + } + if count == 0 { + return nil, errors.New(msgSyntaxError) } + opts.count = count args = args[2:] continue } if strings.ToLower(args[0]) == "match" { if len(args) < 2 { - setDirty(c) - c.WriteError(msgSyntaxError) - return + return nil, errors.New(msgSyntaxError) } - withMatch = true - match, args = args[1], args[2:] + opts.withMatch = true + opts.match, args = args[1], args[2:] continue } if strings.ToLower(args[0]) == "type" { if len(args) < 2 { - setDirty(c) - c.WriteError(msgSyntaxError) - return + return nil, errors.New(msgSyntaxError) } - withType = true - _type, args = strings.ToLower(args[1]), args[2:] + opts.withType = true + opts._type, args = strings.ToLower(args[1]), args[2:] continue } + return nil, errors.New(msgSyntaxError) + } + return &opts, nil +} + +// SCAN +func (m *Miniredis) cmdScan(c *server.Peer, cmd string, args []string) { + if len(args) < 1 { setDirty(c) - c.WriteError(msgSyntaxError) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + if m.checkPubsub(c, cmd) { + return + } + + opts, err := scanParse(cmd, args) + if err != nil { + setDirty(c) + c.WriteError(err.Error()) return } withTx(m, c, func(c *server.Peer, ctx *connCtx) { db := m.db(ctx.selectedDB) // We return _all_ (matched) keys every time. - - if cursor != 0 { - // Invalid cursor. - c.WriteLen(2) - c.WriteBulk("0") // no next cursor - c.WriteLen(0) // no elements - return - } - var keys []string - if withType { + if opts.withType { keys = make([]string, 0) for k, t := range db.keys { // type must be given exactly; no pattern matching is performed - if t == _type { + if t == opts._type { keys = append(keys, k) } } - sort.Strings(keys) // To make things deterministic. } else { keys = db.allKeys() } - if withMatch { - keys, _ = matchKeys(keys, match) + sort.Strings(keys) // To make things deterministic. + + if opts.withMatch { + keys, _ = matchKeys(keys, opts.match) + } + + low := opts.cursor + high := low + opts.count + // validate high is correct + if high > len(keys) || high == 0 { + high = len(keys) + } + if opts.cursor > high { + // invalid cursor + c.WriteLen(2) + c.WriteBulk("0") // no next cursor + c.WriteLen(0) // no elements + return } + cursorValue := low + opts.count + if cursorValue >= len(keys) { + cursorValue = 0 // no next cursor + } + keys = keys[low:high] c.WriteLen(2) - c.WriteBulk("0") // no next cursor + c.WriteBulk(fmt.Sprintf("%d", cursorValue)) c.WriteLen(len(keys)) for _, k := range keys { c.WriteBulk(k) @@ -573,26 +728,15 @@ func (m *Miniredis) cmdScan(c *server.Peer, cmd string, args []string) { }) } -// COPY -func (m *Miniredis) cmdCopy(c *server.Peer, cmd string, args []string) { - if len(args) < 2 { - setDirty(c) - c.WriteError(errWrongNumber(cmd)) - return - } - if !m.handleAuth(c) { - return - } - if m.checkPubsub(c, cmd) { - return - } +type copyOpts struct { + from string + to string + destinationDB int + replace bool +} - var opts = struct { - from string - to string - destinationDB int - replace bool - }{ +func copyParse(cmd string, args []string) (*copyOpts, error) { + opts := copyOpts{ destinationDB: -1, } @@ -601,33 +745,45 @@ func (m *Miniredis) cmdCopy(c *server.Peer, cmd string, args []string) { switch strings.ToLower(args[0]) { case "db": if len(args) < 2 { - setDirty(c) - c.WriteError(msgSyntaxError) - return + return nil, errors.New(msgSyntaxError) } - db, err := strconv.Atoi(args[1]) - if err != nil { - setDirty(c) - c.WriteError(msgInvalidInt) - return + if err := optIntSimple(args[1], &opts.destinationDB); err != nil { + return nil, err } - if db < 0 { - setDirty(c) - c.WriteError(msgDBIndexOutOfRange) - return + if opts.destinationDB < 0 { + return nil, errors.New(msgDBIndexOutOfRange) } - opts.destinationDB = db args = args[2:] case "replace": opts.replace = true args = args[1:] default: - setDirty(c) - c.WriteError(msgSyntaxError) - return + return nil, errors.New(msgSyntaxError) } } + return &opts, nil +} +// COPY +func (m *Miniredis) cmdCopy(c *server.Peer, cmd string, args []string) { + if len(args) < 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + if m.checkPubsub(c, cmd) { + return + } + + opts, err := copyParse(cmd, args) + if err != nil { + setDirty(c) + c.WriteError(err.Error()) + return + } withTx(m, c, func(c *server.Peer, ctx *connCtx) { fromDB, toDB := ctx.selectedDB, opts.destinationDB if toDB == -1 { diff --git a/vendor/github.com/alicebob/miniredis/v2/cmd_geo.go b/vendor/github.com/alicebob/miniredis/v2/cmd_geo.go index a6c1901d6..97f74c3b1 100644 --- a/vendor/github.com/alicebob/miniredis/v2/cmd_geo.go +++ b/vendor/github.com/alicebob/miniredis/v2/cmd_geo.go @@ -40,7 +40,7 @@ func (m *Miniredis) cmdGeoadd(c *server.Peer, cmd string, args []string) { withTx(m, c, func(c *server.Peer, ctx *connCtx) { db := m.db(ctx.selectedDB) - if db.exists(key) && db.t(key) != "zset" { + if db.exists(key) && db.t(key) != keyTypeSortedSet { c.WriteError(ErrWrongType.Error()) return } @@ -103,7 +103,7 @@ func (m *Miniredis) cmdGeodist(c *server.Peer, cmd string, args []string) { c.WriteNull() return } - if db.t(key) != "zset" { + if db.t(key) != keyTypeSortedSet { c.WriteError(ErrWrongType.Error()) return } @@ -157,7 +157,7 @@ func (m *Miniredis) cmdGeopos(c *server.Peer, cmd string, args []string) { withTx(m, c, func(c *server.Peer, ctx *connCtx) { db := m.db(ctx.selectedDB) - if db.exists(key) && db.t(key) != "zset" { + if db.exists(key) && db.t(key) != keyTypeSortedSet { c.WriteError(ErrWrongType.Error()) return } @@ -226,28 +226,28 @@ func (m *Miniredis) cmdGeoradius(c *server.Peer, cmd string, args []string) { } args = args[5:] - var ( - withDist = false - withCoord = false - direction = unsorted - count = 0 - withStore = false - storeKey = "" - withStoredist = false - storedistKey = "" - ) + var opts struct { + withDist bool + withCoord bool + direction direction // unsorted + count int + withStore bool + storeKey string + withStoredist bool + storedistKey string + } for len(args) > 0 { arg := args[0] args = args[1:] switch strings.ToUpper(arg) { case "WITHCOORD": - withCoord = true + opts.withCoord = true case "WITHDIST": - withDist = true + opts.withDist = true case "ASC": - direction = asc + opts.direction = asc case "DESC": - direction = desc + opts.direction = desc case "COUNT": if len(args) == 0 { setDirty(c) @@ -266,15 +266,15 @@ func (m *Miniredis) cmdGeoradius(c *server.Peer, cmd string, args []string) { return } args = args[1:] - count = n + opts.count = n case "STORE": if len(args) == 0 { setDirty(c) c.WriteError("ERR syntax error") return } - withStore = true - storeKey = args[0] + opts.withStore = true + opts.storeKey = args[0] args = args[1:] case "STOREDIST": if len(args) == 0 { @@ -282,8 +282,8 @@ func (m *Miniredis) cmdGeoradius(c *server.Peer, cmd string, args []string) { c.WriteError("ERR syntax error") return } - withStoredist = true - storedistKey = args[0] + opts.withStoredist = true + opts.storedistKey = args[0] args = args[1:] default: setDirty(c) @@ -292,14 +292,14 @@ func (m *Miniredis) cmdGeoradius(c *server.Peer, cmd string, args []string) { } } - if strings.ToUpper(cmd) == "GEORADIUS_RO" && (withStore || withStoredist) { + if strings.ToUpper(cmd) == "GEORADIUS_RO" && (opts.withStore || opts.withStoredist) { setDirty(c) c.WriteError("ERR syntax error") return } withTx(m, c, func(c *server.Peer, ctx *connCtx) { - if (withStore || withStoredist) && (withDist || withCoord) { + if (opts.withStore || opts.withStoredist) && (opts.withDist || opts.withCoord) { c.WriteError("ERR STORE option in GEORADIUS is not compatible with WITHDIST, WITHHASH and WITHCOORDS options") return } @@ -310,9 +310,9 @@ func (m *Miniredis) cmdGeoradius(c *server.Peer, cmd string, args []string) { matches := withinRadius(members, longitude, latitude, radius*toMeter) // deal with ASC/DESC - if direction != unsorted { + if opts.direction != unsorted { sort.Slice(matches, func(i, j int) bool { - if direction == desc { + if opts.direction == desc { return matches[i].Distance > matches[j].Distance } return matches[i].Distance < matches[j].Distance @@ -320,25 +320,25 @@ func (m *Miniredis) cmdGeoradius(c *server.Peer, cmd string, args []string) { } // deal with COUNT - if count > 0 && len(matches) > count { - matches = matches[:count] + if opts.count > 0 && len(matches) > opts.count { + matches = matches[:opts.count] } // deal with "STORE x" - if withStore { - db.del(storeKey, true) + if opts.withStore { + db.del(opts.storeKey, true) for _, member := range matches { - db.ssetAdd(storeKey, member.Score, member.Name) + db.ssetAdd(opts.storeKey, member.Score, member.Name) } c.WriteInt(len(matches)) return } // deal with "STOREDIST x" - if withStoredist { - db.del(storedistKey, true) + if opts.withStoredist { + db.del(opts.storedistKey, true) for _, member := range matches { - db.ssetAdd(storedistKey, member.Distance/toMeter, member.Name) + db.ssetAdd(opts.storedistKey, member.Distance/toMeter, member.Name) } c.WriteInt(len(matches)) return @@ -346,24 +346,24 @@ func (m *Miniredis) cmdGeoradius(c *server.Peer, cmd string, args []string) { c.WriteLen(len(matches)) for _, member := range matches { - if !withDist && !withCoord { + if !opts.withDist && !opts.withCoord { c.WriteBulk(member.Name) continue } len := 1 - if withDist { + if opts.withDist { len++ } - if withCoord { + if opts.withCoord { len++ } c.WriteLen(len) c.WriteBulk(member.Name) - if withDist { + if opts.withDist { c.WriteBulk(fmt.Sprintf("%.4f", member.Distance/toMeter)) } - if withCoord { + if opts.withCoord { c.WriteLen(2) c.WriteBulk(fmt.Sprintf("%f", member.Longitude)) c.WriteBulk(fmt.Sprintf("%f", member.Latitude)) @@ -386,45 +386,53 @@ func (m *Miniredis) cmdGeoradiusbymember(c *server.Peer, cmd string, args []stri return } - key := args[0] - member := args[1] + opts := struct { + key string + member string + radius float64 + toMeter float64 + + withDist bool + withCoord bool + direction direction // unsorted + count int + withStore bool + storeKey string + withStoredist bool + storedistKey string + }{ + key: args[0], + member: args[1], + } - radius, err := strconv.ParseFloat(args[2], 64) - if err != nil || radius < 0 { + r, err := strconv.ParseFloat(args[2], 64) + if err != nil || r < 0 { setDirty(c) c.WriteError(errWrongNumber(cmd)) return } - toMeter := parseUnit(args[3]) - if toMeter == 0 { + opts.radius = r + + opts.toMeter = parseUnit(args[3]) + if opts.toMeter == 0 { setDirty(c) c.WriteError(errWrongNumber(cmd)) return } args = args[4:] - var ( - withDist = false - withCoord = false - direction = unsorted - count = 0 - withStore = false - storeKey = "" - withStoredist = false - storedistKey = "" - ) for len(args) > 0 { arg := args[0] args = args[1:] switch strings.ToUpper(arg) { case "WITHCOORD": - withCoord = true + opts.withCoord = true case "WITHDIST": - withDist = true + opts.withDist = true case "ASC": - direction = asc + opts.direction = asc case "DESC": - direction = desc + opts.direction = desc case "COUNT": if len(args) == 0 { setDirty(c) @@ -443,15 +451,15 @@ func (m *Miniredis) cmdGeoradiusbymember(c *server.Peer, cmd string, args []stri return } args = args[1:] - count = n + opts.count = n case "STORE": if len(args) == 0 { setDirty(c) c.WriteError("ERR syntax error") return } - withStore = true - storeKey = args[0] + opts.withStore = true + opts.storeKey = args[0] args = args[1:] case "STOREDIST": if len(args) == 0 { @@ -459,8 +467,8 @@ func (m *Miniredis) cmdGeoradiusbymember(c *server.Peer, cmd string, args []stri c.WriteError("ERR syntax error") return } - withStoredist = true - storedistKey = args[0] + opts.withStoredist = true + opts.storedistKey = args[0] args = args[1:] default: setDirty(c) @@ -469,44 +477,44 @@ func (m *Miniredis) cmdGeoradiusbymember(c *server.Peer, cmd string, args []stri } } - if strings.ToUpper(cmd) == "GEORADIUSBYMEMBER_RO" && (withStore || withStoredist) { + if strings.ToUpper(cmd) == "GEORADIUSBYMEMBER_RO" && (opts.withStore || opts.withStoredist) { setDirty(c) c.WriteError("ERR syntax error") return } withTx(m, c, func(c *server.Peer, ctx *connCtx) { - if (withStore || withStoredist) && (withDist || withCoord) { + if (opts.withStore || opts.withStoredist) && (opts.withDist || opts.withCoord) { c.WriteError("ERR STORE option in GEORADIUS is not compatible with WITHDIST, WITHHASH and WITHCOORDS options") return } db := m.db(ctx.selectedDB) - if !db.exists(key) { + if !db.exists(opts.key) { c.WriteNull() return } - if db.t(key) != "zset" { + if db.t(opts.key) != keyTypeSortedSet { c.WriteError(ErrWrongType.Error()) return } // get position of member - if !db.ssetExists(key, member) { + if !db.ssetExists(opts.key, opts.member) { c.WriteError("ERR could not decode requested zset member") return } - score := db.ssetScore(key, member) + score := db.ssetScore(opts.key, opts.member) longitude, latitude := fromGeohash(uint64(score)) - members := db.ssetElements(key) - matches := withinRadius(members, longitude, latitude, radius*toMeter) + members := db.ssetElements(opts.key) + matches := withinRadius(members, longitude, latitude, opts.radius*opts.toMeter) // deal with ASC/DESC - if direction != unsorted { + if opts.direction != unsorted { sort.Slice(matches, func(i, j int) bool { - if direction == desc { + if opts.direction == desc { return matches[i].Distance > matches[j].Distance } return matches[i].Distance < matches[j].Distance @@ -514,25 +522,25 @@ func (m *Miniredis) cmdGeoradiusbymember(c *server.Peer, cmd string, args []stri } // deal with COUNT - if count > 0 && len(matches) > count { - matches = matches[:count] + if opts.count > 0 && len(matches) > opts.count { + matches = matches[:opts.count] } // deal with "STORE x" - if withStore { - db.del(storeKey, true) + if opts.withStore { + db.del(opts.storeKey, true) for _, member := range matches { - db.ssetAdd(storeKey, member.Score, member.Name) + db.ssetAdd(opts.storeKey, member.Score, member.Name) } c.WriteInt(len(matches)) return } // deal with "STOREDIST x" - if withStoredist { - db.del(storedistKey, true) + if opts.withStoredist { + db.del(opts.storedistKey, true) for _, member := range matches { - db.ssetAdd(storedistKey, member.Distance/toMeter, member.Name) + db.ssetAdd(opts.storedistKey, member.Distance/opts.toMeter, member.Name) } c.WriteInt(len(matches)) return @@ -540,24 +548,24 @@ func (m *Miniredis) cmdGeoradiusbymember(c *server.Peer, cmd string, args []stri c.WriteLen(len(matches)) for _, member := range matches { - if !withDist && !withCoord { + if !opts.withDist && !opts.withCoord { c.WriteBulk(member.Name) continue } len := 1 - if withDist { + if opts.withDist { len++ } - if withCoord { + if opts.withCoord { len++ } c.WriteLen(len) c.WriteBulk(member.Name) - if withDist { - c.WriteBulk(fmt.Sprintf("%.4f", member.Distance/toMeter)) + if opts.withDist { + c.WriteBulk(fmt.Sprintf("%.4f", member.Distance/opts.toMeter)) } - if withCoord { + if opts.withCoord { c.WriteLen(2) c.WriteBulk(fmt.Sprintf("%f", member.Longitude)) c.WriteBulk(fmt.Sprintf("%f", member.Latitude)) @@ -586,7 +594,7 @@ func withinRadius(members []ssElem, longitude, latitude, radius float64) []geoDi } func parseUnit(u string) float64 { - switch u { + switch strings.ToLower(u) { case "m": return 1 case "km": diff --git a/vendor/github.com/alicebob/miniredis/v2/cmd_hash.go b/vendor/github.com/alicebob/miniredis/v2/cmd_hash.go index 142ba63e1..55332956f 100644 --- a/vendor/github.com/alicebob/miniredis/v2/cmd_hash.go +++ b/vendor/github.com/alicebob/miniredis/v2/cmd_hash.go @@ -27,6 +27,7 @@ func commandsHash(m *Miniredis) { m.srv.Register("HSTRLEN", m.cmdHstrlen) m.srv.Register("HVALS", m.cmdHvals) m.srv.Register("HSCAN", m.cmdHscan) + m.srv.Register("HRANDFIELD", m.cmdHrandfield) } // HSET @@ -53,7 +54,7 @@ func (m *Miniredis) cmdHset(c *server.Peer, cmd string, args []string) { return } - if t, ok := db.keys[key]; ok && t != "hash" { + if t, ok := db.keys[key]; ok && t != keyTypeHash { c.WriteError(msgWrongType) return } @@ -77,27 +78,35 @@ func (m *Miniredis) cmdHsetnx(c *server.Peer, cmd string, args []string) { return } - key, field, value := args[0], args[1], args[2] + opts := struct { + key string + field string + value string + }{ + key: args[0], + field: args[1], + value: args[2], + } withTx(m, c, func(c *server.Peer, ctx *connCtx) { db := m.db(ctx.selectedDB) - if t, ok := db.keys[key]; ok && t != "hash" { + if t, ok := db.keys[opts.key]; ok && t != keyTypeHash { c.WriteError(msgWrongType) return } - if _, ok := db.hashKeys[key]; !ok { - db.hashKeys[key] = map[string]string{} - db.keys[key] = "hash" + if _, ok := db.hashKeys[opts.key]; !ok { + db.hashKeys[opts.key] = map[string]string{} + db.keys[opts.key] = keyTypeHash } - _, ok := db.hashKeys[key][field] + _, ok := db.hashKeys[opts.key][opts.field] if ok { c.WriteInt(0) return } - db.hashKeys[key][field] = value - db.keyVersion[key]++ + db.hashKeys[opts.key][opts.field] = opts.value + db.incr(opts.key) c.WriteInt(1) }) } @@ -126,7 +135,7 @@ func (m *Miniredis) cmdHmset(c *server.Peer, cmd string, args []string) { withTx(m, c, func(c *server.Peer, ctx *connCtx) { db := m.db(ctx.selectedDB) - if t, ok := db.keys[key]; ok && t != "hash" { + if t, ok := db.keys[key]; ok && t != keyTypeHash { c.WriteError(msgWrongType) return } @@ -164,7 +173,7 @@ func (m *Miniredis) cmdHget(c *server.Peer, cmd string, args []string) { c.WriteNull() return } - if t != "hash" { + if t != keyTypeHash { c.WriteError(msgWrongType) return } @@ -191,36 +200,42 @@ func (m *Miniredis) cmdHdel(c *server.Peer, cmd string, args []string) { return } - key, fields := args[0], args[1:] + opts := struct { + key string + fields []string + }{ + key: args[0], + fields: args[1:], + } withTx(m, c, func(c *server.Peer, ctx *connCtx) { db := m.db(ctx.selectedDB) - t, ok := db.keys[key] + t, ok := db.keys[opts.key] if !ok { // No key is zero deleted c.WriteInt(0) return } - if t != "hash" { + if t != keyTypeHash { c.WriteError(msgWrongType) return } deleted := 0 - for _, f := range fields { - _, ok := db.hashKeys[key][f] + for _, f := range opts.fields { + _, ok := db.hashKeys[opts.key][f] if !ok { continue } - delete(db.hashKeys[key], f) + delete(db.hashKeys[opts.key], f) deleted++ } c.WriteInt(deleted) // Nothing left. Remove the whole key. - if len(db.hashKeys[key]) == 0 { - db.del(key, true) + if len(db.hashKeys[opts.key]) == 0 { + db.del(opts.key, true) } }) } @@ -239,22 +254,28 @@ func (m *Miniredis) cmdHexists(c *server.Peer, cmd string, args []string) { return } - key, field := args[0], args[1] + opts := struct { + key string + field string + }{ + key: args[0], + field: args[1], + } withTx(m, c, func(c *server.Peer, ctx *connCtx) { db := m.db(ctx.selectedDB) - t, ok := db.keys[key] + t, ok := db.keys[opts.key] if !ok { c.WriteInt(0) return } - if t != "hash" { + if t != keyTypeHash { c.WriteError(msgWrongType) return } - if _, ok := db.hashKeys[key][field]; !ok { + if _, ok := db.hashKeys[opts.key][opts.field]; !ok { c.WriteInt(0) return } @@ -286,7 +307,7 @@ func (m *Miniredis) cmdHgetall(c *server.Peer, cmd string, args []string) { c.WriteMapLen(0) return } - if t != "hash" { + if t != keyTypeHash { c.WriteError(msgWrongType) return } @@ -322,7 +343,7 @@ func (m *Miniredis) cmdHkeys(c *server.Peer, cmd string, args []string) { c.WriteLen(0) return } - if db.t(key) != "hash" { + if db.t(key) != keyTypeHash { c.WriteError(msgWrongType) return } @@ -359,7 +380,7 @@ func (m *Miniredis) cmdHstrlen(c *server.Peer, cmd string, args []string) { c.WriteInt(0) return } - if t != "hash" { + if t != keyTypeHash { c.WriteError(msgWrongType) return } @@ -393,7 +414,7 @@ func (m *Miniredis) cmdHvals(c *server.Peer, cmd string, args []string) { c.WriteLen(0) return } - if t != "hash" { + if t != keyTypeHash { c.WriteError(msgWrongType) return } @@ -430,7 +451,7 @@ func (m *Miniredis) cmdHlen(c *server.Peer, cmd string, args []string) { c.WriteInt(0) return } - if t != "hash" { + if t != keyTypeHash { c.WriteError(msgWrongType) return } @@ -458,7 +479,7 @@ func (m *Miniredis) cmdHmget(c *server.Peer, cmd string, args []string) { withTx(m, c, func(c *server.Peer, ctx *connCtx) { db := m.db(ctx.selectedDB) - if t, ok := db.keys[key]; ok && t != "hash" { + if t, ok := db.keys[key]; ok && t != keyTypeHash { c.WriteError(msgWrongType) return } @@ -494,24 +515,27 @@ func (m *Miniredis) cmdHincrby(c *server.Peer, cmd string, args []string) { return } - key, field, deltas := args[0], args[1], args[2] - - delta, err := strconv.Atoi(deltas) - if err != nil { - setDirty(c) - c.WriteError(msgInvalidInt) + opts := struct { + key string + field string + delta int + }{ + key: args[0], + field: args[1], + } + if ok := optInt(c, args[2], &opts.delta); !ok { return } withTx(m, c, func(c *server.Peer, ctx *connCtx) { db := m.db(ctx.selectedDB) - if t, ok := db.keys[key]; ok && t != "hash" { + if t, ok := db.keys[opts.key]; ok && t != keyTypeHash { c.WriteError(msgWrongType) return } - v, err := db.hashIncr(key, field, delta) + v, err := db.hashIncr(opts.key, opts.field, opts.delta) if err != nil { c.WriteError(err.Error()) return @@ -534,24 +558,31 @@ func (m *Miniredis) cmdHincrbyfloat(c *server.Peer, cmd string, args []string) { return } - key, field, deltas := args[0], args[1], args[2] - - delta, _, err := big.ParseFloat(deltas, 10, 128, 0) + opts := struct { + key string + field string + delta *big.Float + }{ + key: args[0], + field: args[1], + } + delta, _, err := big.ParseFloat(args[2], 10, 128, 0) if err != nil { setDirty(c) c.WriteError(msgInvalidFloat) return } + opts.delta = delta withTx(m, c, func(c *server.Peer, ctx *connCtx) { db := m.db(ctx.selectedDB) - if t, ok := db.keys[key]; ok && t != "hash" { + if t, ok := db.keys[opts.key]; ok && t != keyTypeHash { c.WriteError(msgWrongType) return } - v, err := db.hashIncrfloat(key, field, delta) + v, err := db.hashIncrfloat(opts.key, opts.field, opts.delta) if err != nil { c.WriteError(err.Error()) return @@ -574,18 +605,20 @@ func (m *Miniredis) cmdHscan(c *server.Peer, cmd string, args []string) { return } - key := args[0] - cursor, err := strconv.Atoi(args[1]) - if err != nil { - setDirty(c) - c.WriteError(msgInvalidCursor) + opts := struct { + key string + cursor int + withMatch bool + match string + }{ + key: args[0], + } + if ok := optIntErr(c, args[1], &opts.cursor, msgInvalidCursor); !ok { return } args = args[2:] // MATCH and COUNT options - var withMatch bool - var match string for len(args) > 0 { if strings.ToLower(args[0]) == "count" { // we do nothing with count @@ -609,8 +642,8 @@ func (m *Miniredis) cmdHscan(c *server.Peer, cmd string, args []string) { c.WriteError(msgSyntaxError) return } - withMatch = true - match, args = args[1], args[2:] + opts.withMatch = true + opts.match, args = args[1], args[2:] continue } setDirty(c) @@ -622,21 +655,21 @@ func (m *Miniredis) cmdHscan(c *server.Peer, cmd string, args []string) { db := m.db(ctx.selectedDB) // return _all_ (matched) keys every time - if cursor != 0 { + if opts.cursor != 0 { // Invalid cursor. c.WriteLen(2) c.WriteBulk("0") // no next cursor c.WriteLen(0) // no elements return } - if db.exists(key) && db.t(key) != "hash" { + if db.exists(opts.key) && db.t(opts.key) != keyTypeHash { c.WriteError(ErrWrongType.Error()) return } - members := db.hashFields(key) - if withMatch { - members, _ = matchKeys(members, match) + members := db.hashFields(opts.key) + if opts.withMatch { + members, _ = matchKeys(members, opts.match) } c.WriteLen(2) @@ -645,7 +678,100 @@ func (m *Miniredis) cmdHscan(c *server.Peer, cmd string, args []string) { c.WriteLen(len(members) * 2) for _, k := range members { c.WriteBulk(k) - c.WriteBulk(db.hashGet(key, k)) + c.WriteBulk(db.hashGet(opts.key, k)) } }) } + +// HRANDFIELD +func (m *Miniredis) cmdHrandfield(c *server.Peer, cmd string, args []string) { + if len(args) > 3 || len(args) < 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + if m.checkPubsub(c, cmd) { + return + } + + opts := struct { + key string + count int + countSet bool + withValues bool + }{ + key: args[0], + } + + if len(args) > 1 { + if ok := optIntErr(c, args[1], &opts.count, msgInvalidInt); !ok { + return + } + opts.countSet = true + } + + if len(args) == 3 { + if strings.ToLower(args[2]) == "withvalues" { + opts.withValues = true + } else { + setDirty(c) + c.WriteError(msgSyntaxError) + return + } + } + + withTx(m, c, func(peer *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + members := db.hashFields(opts.key) + m.shuffle(members) + + if !opts.countSet { + // > When called with just the key argument, return a random field from the + // hash value stored at key. + if len(members) == 0 { + peer.WriteNull() + return + } + peer.WriteBulk(members[0]) + return + } + + if len(members) > abs(opts.count) { + members = members[:abs(opts.count)] + } + switch { + case opts.count >= 0: + // if count is positive there can't be duplicates, and the length is restricted + case opts.count < 0: + // if count is negative there can be duplicates, but length will match + if len(members) > 0 { + for len(members) < -opts.count { + members = append(members, members[m.randIntn(len(members))]) + } + } + } + + if opts.withValues { + peer.WriteMapLen(len(members)) + for _, m := range members { + peer.WriteBulk(m) + peer.WriteBulk(db.hashGet(opts.key, m)) + } + return + } + peer.WriteLen(len(members)) + for _, m := range members { + peer.WriteBulk(m) + } + }) +} + +func abs(n int) int { + if n < 0 { + return -n + } + return n +} diff --git a/vendor/github.com/alicebob/miniredis/v2/cmd_hll.go b/vendor/github.com/alicebob/miniredis/v2/cmd_hll.go index bd2f90c83..ffb4d6f1b 100644 --- a/vendor/github.com/alicebob/miniredis/v2/cmd_hll.go +++ b/vendor/github.com/alicebob/miniredis/v2/cmd_hll.go @@ -28,7 +28,7 @@ func (m *Miniredis) cmdPfadd(c *server.Peer, cmd string, args []string) { withTx(m, c, func(c *server.Peer, ctx *connCtx) { db := m.db(ctx.selectedDB) - if db.exists(key) && db.t(key) != "hll" { + if db.exists(key) && db.t(key) != keyTypeHll { c.WriteError(ErrNotValidHllValue.Error()) return } @@ -52,7 +52,7 @@ func (m *Miniredis) cmdPfcount(c *server.Peer, cmd string, args []string) { return } - keys := args[:] + keys := args withTx(m, c, func(c *server.Peer, ctx *connCtx) { db := m.db(ctx.selectedDB) diff --git a/vendor/github.com/alicebob/miniredis/v2/cmd_info.go b/vendor/github.com/alicebob/miniredis/v2/cmd_info.go new file mode 100644 index 000000000..e5984a9b2 --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/v2/cmd_info.go @@ -0,0 +1,40 @@ +package miniredis + +import ( + "fmt" + + "github.com/alicebob/miniredis/v2/server" +) + +// Command 'INFO' from https://redis.io/commands/info/ +func (m *Miniredis) cmdInfo(c *server.Peer, cmd string, args []string) { + if !m.isValidCMD(c, cmd) { + return + } + + if len(args) > 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + const ( + clientsSectionName = "clients" + clientsSectionContent = "# Clients\nconnected_clients:%d\r\n" + ) + + var result string + + for _, key := range args { + if key != clientsSectionName { + setDirty(c) + c.WriteError(fmt.Sprintf("section (%s) is not supported", key)) + return + } + } + result = fmt.Sprintf(clientsSectionContent, m.Server().ClientsLen()) + + c.WriteBulk(result) + }) +} diff --git a/vendor/github.com/alicebob/miniredis/v2/cmd_list.go b/vendor/github.com/alicebob/miniredis/v2/cmd_list.go index 62f969188..58199454a 100644 --- a/vendor/github.com/alicebob/miniredis/v2/cmd_list.go +++ b/vendor/github.com/alicebob/miniredis/v2/cmd_list.go @@ -23,6 +23,7 @@ func commandsList(m *Miniredis) { m.srv.Register("BRPOP", m.cmdBrpop) m.srv.Register("BRPOPLPUSH", m.cmdBrpoplpush) m.srv.Register("LINDEX", m.cmdLindex) + m.srv.Register("LPOS", m.cmdLpos) m.srv.Register("LINSERT", m.cmdLinsert) m.srv.Register("LLEN", m.cmdLlen) m.srv.Register("LPOP", m.cmdLpop) @@ -37,6 +38,7 @@ func commandsList(m *Miniredis) { m.srv.Register("RPUSH", m.cmdRpush) m.srv.Register("RPUSHX", m.cmdRpushx) m.srv.Register("LMOVE", m.cmdLmove) + m.srv.Register("BLMOVE", m.cmdBlmove) } // BLPOP @@ -62,32 +64,27 @@ func (m *Miniredis) cmdBXpop(c *server.Peer, cmd string, args []string, lr leftr return } - timeoutS := args[len(args)-1] - keys := args[:len(args)-1] - - timeout, err := strconv.Atoi(timeoutS) - if err != nil { - setDirty(c) - c.WriteError(msgInvalidTimeout) - return + var opts struct { + keys []string + timeout time.Duration } - if timeout < 0 { - setDirty(c) - c.WriteError(msgNegTimeout) + + if ok := optDuration(c, args[len(args)-1], &opts.timeout); !ok { return } + opts.keys = args[:len(args)-1] blocking( m, c, - time.Duration(timeout)*time.Second, + opts.timeout, func(c *server.Peer, ctx *connCtx) bool { db := m.db(ctx.selectedDB) - for _, key := range keys { + for _, key := range opts.keys { if !db.exists(key) { continue } - if db.t(key) != "list" { + if db.t(key) != keyTypeList { c.WriteError(msgWrongType) return true } @@ -148,7 +145,7 @@ func (m *Miniredis) cmdLindex(c *server.Peer, cmd string, args []string) { c.WriteNull() return } - if t != "list" { + if t != keyTypeList { c.WriteError(msgWrongType) return } @@ -165,6 +162,153 @@ func (m *Miniredis) cmdLindex(c *server.Peer, cmd string, args []string) { }) } +// LPOS key element [RANK rank] [COUNT num-matches] [MAXLEN len] +func (m *Miniredis) cmdLpos(c *server.Peer, cmd string, args []string) { + if !m.handleAuth(c) { + return + } + if m.checkPubsub(c, cmd) { + return + } + + if len(args) == 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + + // Extract options from arguments if present. + // + // Redis allows duplicate options and uses the last specified. + // `LPOS key term RANK 1 RANK 2` is effectively the same as + // `LPOS key term RANK 2` + if len(args)%2 == 1 { + setDirty(c) + c.WriteError(msgSyntaxError) + return + } + rank, count := 1, 1 // Default values + var maxlen int // Default value is the list length (see below) + var countSpecified, maxlenSpecified bool + if len(args) > 2 { + for i := 2; i < len(args); i++ { + if i%2 == 0 { + val := args[i+1] + var err error + switch strings.ToLower(args[i]) { + case "rank": + if rank, err = strconv.Atoi(val); err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + if rank == 0 { + setDirty(c) + c.WriteError(msgRankIsZero) + return + } + case "count": + countSpecified = true + if count, err = strconv.Atoi(val); err != nil || count < 0 { + setDirty(c) + c.WriteError(msgCountIsNegative) + return + } + case "maxlen": + maxlenSpecified = true + if maxlen, err = strconv.Atoi(val); err != nil || maxlen < 0 { + setDirty(c) + c.WriteError(msgMaxLengthIsNegative) + return + } + default: + setDirty(c) + c.WriteError(msgSyntaxError) + return + } + } + } + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + key, element := args[0], args[1] + t, ok := db.keys[key] + if !ok { + // No such key + c.WriteNull() + return + } + if t != keyTypeList { + c.WriteError(msgWrongType) + return + } + l := db.listKeys[key] + + // RANK cannot be zero (see above). + // If RANK is positive search forward (left to right). + // If RANK is negative search backward (right to left). + // Iterator returns true to continue iterating. + iterate := func(iterator func(i int, e string) bool) { + comparisons := len(l) + // Only use max length if specified, not zero, and less than total length. + // When max length is specified, but is zero, this means "unlimited". + if maxlenSpecified && maxlen != 0 && maxlen < len(l) { + comparisons = maxlen + } + if rank > 0 { + for i := 0; i < comparisons; i++ { + if resume := iterator(i, l[i]); !resume { + return + } + } + } else if rank < 0 { + start := len(l) - 1 + end := len(l) - comparisons + for i := start; i >= end; i-- { + if resume := iterator(i, l[i]); !resume { + return + } + } + } + } + + var currentRank, currentCount int + vals := make([]int, 0, count) + iterate(func(i int, e string) bool { + if e == element { + currentRank++ + // Only collect values only after surpassing the absolute value of rank. + if rank > 0 && currentRank < rank { + return true + } + if rank < 0 && currentRank < -rank { + return true + } + vals = append(vals, i) + currentCount++ + if currentCount == count { + return false + } + } + return true + }) + + if !countSpecified && len(vals) == 0 { + c.WriteNull() + return + } + if !countSpecified && len(vals) == 1 { + c.WriteInt(vals[0]) + return + } + c.WriteLen(len(vals)) + for _, val := range vals { + c.WriteInt(val) + } + }) +} + // LINSERT func (m *Miniredis) cmdLinsert(c *server.Peer, cmd string, args []string) { if len(args) != 4 { @@ -203,7 +347,7 @@ func (m *Miniredis) cmdLinsert(c *server.Peer, cmd string, args []string) { c.WriteInt(0) return } - if t != "list" { + if t != keyTypeList { c.WriteError(msgWrongType) return } @@ -224,7 +368,7 @@ func (m *Miniredis) cmdLinsert(c *server.Peer, cmd string, args []string) { } } db.listKeys[key] = l - db.keyVersion[key]++ + db.incr(key) c.WriteInt(len(l)) return } @@ -257,7 +401,7 @@ func (m *Miniredis) cmdLlen(c *server.Peer, cmd string, args []string) { c.WriteInt(0) return } - if t != "list" { + if t != keyTypeList { c.WriteError(msgWrongType) return } @@ -297,18 +441,14 @@ func (m *Miniredis) cmdXpop(c *server.Peer, cmd string, args []string, lr leftri opts.key, args = args[0], args[1:] if len(args) > 0 { - v, err := strconv.Atoi(args[0]) - if err != nil { - setDirty(c) - c.WriteError(msgInvalidInt) + if ok := optInt(c, args[0], &opts.count); !ok { return } - if v < 0 { + if opts.count < 0 { setDirty(c) c.WriteError(msgOutOfRange) return } - opts.count = v opts.withCount = true args = args[1:] } @@ -323,10 +463,15 @@ func (m *Miniredis) cmdXpop(c *server.Peer, cmd string, args []string, lr leftri if !db.exists(opts.key) { // non-existing key is fine + if opts.withCount && !c.Resp3 { + // zero-length list in this specific case. Looks like a redis bug to me. + c.WriteLen(-1) + return + } c.WriteNull() return } - if db.t(opts.key) != "list" { + if db.t(opts.key) != keyTypeList { c.WriteError(msgWrongType) return } @@ -342,11 +487,7 @@ func (m *Miniredis) cmdXpop(c *server.Peer, cmd string, args []string, lr leftri } opts.count -= 1 } - if len(popped) == 0 { - c.WriteLen(-1) - } else { - c.WriteStrings(popped) - } + c.WriteStrings(popped) return } @@ -389,7 +530,7 @@ func (m *Miniredis) cmdXpush(c *server.Peer, cmd string, args []string, lr leftr withTx(m, c, func(c *server.Peer, ctx *connCtx) { db := m.db(ctx.selectedDB) - if db.exists(key) && db.t(key) != "list" { + if db.exists(key) && db.t(key) != keyTypeList { c.WriteError(msgWrongType) return } @@ -439,7 +580,7 @@ func (m *Miniredis) cmdXpushx(c *server.Peer, cmd string, args []string, lr left c.WriteInt(0) return } - if db.t(key) != "list" { + if db.t(key) != keyTypeList { c.WriteError(msgWrongType) return } @@ -471,35 +612,35 @@ func (m *Miniredis) cmdLrange(c *server.Peer, cmd string, args []string) { return } - key := args[0] - start, err := strconv.Atoi(args[1]) - if err != nil { - setDirty(c) - c.WriteError(msgInvalidInt) + opts := struct { + key string + start int + end int + }{ + key: args[0], + } + if ok := optInt(c, args[1], &opts.start); !ok { return } - end, err := strconv.Atoi(args[2]) - if err != nil { - setDirty(c) - c.WriteError(msgInvalidInt) + if ok := optInt(c, args[2], &opts.end); !ok { return } withTx(m, c, func(c *server.Peer, ctx *connCtx) { db := m.db(ctx.selectedDB) - if t, ok := db.keys[key]; ok && t != "list" { + if t, ok := db.keys[opts.key]; ok && t != keyTypeList { c.WriteError(msgWrongType) return } - l := db.listKeys[key] + l := db.listKeys[opts.key] if len(l) == 0 { c.WriteLen(0) return } - rs, re := redisRange(len(l), start, end, false) + rs, re := redisRange(len(l), opts.start, opts.end, false) c.WriteLen(re - rs) for _, el := range l[rs:re] { c.WriteBulk(el) @@ -521,42 +662,44 @@ func (m *Miniredis) cmdLrem(c *server.Peer, cmd string, args []string) { return } - key := args[0] - count, err := strconv.Atoi(args[1]) - if err != nil { - setDirty(c) - c.WriteError(msgInvalidInt) + var opts struct { + key string + count int + value string + } + opts.key = args[0] + if ok := optInt(c, args[1], &opts.count); !ok { return } - value := args[2] + opts.value = args[2] withTx(m, c, func(c *server.Peer, ctx *connCtx) { db := m.db(ctx.selectedDB) - if !db.exists(key) { + if !db.exists(opts.key) { c.WriteInt(0) return } - if db.t(key) != "list" { + if db.t(opts.key) != keyTypeList { c.WriteError(msgWrongType) return } - l := db.listKeys[key] - if count < 0 { + l := db.listKeys[opts.key] + if opts.count < 0 { reverseSlice(l) } deleted := 0 newL := []string{} toDelete := len(l) - if count < 0 { - toDelete = -count + if opts.count < 0 { + toDelete = -opts.count } - if count > 0 { - toDelete = count + if opts.count > 0 { + toDelete = opts.count } for _, el := range l { - if el == value { + if el == opts.value { if toDelete > 0 { deleted++ toDelete-- @@ -565,14 +708,14 @@ func (m *Miniredis) cmdLrem(c *server.Peer, cmd string, args []string) { } newL = append(newL, el) } - if count < 0 { + if opts.count < 0 { reverseSlice(newL) } if len(newL) == 0 { - db.del(key, true) + db.del(opts.key, true) } else { - db.listKeys[key] = newL - db.keyVersion[key]++ + db.listKeys[opts.key] = newL + db.incr(opts.key) } c.WriteInt(deleted) @@ -593,28 +736,31 @@ func (m *Miniredis) cmdLset(c *server.Peer, cmd string, args []string) { return } - key := args[0] - index, err := strconv.Atoi(args[1]) - if err != nil { - setDirty(c) - c.WriteError(msgInvalidInt) + var opts struct { + key string + index int + value string + } + opts.key = args[0] + if ok := optInt(c, args[1], &opts.index); !ok { return } - value := args[2] + opts.value = args[2] withTx(m, c, func(c *server.Peer, ctx *connCtx) { db := m.db(ctx.selectedDB) - if !db.exists(key) { + if !db.exists(opts.key) { c.WriteError(msgKeyNotFound) return } - if db.t(key) != "list" { + if db.t(opts.key) != keyTypeList { c.WriteError(msgWrongType) return } - l := db.listKeys[key] + l := db.listKeys[opts.key] + index := opts.index if index < 0 { index = len(l) + index } @@ -622,8 +768,8 @@ func (m *Miniredis) cmdLset(c *server.Peer, cmd string, args []string) { c.WriteError(msgOutOfRange) return } - l[index] = value - db.keyVersion[key]++ + l[index] = opts.value + db.incr(opts.key) c.WriteOK() }) @@ -643,41 +789,41 @@ func (m *Miniredis) cmdLtrim(c *server.Peer, cmd string, args []string) { return } - key := args[0] - start, err := strconv.Atoi(args[1]) - if err != nil { - setDirty(c) - c.WriteError(msgInvalidInt) + var opts struct { + key string + start int + end int + } + + opts.key = args[0] + if ok := optInt(c, args[1], &opts.start); !ok { return } - end, err := strconv.Atoi(args[2]) - if err != nil { - setDirty(c) - c.WriteError(msgInvalidInt) + if ok := optInt(c, args[2], &opts.end); !ok { return } withTx(m, c, func(c *server.Peer, ctx *connCtx) { db := m.db(ctx.selectedDB) - t, ok := db.keys[key] + t, ok := db.keys[opts.key] if !ok { c.WriteOK() return } - if t != "list" { + if t != keyTypeList { c.WriteError(msgWrongType) return } - l := db.listKeys[key] - rs, re := redisRange(len(l), start, end, false) + l := db.listKeys[opts.key] + rs, re := redisRange(len(l), opts.start, opts.end, false) l = l[rs:re] if len(l) == 0 { - db.del(key, true) + db.del(opts.key, true) } else { - db.listKeys[key] = l - db.keyVersion[key]++ + db.listKeys[opts.key] = l + db.incr(opts.key) } c.WriteOK() }) @@ -706,7 +852,7 @@ func (m *Miniredis) cmdRpoplpush(c *server.Peer, cmd string, args []string) { c.WriteNull() return } - if db.t(src) != "list" || (db.exists(dst) && db.t(dst) != "list") { + if db.t(src) != keyTypeList || (db.exists(dst) && db.t(dst) != keyTypeList) { c.WriteError(msgWrongType) return } @@ -730,39 +876,36 @@ func (m *Miniredis) cmdBrpoplpush(c *server.Peer, cmd string, args []string) { return } - src := args[0] - dst := args[1] - timeout, err := strconv.Atoi(args[2]) - if err != nil { - setDirty(c) - c.WriteError(msgInvalidTimeout) - return + var opts struct { + src string + dst string + timeout time.Duration } - if timeout < 0 { - setDirty(c) - c.WriteError(msgNegTimeout) + opts.src = args[0] + opts.dst = args[1] + if ok := optDuration(c, args[2], &opts.timeout); !ok { return } blocking( m, c, - time.Duration(timeout)*time.Second, + opts.timeout, func(c *server.Peer, ctx *connCtx) bool { db := m.db(ctx.selectedDB) - if !db.exists(src) { + if !db.exists(opts.src) { return false } - if db.t(src) != "list" || (db.exists(dst) && db.t(dst) != "list") { + if db.t(opts.src) != keyTypeList || (db.exists(opts.dst) && db.t(opts.dst) != keyTypeList) { c.WriteError(msgWrongType) return true } - if len(db.listKeys[src]) == 0 { + if len(db.listKeys[opts.src]) == 0 { return false } - elem := db.listPop(src) - db.listLpush(dst, elem) + elem := db.listPop(opts.src) + db.listLpush(opts.dst, elem) c.WriteBulk(elem) return true }, @@ -787,37 +930,131 @@ func (m *Miniredis) cmdLmove(c *server.Peer, cmd string, args []string) { return } - src, dst, srcDir, dstDir := args[0], args[1], strings.ToLower(args[2]), strings.ToLower(args[3]) + opts := struct { + src string + dst string + srcDir string + dstDir string + }{ + src: args[0], + dst: args[1], + srcDir: strings.ToLower(args[2]), + dstDir: strings.ToLower(args[3]), + } withTx(m, c, func(c *server.Peer, ctx *connCtx) { db := m.db(ctx.selectedDB) - if !db.exists(src) { + if !db.exists(opts.src) { c.WriteNull() return } - if db.t(src) != "list" || (db.exists(dst) && db.t(dst) != "list") { + if db.t(opts.src) != keyTypeList || (db.exists(opts.dst) && db.t(opts.dst) != keyTypeList) { c.WriteError(msgWrongType) return } var elem string - if srcDir == "left" { - elem = db.listLpop(src) - } else if srcDir == "right" { - elem = db.listPop(src) - } else { + switch opts.srcDir { + case "left": + elem = db.listLpop(opts.src) + case "right": + elem = db.listPop(opts.src) + default: c.WriteError(msgSyntaxError) return } - if dstDir == "left" { - db.listLpush(dst, elem) - } else if dstDir == "right" { - db.listPush(dst, elem) - } else { + switch opts.dstDir { + case "left": + db.listLpush(opts.dst, elem) + case "right": + db.listPush(opts.dst, elem) + default: c.WriteError(msgSyntaxError) return } c.WriteBulk(elem) }) } + +// BLMOVE +func (m *Miniredis) cmdBlmove(c *server.Peer, cmd string, args []string) { + if len(args) != 5 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + if m.checkPubsub(c, cmd) { + return + } + + opts := struct { + src string + dst string + srcDir string + dstDir string + timeout time.Duration + }{ + src: args[0], + dst: args[1], + srcDir: strings.ToLower(args[2]), + dstDir: strings.ToLower(args[3]), + } + if ok := optDuration(c, args[len(args)-1], &opts.timeout); !ok { + return + } + + blocking( + m, + c, + opts.timeout, + func(c *server.Peer, ctx *connCtx) bool { + db := m.db(ctx.selectedDB) + + if !db.exists(opts.src) { + return false + } + if db.t(opts.src) != keyTypeList || (db.exists(opts.dst) && db.t(opts.dst) != keyTypeList) { + c.WriteError(msgWrongType) + return true + } + + var ( + elem string + ttl = db.ttl[opts.src] // in case we empty the array (deletes the entry) + ) + switch opts.srcDir { + case "left": + elem = db.listLpop(opts.src) + case "right": + elem = db.listPop(opts.src) + default: + c.WriteError(msgSyntaxError) + return true + } + + switch opts.dstDir { + case "left": + db.listLpush(opts.dst, elem) + case "right": + db.listPush(opts.dst, elem) + default: + c.WriteError(msgSyntaxError) + return true + } + if ttl > 0 { + db.ttl[opts.dst] = ttl + } + + c.WriteBulk(elem) + return true + }, + func(c *server.Peer) { + // timeout + c.WriteLen(-1) + }, + ) +} diff --git a/vendor/github.com/alicebob/miniredis/v2/cmd_object.go b/vendor/github.com/alicebob/miniredis/v2/cmd_object.go new file mode 100644 index 000000000..b958a95cf --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/v2/cmd_object.go @@ -0,0 +1,58 @@ +package miniredis + +import ( + "fmt" + "strings" + + "github.com/alicebob/miniredis/v2/server" +) + +// commandsObject handles all object operations. +func commandsObject(m *Miniredis) { + m.srv.Register("OBJECT", m.cmdObject) +} + +// OBJECT +func (m *Miniredis) cmdObject(c *server.Peer, cmd string, args []string) { + if len(args) == 0 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + if m.checkPubsub(c, cmd) { + return + } + + switch sub := strings.ToLower(args[0]); sub { + case "idletime": + m.cmdObjectIdletime(c, args[1:]) + default: + setDirty(c) + c.WriteError(fmt.Sprintf(msgFObjectUsage, sub)) + } +} + +// OBJECT IDLETIME +func (m *Miniredis) cmdObjectIdletime(c *server.Peer, args []string) { + if len(args) != 1 { + setDirty(c) + c.WriteError(errWrongNumber("object|idletime")) + return + } + key := args[0] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + t, ok := db.lru[key] + if !ok { + c.WriteNull() + return + } + + c.WriteInt(int(db.master.effectiveNow().Sub(t).Seconds())) + }) +} diff --git a/vendor/github.com/alicebob/miniredis/v2/cmd_pubsub.go b/vendor/github.com/alicebob/miniredis/v2/cmd_pubsub.go index 70997be5a..0fc9f0de3 100644 --- a/vendor/github.com/alicebob/miniredis/v2/cmd_pubsub.go +++ b/vendor/github.com/alicebob/miniredis/v2/cmd_pubsub.go @@ -29,8 +29,9 @@ func (m *Miniredis) cmdSubscribe(c *server.Peer, cmd string, args []string) { if !m.handleAuth(c) { return } - if getCtx(c).nested { - c.WriteError(msgNotFromScripts) + ctx := getCtx(c) + if ctx.nested { + c.WriteError(msgNotFromScripts(ctx.nestedSHA)) return } @@ -53,8 +54,9 @@ func (m *Miniredis) cmdUnsubscribe(c *server.Peer, cmd string, args []string) { if !m.handleAuth(c) { return } - if getCtx(c).nested { - c.WriteError(msgNotFromScripts) + ctx := getCtx(c) + if ctx.nested { + c.WriteError(msgNotFromScripts(ctx.nestedSHA)) return } @@ -103,8 +105,9 @@ func (m *Miniredis) cmdPsubscribe(c *server.Peer, cmd string, args []string) { if !m.handleAuth(c) { return } - if getCtx(c).nested { - c.WriteError(msgNotFromScripts) + ctx := getCtx(c) + if ctx.nested { + c.WriteError(msgNotFromScripts(ctx.nestedSHA)) return } @@ -127,8 +130,9 @@ func (m *Miniredis) cmdPunsubscribe(c *server.Peer, cmd string, args []string) { if !m.handleAuth(c) { return } - if getCtx(c).nested { - c.WriteError(msgNotFromScripts) + ctx := getCtx(c) + if ctx.nested { + c.WriteError(msgNotFromScripts(ctx.nestedSHA)) return } @@ -212,7 +216,9 @@ func (m *Miniredis) cmdPubSub(c *server.Peer, cmd string, args []string) { case "NUMPAT": argsOk = len(subargs) == 0 default: - argsOk = false + setDirty(c) + c.WriteError(fmt.Sprintf(msgFPubsubUsageSimple, subcommand)) + return } if !argsOk { diff --git a/vendor/github.com/alicebob/miniredis/v2/cmd_scripting.go b/vendor/github.com/alicebob/miniredis/v2/cmd_scripting.go index ef10aaef0..188a15e97 100644 --- a/vendor/github.com/alicebob/miniredis/v2/cmd_scripting.go +++ b/vendor/github.com/alicebob/miniredis/v2/cmd_scripting.go @@ -7,11 +7,12 @@ import ( "io" "strconv" "strings" + "sync" - luajson "github.com/alicebob/gopher-json" lua "github.com/yuin/gopher-lua" "github.com/yuin/gopher-lua/parse" + luajson "github.com/alicebob/miniredis/v2/gopher-json" "github.com/alicebob/miniredis/v2/server" ) @@ -21,9 +22,13 @@ func commandsScripting(m *Miniredis) { m.srv.Register("SCRIPT", m.cmdScript) } +var ( + parsedScripts = sync.Map{} +) + // Execute lua. Needs to run m.Lock()ed, from within withTx(). // Returns true if the lua was OK (and hence should be cached). -func (m *Miniredis) runLuaScript(c *server.Peer, script string, args []string) bool { +func (m *Miniredis) runLuaScript(c *server.Peer, sha, script string, args []string) bool { l := lua.NewState(lua.Options{SkipOpenLibs: true}) defer l.Close() @@ -80,7 +85,7 @@ func (m *Miniredis) runLuaScript(c *server.Peer, script string, args []string) b } l.SetGlobal("ARGV", argvTable) - redisFuncs, redisConstants := mkLua(m.srv, c) + redisFuncs, redisConstants := mkLua(m.srv, c, sha) // Register command handlers l.Push(l.NewFunction(func(l *lua.LState) int { mod := l.RegisterModule("redis", redisFuncs).(*lua.LTable) @@ -91,20 +96,60 @@ func (m *Miniredis) runLuaScript(c *server.Peer, script string, args []string) b return 1 })) - l.DoString(protectGlobals) + _ = doScript(l, protectGlobals) l.Push(lua.LString("redis")) l.Call(1, 0) - if err := l.DoString(script); err != nil { - c.WriteError(errLuaParseError(err)) + // lua can call redis.setresp(...), but it's tmp state. + oldresp := c.Resp3 + if err := doScript(l, script); err != nil { + c.WriteError(err.Error()) return false } luaToRedis(l, c, l.Get(1)) + c.Resp3 = oldresp + c.SwitchResp3 = nil return true } +// doScript pre-compiles the given script into a Lua prototype, +// then executes the pre-compiled function against the given lua state. +// +// This is thread-safe. +func doScript(l *lua.LState, script string) error { + proto, err := compile(script) + if err != nil { + return fmt.Errorf(errLuaParseError(err)) + } + + lfunc := l.NewFunctionFromProto(proto) + l.Push(lfunc) + if err := l.PCall(0, lua.MultRet, nil); err != nil { + // ensure we wrap with the correct format. + return fmt.Errorf(errLuaParseError(err)) + } + + return nil +} + +func compile(script string) (*lua.FunctionProto, error) { + if val, ok := parsedScripts.Load(script); ok { + return val.(*lua.FunctionProto), nil + } + chunk, err := parse.Parse(strings.NewReader(script), "") + if err != nil { + return nil, err + } + proto, err := lua.Compile(chunk, "") + if err != nil { + return nil, err + } + parsedScripts.Store(script, proto) + return proto, nil +} + func (m *Miniredis) cmdEval(c *server.Peer, cmd string, args []string) { if len(args) < 2 { setDirty(c) @@ -117,18 +162,18 @@ func (m *Miniredis) cmdEval(c *server.Peer, cmd string, args []string) { if m.checkPubsub(c, cmd) { return } - - if getCtx(c).nested { - c.WriteError(msgNotFromScripts) + ctx := getCtx(c) + if ctx.nested { + c.WriteError(msgNotFromScripts(ctx.nestedSHA)) return } script, args := args[0], args[1:] withTx(m, c, func(c *server.Peer, ctx *connCtx) { - ok := m.runLuaScript(c, script, args) + sha := sha1Hex(script) + ok := m.runLuaScript(c, sha, script, args) if ok { - sha := sha1Hex(script) m.scripts[sha] = script } }) @@ -146,8 +191,9 @@ func (m *Miniredis) cmdEvalsha(c *server.Peer, cmd string, args []string) { if m.checkPubsub(c, cmd) { return } - if getCtx(c).nested { - c.WriteError(msgNotFromScripts) + ctx := getCtx(c) + if ctx.nested { + c.WriteError(msgNotFromScripts(ctx.nestedSHA)) return } @@ -160,7 +206,7 @@ func (m *Miniredis) cmdEvalsha(c *server.Peer, cmd string, args []string) { return } - m.runLuaScript(c, script, args) + m.runLuaScript(c, sha, script, args) }) } @@ -177,30 +223,62 @@ func (m *Miniredis) cmdScript(c *server.Peer, cmd string, args []string) { return } - if getCtx(c).nested { - c.WriteError(msgNotFromScripts) + ctx := getCtx(c) + if ctx.nested { + c.WriteError(msgNotFromScripts(ctx.nestedSHA)) return } - subcmd, args := args[0], args[1:] + var opts struct { + subcmd string + script string + } - withTx(m, c, func(c *server.Peer, ctx *connCtx) { - switch strings.ToLower(subcmd) { - case "load": - if len(args) != 1 { - c.WriteError(fmt.Sprintf(msgFScriptUsage, "LOAD")) - return + opts.subcmd, args = args[0], args[1:] + + switch strings.ToLower(opts.subcmd) { + case "load": + if len(args) != 1 { + setDirty(c) + c.WriteError(fmt.Sprintf(msgFScriptUsage, "LOAD")) + return + } + opts.script = args[0] + case "exists": + if len(args) == 0 { + setDirty(c) + c.WriteError(errWrongNumber("script|exists")) + return + } + case "flush": + if len(args) == 1 { + switch strings.ToUpper(args[0]) { + case "SYNC", "ASYNC": + args = args[1:] + default: } - script := args[0] + } + if len(args) != 0 { + setDirty(c) + c.WriteError(msgScriptFlush) + return + } + default: + setDirty(c) + c.WriteError(fmt.Sprintf(msgFScriptUsageSimple, strings.ToUpper(opts.subcmd))) + return + } - if _, err := parse.Parse(strings.NewReader(script), "user_script"); err != nil { + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + switch strings.ToLower(opts.subcmd) { + case "load": + if _, err := parse.Parse(strings.NewReader(opts.script), "user_script"); err != nil { c.WriteError(errLuaParseError(err)) return } - sha := sha1Hex(script) - m.scripts[sha] = script + sha := sha1Hex(opts.script) + m.scripts[sha] = opts.script c.WriteBulk(sha) - case "exists": c.WriteLen(len(args)) for _, arg := range args { @@ -210,25 +288,9 @@ func (m *Miniredis) cmdScript(c *server.Peer, cmd string, args []string) { c.WriteInt(0) } } - case "flush": - if len(args) == 1 { - switch strings.ToUpper(args[0]) { - case "SYNC", "ASYNC": - args = args[1:] - default: - } - } - if len(args) != 0 { - c.WriteError(msgScriptFlush) - return - } - m.scripts = map[string]string{} c.WriteOK() - - default: - c.WriteError(fmt.Sprintf(msgFScriptUsage, strings.ToUpper(subcmd))) } }) } diff --git a/vendor/github.com/alicebob/miniredis/v2/cmd_server.go b/vendor/github.com/alicebob/miniredis/v2/cmd_server.go index 223651d39..5fe55dd4f 100644 --- a/vendor/github.com/alicebob/miniredis/v2/cmd_server.go +++ b/vendor/github.com/alicebob/miniredis/v2/cmd_server.go @@ -3,17 +3,84 @@ package miniredis import ( + "fmt" "strconv" "strings" "github.com/alicebob/miniredis/v2/server" + "github.com/alicebob/miniredis/v2/size" ) func commandsServer(m *Miniredis) { + m.srv.Register("COMMAND", m.cmdCommand) m.srv.Register("DBSIZE", m.cmdDbsize) m.srv.Register("FLUSHALL", m.cmdFlushall) m.srv.Register("FLUSHDB", m.cmdFlushdb) + m.srv.Register("INFO", m.cmdInfo) m.srv.Register("TIME", m.cmdTime) + m.srv.Register("MEMORY", m.cmdMemory) +} + +// MEMORY +func (m *Miniredis) cmdMemory(c *server.Peer, cmd string, args []string) { + if len(args) == 0 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + if m.checkPubsub(c, cmd) { + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + cmd, args := strings.ToLower(args[0]), args[1:] + switch cmd { + case "usage": + if len(args) < 1 { + setDirty(c) + c.WriteError(errWrongNumber("memory|usage")) + return + } + if len(args) > 1 { + setDirty(c) + c.WriteError(msgSyntaxError) + return + } + + var ( + value interface{} + ok bool + ) + switch db.keys[args[0]] { + case keyTypeString: + value, ok = db.stringKeys[args[0]] + case keyTypeSet: + value, ok = db.setKeys[args[0]] + case keyTypeHash: + value, ok = db.hashKeys[args[0]] + case keyTypeList: + value, ok = db.listKeys[args[0]] + case keyTypeHll: + value, ok = db.hllKeys[args[0]] + case keyTypeSortedSet: + value, ok = db.sortedsetKeys[args[0]] + case keyTypeStream: + value, ok = db.streamKeys[args[0]] + } + if !ok { + c.WriteNull() + return + } + c.WriteInt(size.Of(value)) + default: + c.WriteError(fmt.Sprintf(msgMemorySubcommand, strings.ToUpper(cmd))) + } + }) } // DBSIZE @@ -100,8 +167,8 @@ func (m *Miniredis) cmdTime(c *server.Peer, cmd string, args []string) { withTx(m, c, func(c *server.Peer, ctx *connCtx) { now := m.effectiveNow() nanos := now.UnixNano() - seconds := nanos / 1000000000 - microseconds := (nanos / 1000) % 1000000 + seconds := nanos / 1_000_000_000 + microseconds := (nanos / 1_000) % 1_000_000 c.WriteLen(2) c.WriteBulk(strconv.FormatInt(seconds, 10)) diff --git a/vendor/github.com/alicebob/miniredis/v2/cmd_set.go b/vendor/github.com/alicebob/miniredis/v2/cmd_set.go index a9cdf411a..12e4d5845 100644 --- a/vendor/github.com/alicebob/miniredis/v2/cmd_set.go +++ b/vendor/github.com/alicebob/miniredis/v2/cmd_set.go @@ -3,6 +3,7 @@ package miniredis import ( + "fmt" "strconv" "strings" @@ -15,10 +16,12 @@ func commandsSet(m *Miniredis) { m.srv.Register("SCARD", m.cmdScard) m.srv.Register("SDIFF", m.cmdSdiff) m.srv.Register("SDIFFSTORE", m.cmdSdiffstore) + m.srv.Register("SINTERCARD", m.cmdSintercard) m.srv.Register("SINTER", m.cmdSinter) m.srv.Register("SINTERSTORE", m.cmdSinterstore) m.srv.Register("SISMEMBER", m.cmdSismember) m.srv.Register("SMEMBERS", m.cmdSmembers) + m.srv.Register("SMISMEMBER", m.cmdSmismember) m.srv.Register("SMOVE", m.cmdSmove) m.srv.Register("SPOP", m.cmdSpop) m.srv.Register("SRANDMEMBER", m.cmdSrandmember) @@ -47,7 +50,7 @@ func (m *Miniredis) cmdSadd(c *server.Peer, cmd string, args []string) { withTx(m, c, func(c *server.Peer, ctx *connCtx) { db := m.db(ctx.selectedDB) - if db.exists(key) && db.t(key) != "set" { + if db.exists(key) && db.t(key) != keyTypeSet { c.WriteError(ErrWrongType.Error()) return } @@ -217,6 +220,77 @@ func (m *Miniredis) cmdSinterstore(c *server.Peer, cmd string, args []string) { }) } +// SINTERCARD +func (m *Miniredis) cmdSintercard(c *server.Peer, cmd string, args []string) { + if len(args) < 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + if m.checkPubsub(c, cmd) { + return + } + + opts := struct { + keys []string + limit int + }{} + + numKeys, err := strconv.Atoi(args[0]) + if err != nil { + setDirty(c) + c.WriteError("ERR numkeys should be greater than 0") + return + } + if numKeys < 1 { + setDirty(c) + c.WriteError("ERR numkeys should be greater than 0") + return + } + + args = args[1:] + if len(args) < numKeys { + setDirty(c) + c.WriteError("ERR Number of keys can't be greater than number of args") + return + } + opts.keys = args[:numKeys] + + args = args[numKeys:] + if len(args) == 2 && strings.ToLower(args[0]) == "limit" { + l, err := strconv.Atoi(args[1]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + if l < 0 { + setDirty(c) + c.WriteError(msgLimitIsNegative) + return + } + opts.limit = l + } else if len(args) > 0 { + setDirty(c) + c.WriteError(msgSyntaxError) + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + count, err := db.setIntercard(opts.keys, opts.limit) + if err != nil { + c.WriteError(err.Error()) + return + } + c.WriteInt(count) + }) +} + // SISMEMBER func (m *Miniredis) cmdSismember(c *server.Peer, cmd string, args []string) { if len(args) != 2 { @@ -292,6 +366,50 @@ func (m *Miniredis) cmdSmembers(c *server.Peer, cmd string, args []string) { }) } +// SMISMEMBER +func (m *Miniredis) cmdSmismember(c *server.Peer, cmd string, args []string) { + if len(args) < 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + if m.checkPubsub(c, cmd) { + return + } + + key, values := args[0], args[1:] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if !db.exists(key) { + c.WriteLen(len(values)) + for range values { + c.WriteInt(0) + } + return + } + + if db.t(key) != "set" { + c.WriteError(ErrWrongType.Error()) + return + } + + c.WriteLen(len(values)) + for _, value := range values { + if db.setIsMember(key, value) { + c.WriteInt(1) + } else { + c.WriteInt(0) + } + } + return + }) +} + // SMOVE func (m *Miniredis) cmdSmove(c *server.Peer, cmd string, args []string) { if len(args) != 3 { @@ -399,12 +517,14 @@ func (m *Miniredis) cmdSpop(c *server.Peer, cmd string, args []string) { } var deleted []string + members := db.setMembers(opts.key) for i := 0; i < opts.count; i++ { - members := db.setMembers(opts.key) if len(members) == 0 { break } - member := members[m.randIntn(len(members))] + i := m.randIntn(len(members)) + member := members[i] + members = delElem(members, i) db.setRem(opts.key, member) deleted = append(deleted, member) } @@ -462,6 +582,10 @@ func (m *Miniredis) cmdSrandmember(c *server.Peer, cmd string, args []string) { db := m.db(ctx.selectedDB) if !db.exists(key) { + if withCount { + c.WriteLen(0) + return + } c.WriteNull() return } @@ -609,17 +733,22 @@ func (m *Miniredis) cmdSscan(c *server.Peer, cmd string, args []string) { return } - key := args[0] - cursor, err := strconv.Atoi(args[1]) - if err != nil { - setDirty(c) - c.WriteError(msgInvalidCursor) + var opts struct { + key string + value int + cursor int + count int + withMatch bool + match string + } + + opts.key = args[0] + if ok := optIntErr(c, args[1], &opts.cursor, msgInvalidCursor); !ok { return } args = args[2:] + // MATCH and COUNT options - var withMatch bool - var match string for len(args) > 0 { if strings.ToLower(args[0]) == "count" { if len(args) < 2 { @@ -627,13 +756,18 @@ func (m *Miniredis) cmdSscan(c *server.Peer, cmd string, args []string) { c.WriteError(msgSyntaxError) return } - _, err := strconv.Atoi(args[1]) - if err != nil { + count, err := strconv.Atoi(args[1]) + if err != nil || count < 0 { setDirty(c) c.WriteError(msgInvalidInt) return } - // We do nothing with count. + if count == 0 { + setDirty(c) + c.WriteError(msgSyntaxError) + return + } + opts.count = count args = args[2:] continue } @@ -643,8 +777,8 @@ func (m *Miniredis) cmdSscan(c *server.Peer, cmd string, args []string) { c.WriteError(msgSyntaxError) return } - withMatch = true - match = args[1] + opts.withMatch = true + opts.match = args[1] args = args[2:] continue } @@ -656,29 +790,47 @@ func (m *Miniredis) cmdSscan(c *server.Peer, cmd string, args []string) { withTx(m, c, func(c *server.Peer, ctx *connCtx) { db := m.db(ctx.selectedDB) // return _all_ (matched) keys every time - - if cursor != 0 { + if db.exists(opts.key) && db.t(opts.key) != "set" { + c.WriteError(ErrWrongType.Error()) + return + } + members := db.setMembers(opts.key) + if opts.withMatch { + members, _ = matchKeys(members, opts.match) + } + low := opts.cursor + high := low + opts.count + // validate high is correct + if high > len(members) || high == 0 { + high = len(members) + } + if opts.cursor > high { // invalid cursor c.WriteLen(2) c.WriteBulk("0") // no next cursor c.WriteLen(0) // no elements return } - if db.exists(key) && db.t(key) != "set" { - c.WriteError(ErrWrongType.Error()) - return + cursorValue := low + opts.count + if cursorValue > len(members) { + cursorValue = 0 // no next cursor } - - members := db.setMembers(key) - if withMatch { - members, _ = matchKeys(members, match) - } - + members = members[low:high] c.WriteLen(2) - c.WriteBulk("0") // no next cursor + c.WriteBulk(fmt.Sprintf("%d", cursorValue)) c.WriteLen(len(members)) for _, k := range members { c.WriteBulk(k) } + }) } + +func delElem(ls []string, i int) []string { + // this swap+truncate is faster but changes behaviour: + // ls[i] = ls[len(ls)-1] + // ls = ls[:len(ls)-1] + // so we do the dumb thing: + ls = append(ls[:i], ls[i+1:]...) + return ls +} diff --git a/vendor/github.com/alicebob/miniredis/v2/cmd_sorted_set.go b/vendor/github.com/alicebob/miniredis/v2/cmd_sorted_set.go index 75b540baf..85bc569f1 100644 --- a/vendor/github.com/alicebob/miniredis/v2/cmd_sorted_set.go +++ b/vendor/github.com/alicebob/miniredis/v2/cmd_sorted_set.go @@ -4,6 +4,7 @@ package miniredis import ( "errors" + "fmt" "math" "sort" "strconv" @@ -18,7 +19,8 @@ func commandsSortedSet(m *Miniredis) { m.srv.Register("ZCARD", m.cmdZcard) m.srv.Register("ZCOUNT", m.cmdZcount) m.srv.Register("ZINCRBY", m.cmdZincrby) - m.srv.Register("ZINTERSTORE", m.cmdZinterstore) + m.srv.Register("ZINTER", m.makeCmdZinter(false)) + m.srv.Register("ZINTERSTORE", m.makeCmdZinter(true)) m.srv.Register("ZLEXCOUNT", m.cmdZlexcount) m.srv.Register("ZRANGE", m.cmdZrange) m.srv.Register("ZRANGEBYLEX", m.makeCmdZrangebylex(false)) @@ -33,6 +35,7 @@ func commandsSortedSet(m *Miniredis) { m.srv.Register("ZREVRANGEBYSCORE", m.makeCmdZrangebyscore(true)) m.srv.Register("ZREVRANK", m.makeCmdZrank(true)) m.srv.Register("ZSCORE", m.cmdZscore) + m.srv.Register("ZMSCORE", m.cmdZMscore) m.srv.Register("ZUNION", m.cmdZunion) m.srv.Register("ZUNIONSTORE", m.cmdZunionstore) m.srv.Register("ZSCAN", m.cmdZscan) @@ -55,42 +58,44 @@ func (m *Miniredis) cmdZadd(c *server.Peer, cmd string, args []string) { return } - key, args := args[0], args[1:] - var ( - nx = false - xx = false - gt = false - lt = false - ch = false - incr = false - elems = map[string]float64{} - ) + var opts struct { + key string + nx bool + xx bool + gt bool + lt bool + ch bool + incr bool + } + elems := map[string]float64{} + opts.key = args[0] + args = args[1:] outer: for len(args) > 0 { switch strings.ToUpper(args[0]) { case "NX": - nx = true + opts.nx = true args = args[1:] continue case "XX": - xx = true + opts.xx = true args = args[1:] continue case "GT": - gt = true + opts.gt = true args = args[1:] continue case "LT": - lt = true + opts.lt = true args = args[1:] continue case "CH": - ch = true + opts.ch = true args = args[1:] continue case "INCR": - incr = true + opts.incr = true args = args[1:] continue default: @@ -114,21 +119,21 @@ outer: args = args[2:] } - if xx && nx { + if opts.xx && opts.nx { setDirty(c) c.WriteError(msgXXandNX) return } - if gt && lt || - gt && nx || - lt && nx { + if opts.gt && opts.lt || + opts.gt && opts.nx || + opts.lt && opts.nx { setDirty(c) c.WriteError(msgGTLTandNX) return } - if incr && len(elems) > 1 { + if opts.incr && len(elems) > 1 { setDirty(c) c.WriteError(msgSingleElementPair) return @@ -137,22 +142,22 @@ outer: withTx(m, c, func(c *server.Peer, ctx *connCtx) { db := m.db(ctx.selectedDB) - if db.exists(key) && db.t(key) != "zset" { + if db.exists(opts.key) && db.t(opts.key) != keyTypeSortedSet { c.WriteError(ErrWrongType.Error()) return } - if incr { + if opts.incr { for member, delta := range elems { - if nx && db.ssetExists(key, member) { + if opts.nx && db.ssetExists(opts.key, member) { c.WriteNull() return } - if xx && !db.ssetExists(key, member) { + if opts.xx && !db.ssetExists(opts.key, member) { c.WriteNull() return } - newScore := db.ssetIncrby(key, member, delta) + newScore := db.ssetIncrby(opts.key, member, delta) c.WriteFloat(newScore) } return @@ -160,23 +165,24 @@ outer: res := 0 for member, score := range elems { - if nx && db.ssetExists(key, member) { + exists := db.ssetExists(opts.key, member) + if opts.nx && exists { continue } - if xx && !db.ssetExists(key, member) { + if opts.xx && !exists { continue } - old := db.ssetScore(key, member) - if gt && score <= old { + old := db.ssetScore(opts.key, member) + if opts.gt && exists && score <= old { continue } - if lt && score >= old { + if opts.lt && exists && score >= old { continue } - if db.ssetAdd(key, score, member) { + if db.ssetAdd(opts.key, score, member) { res++ } else { - if ch && old != score { + if opts.ch && old != score { // if 'CH' is specified, only count changed keys res++ } @@ -210,7 +216,7 @@ func (m *Miniredis) cmdZcard(c *server.Peer, cmd string, args []string) { return } - if db.t(key) != "zset" { + if db.t(key) != keyTypeSortedSet { c.WriteError(ErrWrongType.Error()) return } @@ -233,14 +239,25 @@ func (m *Miniredis) cmdZcount(c *server.Peer, cmd string, args []string) { return } - key := args[0] - min, minIncl, err := parseFloatRange(args[1]) + var ( + opts struct { + key string + min float64 + minIncl bool + max float64 + maxIncl bool + } + err error + ) + + opts.key = args[0] + opts.min, opts.minIncl, err = parseFloatRange(args[1]) if err != nil { setDirty(c) c.WriteError(msgInvalidMinMax) return } - max, maxIncl, err := parseFloatRange(args[2]) + opts.max, opts.maxIncl, err = parseFloatRange(args[2]) if err != nil { setDirty(c) c.WriteError(msgInvalidMinMax) @@ -250,18 +267,18 @@ func (m *Miniredis) cmdZcount(c *server.Peer, cmd string, args []string) { withTx(m, c, func(c *server.Peer, ctx *connCtx) { db := m.db(ctx.selectedDB) - if !db.exists(key) { + if !db.exists(opts.key) { c.WriteInt(0) return } - if db.t(key) != "zset" { + if db.t(opts.key) != keyTypeSortedSet { c.WriteError(ErrWrongType.Error()) return } - members := db.ssetElements(key) - members = withSSRange(members, min, minIncl, max, maxIncl) + members := db.ssetElements(opts.key) + members = withSSRange(members, opts.min, opts.minIncl, opts.max, opts.maxIncl) c.WriteInt(len(members)) }) } @@ -280,166 +297,218 @@ func (m *Miniredis) cmdZincrby(c *server.Peer, cmd string, args []string) { return } - key := args[0] - delta, err := strconv.ParseFloat(args[1], 64) + var opts struct { + key string + delta float64 + member string + } + + opts.key = args[0] + d, err := strconv.ParseFloat(args[1], 64) if err != nil { setDirty(c) c.WriteError(msgInvalidFloat) return } - member := args[2] + opts.delta = d + opts.member = args[2] withTx(m, c, func(c *server.Peer, ctx *connCtx) { db := m.db(ctx.selectedDB) - if db.exists(key) && db.t(key) != "zset" { + if db.exists(opts.key) && db.t(opts.key) != keyTypeSortedSet { c.WriteError(msgWrongType) return } - newScore := db.ssetIncrby(key, member, delta) + newScore := db.ssetIncrby(opts.key, opts.member, opts.delta) c.WriteFloat(newScore) }) } -// ZINTERSTORE -func (m *Miniredis) cmdZinterstore(c *server.Peer, cmd string, args []string) { - if len(args) < 3 { - setDirty(c) - c.WriteError(errWrongNumber(cmd)) - return - } - if !m.handleAuth(c) { - return - } - if m.checkPubsub(c, cmd) { - return - } +// ZINTERSTORE and ZINTER +func (m *Miniredis) makeCmdZinter(store bool) func(c *server.Peer, cmd string, args []string) { + return func(c *server.Peer, cmd string, args []string) { + minArgs := 2 + if store { + minArgs++ + } + if len(args) < minArgs { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + if m.checkPubsub(c, cmd) { + return + } - destination := args[0] - numKeys, err := strconv.Atoi(args[1]) - if err != nil { - setDirty(c) - c.WriteError(msgInvalidInt) - return - } - args = args[2:] - if len(args) < numKeys { - setDirty(c) - c.WriteError(msgSyntaxError) - return - } - if numKeys <= 0 { - setDirty(c) - c.WriteError("ERR at least 1 input key is needed for ZUNIONSTORE/ZINTERSTORE") - return - } - keys := args[:numKeys] - args = args[numKeys:] + var opts = struct { + Store bool // if true this is ZINTERSTORE + Destination string // only relevant if $store is true + Keys []string + Aggregate string + WithWeights bool + Weights []float64 + WithScores bool // only for ZINTER + }{ + Store: store, + Aggregate: "sum", + } - withWeights := false - weights := []float64{} - aggregate := "sum" - for len(args) > 0 { - switch strings.ToLower(args[0]) { - case "weights": - if len(args) < numKeys+1 { - setDirty(c) - c.WriteError(msgSyntaxError) - return - } - for i := 0; i < numKeys; i++ { - f, err := strconv.ParseFloat(args[i+1], 64) - if err != nil { + if store { + opts.Destination = args[0] + args = args[1:] + } + numKeys, err := strconv.Atoi(args[0]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + args = args[1:] + if len(args) < numKeys { + setDirty(c) + c.WriteError(msgSyntaxError) + return + } + if numKeys <= 0 { + setDirty(c) + c.WriteError("ERR at least 1 input key is needed for ZUNIONSTORE/ZINTERSTORE") + return + } + opts.Keys = args[:numKeys] + args = args[numKeys:] + + for len(args) > 0 { + switch strings.ToLower(args[0]) { + case "weights": + if len(args) < numKeys+1 { setDirty(c) - c.WriteError("ERR weight value is not a float") + c.WriteError(msgSyntaxError) return } - weights = append(weights, f) - } - withWeights = true - args = args[numKeys+1:] - case "aggregate": - if len(args) < 2 { - setDirty(c) - c.WriteError(msgSyntaxError) - return - } - aggregate = strings.ToLower(args[1]) - switch aggregate { - case "sum", "min", "max": + for i := 0; i < numKeys; i++ { + f, err := strconv.ParseFloat(args[i+1], 64) + if err != nil { + setDirty(c) + c.WriteError("ERR weight value is not a float") + return + } + opts.Weights = append(opts.Weights, f) + } + opts.WithWeights = true + args = args[numKeys+1:] + case "aggregate": + if len(args) < 2 { + setDirty(c) + c.WriteError(msgSyntaxError) + return + } + aggregate := strings.ToLower(args[1]) + switch aggregate { + case "sum", "min", "max": + opts.Aggregate = aggregate + default: + setDirty(c) + c.WriteError(msgSyntaxError) + return + } + args = args[2:] + case "withscores": + if store { + setDirty(c) + c.WriteError(msgSyntaxError) + return + } + opts.WithScores = true + args = args[1:] default: setDirty(c) c.WriteError(msgSyntaxError) return } - args = args[2:] - default: - setDirty(c) - c.WriteError(msgSyntaxError) - return } - } - - withTx(m, c, func(c *server.Peer, ctx *connCtx) { - db := m.db(ctx.selectedDB) - db.del(destination, true) - // We collect everything and remove all keys which turned out not to be - // present in every set. - sset := map[string]float64{} - counts := map[string]int{} - for i, key := range keys { - if !db.exists(key) { - continue - } + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) - var set map[string]float64 - switch db.t(key) { - case "set": - set = map[string]float64{} - for elem := range db.setKeys[key] { - set[elem] = 1.0 - } - case "zset": - set = db.sortedSet(key) - default: - c.WriteError(msgWrongType) - return - } - for member, score := range set { - if withWeights { - score *= weights[i] - } - counts[member]++ - old, ok := sset[member] - if !ok { - sset[member] = score + // We collect everything and remove all keys which turned out not to be + // present in every set. + sset := map[string]float64{} + counts := map[string]int{} + for i, key := range opts.Keys { + if !db.exists(key) { continue } - switch aggregate { + + var set map[string]float64 + switch db.t(key) { + case keyTypeSet: + set = map[string]float64{} + for elem := range db.setKeys[key] { + set[elem] = 1.0 + } + case keyTypeSortedSet: + set = db.sortedSet(key) default: - panic("Invalid aggregate") - case "sum": - sset[member] += score - case "min": - if score < old { - sset[member] = score + c.WriteError(msgWrongType) + return + } + for member, score := range set { + if opts.WithWeights { + score *= opts.Weights[i] } - case "max": - if score > old { + counts[member]++ + old, ok := sset[member] + if !ok { sset[member] = score + continue + } + switch opts.Aggregate { + default: + panic("Invalid aggregate") + case "sum": + sset[member] += score + case "min": + if score < old { + sset[member] = score + } + case "max": + if score > old { + sset[member] = score + } } } } - } - for key, count := range counts { - if count != numKeys { - delete(sset, key) + for key, count := range counts { + if count != numKeys { + delete(sset, key) + } } - } - db.ssetSet(destination, sset) - c.WriteInt(len(sset)) - }) + + if opts.Store { + // ZINTERSTORE mode + db.del(opts.Destination, true) + db.ssetSet(opts.Destination, sset) + c.WriteInt(len(sset)) + return + } + // ZINTER mode + size := len(sset) + if opts.WithScores { + size *= 2 + } + c.WriteLen(size) + for _, l := range sortedKeys(sset) { + c.WriteBulk(l) + if opts.WithScores { + c.WriteFloat(sset[l]) + } + } + }) + } } // ZLEXCOUNT @@ -481,7 +550,7 @@ func (m *Miniredis) cmdZlexcount(c *server.Peer, cmd string, args []string) { return } - if db.t(opts.Key) != "zset" { + if db.t(opts.Key) != keyTypeSortedSet { c.WriteError(ErrWrongType.Error()) return } @@ -739,7 +808,7 @@ func (m *Miniredis) makeCmdZrangebyscore(reverse bool) server.Cmd { // ZRANK and ZREVRANK func (m *Miniredis) makeCmdZrank(reverse bool) server.Cmd { return func(c *server.Peer, cmd string, args []string) { - if len(args) != 2 { + if len(args) < 2 { setDirty(c) c.WriteError(errWrongNumber(cmd)) return @@ -756,12 +825,28 @@ func (m *Miniredis) makeCmdZrank(reverse bool) server.Cmd { withTx(m, c, func(c *server.Peer, ctx *connCtx) { db := m.db(ctx.selectedDB) + withScore := false + if len(args) > 0 && strings.ToUpper(args[len(args)-1]) == "WITHSCORE" { + withScore = true + args = args[:len(args)-1] + } + + if len(args) > 2 { + setDirty(c) + c.WriteError(msgSyntaxError) + return + } + if !db.exists(key) { - c.WriteNull() + if withScore { + c.WriteLen(-1) + } else { + c.WriteNull() + } return } - if db.t(key) != "zset" { + if db.t(key) != keyTypeSortedSet { c.WriteError(ErrWrongType.Error()) return } @@ -772,10 +857,21 @@ func (m *Miniredis) makeCmdZrank(reverse bool) server.Cmd { } rank, ok := db.ssetRank(key, member, direction) if !ok { - c.WriteNull() + if withScore { + c.WriteLen(-1) + } else { + c.WriteNull() + } return } - c.WriteInt(rank) + + if withScore { + c.WriteLen(2) + c.WriteInt(rank) + c.WriteFloat(db.ssetScore(key, member)) + } else { + c.WriteInt(rank) + } }) } } @@ -804,7 +900,7 @@ func (m *Miniredis) cmdZrem(c *server.Peer, cmd string, args []string) { return } - if db.t(key) != "zset" { + if db.t(key) != keyTypeSortedSet { c.WriteError(ErrWrongType.Error()) return } @@ -858,7 +954,7 @@ func (m *Miniredis) cmdZremrangebylex(c *server.Peer, cmd string, args []string) return } - if db.t(opts.Key) != "zset" { + if db.t(opts.Key) != keyTypeSortedSet { c.WriteError(ErrWrongType.Error()) return } @@ -889,37 +985,37 @@ func (m *Miniredis) cmdZremrangebyrank(c *server.Peer, cmd string, args []string return } - key := args[0] - start, err := strconv.Atoi(args[1]) - if err != nil { - setDirty(c) - c.WriteError(msgInvalidInt) + var opts struct { + key string + start int + end int + } + + opts.key = args[0] + if ok := optInt(c, args[1], &opts.start); !ok { return } - end, err := strconv.Atoi(args[2]) - if err != nil { - setDirty(c) - c.WriteError(msgInvalidInt) + if ok := optInt(c, args[2], &opts.end); !ok { return } withTx(m, c, func(c *server.Peer, ctx *connCtx) { db := m.db(ctx.selectedDB) - if !db.exists(key) { + if !db.exists(opts.key) { c.WriteInt(0) return } - if db.t(key) != "zset" { + if db.t(opts.key) != keyTypeSortedSet { c.WriteError(ErrWrongType.Error()) return } - members := db.ssetMembers(key) - rs, re := redisRange(len(members), start, end, false) + members := db.ssetMembers(opts.key) + rs, re := redisRange(len(members), opts.start, opts.end, false) for _, el := range members[rs:re] { - db.ssetRem(key, el) + db.ssetRem(opts.key, el) } c.WriteInt(re - rs) }) @@ -939,14 +1035,24 @@ func (m *Miniredis) cmdZremrangebyscore(c *server.Peer, cmd string, args []strin return } - key := args[0] - min, minIncl, err := parseFloatRange(args[1]) + var ( + opts struct { + key string + min float64 + minIncl bool + max float64 + maxIncl bool + } + err error + ) + opts.key = args[0] + opts.min, opts.minIncl, err = parseFloatRange(args[1]) if err != nil { setDirty(c) c.WriteError(msgInvalidMinMax) return } - max, maxIncl, err := parseFloatRange(args[2]) + opts.max, opts.maxIncl, err = parseFloatRange(args[2]) if err != nil { setDirty(c) c.WriteError(msgInvalidMinMax) @@ -956,21 +1062,21 @@ func (m *Miniredis) cmdZremrangebyscore(c *server.Peer, cmd string, args []strin withTx(m, c, func(c *server.Peer, ctx *connCtx) { db := m.db(ctx.selectedDB) - if !db.exists(key) { + if !db.exists(opts.key) { c.WriteInt(0) return } - if db.t(key) != "zset" { + if db.t(opts.key) != keyTypeSortedSet { c.WriteError(ErrWrongType.Error()) return } - members := db.ssetElements(key) - members = withSSRange(members, min, minIncl, max, maxIncl) + members := db.ssetElements(opts.key) + members = withSSRange(members, opts.min, opts.minIncl, opts.max, opts.maxIncl) for _, el := range members { - db.ssetRem(key, el.member) + db.ssetRem(opts.key, el.member) } c.WriteInt(len(members)) }) @@ -1000,7 +1106,7 @@ func (m *Miniredis) cmdZscore(c *server.Peer, cmd string, args []string) { return } - if db.t(key) != "zset" { + if db.t(key) != keyTypeSortedSet { c.WriteError(ErrWrongType.Error()) return } @@ -1014,6 +1120,49 @@ func (m *Miniredis) cmdZscore(c *server.Peer, cmd string, args []string) { }) } +// ZMSCORE +func (m *Miniredis) cmdZMscore(c *server.Peer, cmd string, args []string) { + if len(args) < 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + if m.checkPubsub(c, cmd) { + return + } + + key, members := args[0], args[1:] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if !db.exists(key) { + c.WriteLen(len(members)) + for range members { + c.WriteNull() + } + return + } + + if db.t(key) != keyTypeSortedSet { + c.WriteError(ErrWrongType.Error()) + return + } + + c.WriteLen(len(members)) + for _, member := range members { + if !db.ssetExists(key, member) { + c.WriteNull() + continue + } + c.WriteFloat(db.ssetScore(key, member)) + } + }) +} + // parseFloatRange handles ZRANGEBYSCORE floats. They are inclusive unless the // string starts with '(' func parseFloatRange(s string) (float64, bool, error) { @@ -1317,12 +1466,12 @@ func executeZUnion(db *RedisDB, opts zunionOptions) (sortedSet, error) { var set map[string]float64 switch db.t(key) { - case "set": + case keyTypeSet: set = map[string]float64{} for elem := range db.setKeys[key] { set[elem] = 1.0 } - case "zset": + case keyTypeSortedSet: set = db.sortedSet(key) default: return nil, errors.New(msgWrongType) @@ -1371,17 +1520,20 @@ func (m *Miniredis) cmdZscan(c *server.Peer, cmd string, args []string) { return } - key := args[0] - cursor, err := strconv.Atoi(args[1]) - if err != nil { - setDirty(c) - c.WriteError(msgInvalidCursor) + var opts struct { + key string + cursor int + count int + withMatch bool + match string + } + + opts.key = args[0] + if ok := optIntErr(c, args[1], &opts.cursor, msgInvalidCursor); !ok { return } args = args[2:] // MATCH and COUNT options - var withMatch bool - var match string for len(args) > 0 { if strings.ToLower(args[0]) == "count" { if len(args) < 2 { @@ -1389,13 +1541,18 @@ func (m *Miniredis) cmdZscan(c *server.Peer, cmd string, args []string) { c.WriteError(msgSyntaxError) return } - _, err := strconv.Atoi(args[1]) + count, err := strconv.Atoi(args[1]) if err != nil { setDirty(c) c.WriteError(msgInvalidInt) return } - // We do nothing with count. + if count <= 0 { + setDirty(c) + c.WriteError(msgSyntaxError) + return + } + opts.count = count args = args[2:] continue } @@ -1405,8 +1562,8 @@ func (m *Miniredis) cmdZscan(c *server.Peer, cmd string, args []string) { c.WriteError(msgSyntaxError) return } - withMatch = true - match = args[1] + opts.withMatch = true + opts.match = args[1] args = args[2:] continue } @@ -1417,31 +1574,42 @@ func (m *Miniredis) cmdZscan(c *server.Peer, cmd string, args []string) { withTx(m, c, func(c *server.Peer, ctx *connCtx) { db := m.db(ctx.selectedDB) - // Paging is not implementend, all results are returned for cursor 0. - if cursor != 0 { - // Invalid cursor. + if db.exists(opts.key) && db.t(opts.key) != keyTypeSortedSet { + c.WriteError(ErrWrongType.Error()) + return + } + + members := db.ssetMembers(opts.key) + if opts.withMatch { + members, _ = matchKeys(members, opts.match) + } + + low := opts.cursor + high := low + opts.count + // validate high is correct + if high > len(members) || high == 0 { + high = len(members) + } + if opts.cursor > high { + // invalid cursor c.WriteLen(2) c.WriteBulk("0") // no next cursor c.WriteLen(0) // no elements return } - if db.exists(key) && db.t(key) != "zset" { - c.WriteError(ErrWrongType.Error()) - return - } - - members := db.ssetMembers(key) - if withMatch { - members, _ = matchKeys(members, match) + cursorValue := low + opts.count + if cursorValue >= len(members) { + cursorValue = 0 // no next cursor } + members = members[low:high] c.WriteLen(2) - c.WriteBulk("0") // no next cursor + c.WriteBulk(fmt.Sprintf("%d", cursorValue)) // HSCAN gives key, values. c.WriteLen(len(members) * 2) for _, k := range members { c.WriteBulk(k) - c.WriteFloat(db.ssetScore(key, k)) + c.WriteFloat(db.ssetScore(opts.key, k)) } }) } @@ -1463,10 +1631,9 @@ func (m *Miniredis) cmdZpopmax(reverse bool) server.Cmd { var err error if len(args) > 1 { count, err = strconv.Atoi(args[1]) - - if err != nil { + if err != nil || count < 0 { setDirty(c) - c.WriteError(msgInvalidInt) + c.WriteError(msgInvalidRange) return } } @@ -1485,7 +1652,7 @@ func (m *Miniredis) cmdZpopmax(reverse bool) server.Cmd { return } - if db.t(key) != "zset" { + if db.t(key) != keyTypeSortedSet { c.WriteError(ErrWrongType.Error()) return } @@ -1536,17 +1703,12 @@ func (m *Miniredis) cmdZrandmember(c *server.Peer, cmd string, args []string) { args = args[1:] if len(args) > 0 { - count := args[0] - args = args[1:] - - n, err := strconv.Atoi(count) - if err != nil { - setDirty(c) - c.WriteError(msgInvalidInt) + // can be negative + if ok := optInt(c, args[0], &opts.count); !ok { return } opts.withCount = true - opts.count = n // can be negative + args = args[1:] } if len(args) > 0 && strings.ToUpper(args[0]) == "WITHSCORES" { @@ -1572,7 +1734,7 @@ func (m *Miniredis) cmdZrandmember(c *server.Peer, cmd string, args []string) { return } - if db.t(opts.key) != "zset" { + if db.t(opts.key) != keyTypeSortedSet { c.WriteError(ErrWrongType.Error()) return } @@ -1639,7 +1801,7 @@ func runRange(m *Miniredis, c *server.Peer, cctx *connCtx, opts optsRange) { return } - if db.t(opts.Key) != "zset" { + if db.t(opts.Key) != keyTypeSortedSet { c.WriteError(ErrWrongType.Error()) return } @@ -1702,7 +1864,7 @@ func runRangeByScore(m *Miniredis, c *server.Peer, cctx *connCtx, opts optsRange return } - if db.t(opts.Key) != "zset" { + if db.t(opts.Key) != keyTypeSortedSet { c.WriteError(ErrWrongType.Error()) return } @@ -1789,7 +1951,7 @@ func runRangeByLex(m *Miniredis, c *server.Peer, cctx *connCtx, opts optsRangeBy return } - if db.t(opts.Key) != "zset" { + if db.t(opts.Key) != keyTypeSortedSet { c.WriteError(ErrWrongType.Error()) return } @@ -1852,3 +2014,12 @@ func parseLexrange(s string) (string, bool, error) { return "", false, errors.New(msgInvalidRangeItem) } } + +func sortedKeys(m map[string]float64) []string { + var keys []string + for k := range m { + keys = append(keys, k) + } + sort.Strings(keys) + return keys +} diff --git a/vendor/github.com/alicebob/miniredis/v2/cmd_stream.go b/vendor/github.com/alicebob/miniredis/v2/cmd_stream.go index bc0991ba5..7ce89d109 100644 --- a/vendor/github.com/alicebob/miniredis/v2/cmd_stream.go +++ b/vendor/github.com/alicebob/miniredis/v2/cmd_stream.go @@ -28,6 +28,7 @@ func commandsStream(m *Miniredis) { m.srv.Register("XPENDING", m.cmdXpending) m.srv.Register("XTRIM", m.cmdXtrim) m.srv.Register("XAUTOCLAIM", m.cmdXautoclaim) + m.srv.Register("XCLAIM", m.cmdXclaim) } // XADD @@ -47,8 +48,13 @@ func (m *Miniredis) cmdXadd(c *server.Peer, cmd string, args []string) { key, args := args[0], args[1:] withTx(m, c, func(c *server.Peer, ctx *connCtx) { - maxlen := -1 + minID := "" + makeStream := true + if strings.ToLower(args[0]) == "nomkstream" { + args = args[1:] + makeStream = false + } if strings.ToLower(args[0]) == "maxlen" { args = args[1:] // we don't treat "~" special @@ -66,6 +72,14 @@ func (m *Miniredis) cmdXadd(c *server.Peer, cmd string, args []string) { } maxlen = n args = args[1:] + } else if strings.ToLower(args[0]) == "minid" { + args = args[1:] + // we don't treat "~" special + if args[0] == "~" { + args = args[1:] + } + minID = args[0] + args = args[1:] } if len(args) < 1 { c.WriteError(errWrongNumber(cmd)) @@ -92,7 +106,10 @@ func (m *Miniredis) cmdXadd(c *server.Peer, cmd string, args []string) { return } if s == nil { - // TODO: NOMKSTREAM + if !makeStream { + c.WriteNull() + return + } s, _ = db.newStream(key) } @@ -109,7 +126,10 @@ func (m *Miniredis) cmdXadd(c *server.Peer, cmd string, args []string) { if maxlen >= 0 { s.trim(maxlen) } - db.keyVersion[key]++ + if minID != "" { + s.trimBefore(minID) + } + db.incr(key) c.WriteBulk(newID) }) @@ -168,26 +188,30 @@ func (m *Miniredis) makeCmdXrange(reverse bool) server.Cmd { return } - var ( - key = args[0] - startKey = args[1] - endKey = args[2] + opts := struct { + key string + startKey string startExclusive bool + endKey string endExclusive bool - ) - if strings.HasPrefix(startKey, "(") { - startExclusive = true - startKey = startKey[1:] - if startKey == "-" || startKey == "+" { + }{ + key: args[0], + startKey: args[1], + endKey: args[2], + } + if strings.HasPrefix(opts.startKey, "(") { + opts.startExclusive = true + opts.startKey = opts.startKey[1:] + if opts.startKey == "-" || opts.startKey == "+" { setDirty(c) c.WriteError(msgInvalidStreamID) return } } - if strings.HasPrefix(endKey, "(") { - endExclusive = true - endKey = endKey[1:] - if endKey == "-" || endKey == "+" { + if strings.HasPrefix(opts.endKey, "(") { + opts.endExclusive = true + opts.endKey = opts.endKey[1:] + if opts.endKey == "-" || opts.endKey == "+" { setDirty(c) c.WriteError(msgInvalidStreamID) return @@ -205,12 +229,12 @@ func (m *Miniredis) makeCmdXrange(reverse bool) server.Cmd { } withTx(m, c, func(c *server.Peer, ctx *connCtx) { - start, err := formatStreamRangeBound(startKey, true, reverse) + start, err := formatStreamRangeBound(opts.startKey, true, reverse) if err != nil { c.WriteError(msgInvalidStreamID) return } - end, err := formatStreamRangeBound(endKey, false, reverse) + end, err := formatStreamRangeBound(opts.endKey, false, reverse) if err != nil { c.WriteError(msgInvalidStreamID) return @@ -223,17 +247,17 @@ func (m *Miniredis) makeCmdXrange(reverse bool) server.Cmd { db := m.db(ctx.selectedDB) - if !db.exists(key) { + if !db.exists(opts.key) { c.WriteLen(0) return } - if db.t(key) != "stream" { + if db.t(opts.key) != keyTypeStream { c.WriteError(ErrWrongType.Error()) return } - var entries = db.streamKeys[key].entries + var entries = db.streamKeys[opts.key].entries if reverse { entries = reversedStreamEntries(entries) } @@ -270,11 +294,11 @@ func (m *Miniredis) makeCmdXrange(reverse bool) server.Cmd { } // Continue if start exclusive and entry ID == start - if startExclusive && streamCmp(entry.ID, start) == 0 { + if opts.startExclusive && streamCmp(entry.ID, start) == 0 { continue } // Continue if end exclusive and entry ID == end - if endExclusive && streamCmp(entry.ID, end) == 0 { + if opts.endExclusive && streamCmp(entry.ID, end) == 0 { continue } @@ -296,19 +320,50 @@ func (m *Miniredis) makeCmdXrange(reverse bool) server.Cmd { // XGROUP func (m *Miniredis) cmdXgroup(c *server.Peer, cmd string, args []string) { - if (len(args) == 4 || len(args) == 5) && strings.ToUpper(args[0]) == "CREATE" { + if len(args) == 0 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + if m.checkPubsub(c, cmd) { + return + } + + subCmd, args := strings.ToLower(args[0]), args[1:] + switch subCmd { + case "create": m.cmdXgroupCreate(c, cmd, args) - } else { - j := strings.Join(args, " ") - err := fmt.Sprintf("ERR 'XGROUP %s' not supported", j) + case "destroy": + m.cmdXgroupDestroy(c, cmd, args) + case "createconsumer": + m.cmdXgroupCreateconsumer(c, cmd, args) + case "delconsumer": + m.cmdXgroupDelconsumer(c, cmd, args) + case "help", + "setid": + err := fmt.Sprintf("ERR 'XGROUP %s' not supported", subCmd) setDirty(c) c.WriteError(err) + default: + setDirty(c) + c.WriteError(fmt.Sprintf( + "ERR unknown subcommand '%s'. Try XGROUP HELP.", + subCmd, + )) } } // XGROUP CREATE func (m *Miniredis) cmdXgroupCreate(c *server.Peer, cmd string, args []string) { - stream, group, id := args[1], args[2], args[3] + if len(args) != 3 && len(args) != 4 { + setDirty(c) + c.WriteError(errWrongNumber("CREATE")) + return + } + stream, group, id := args[0], args[1], args[2] withTx(m, c, func(c *server.Peer, ctx *connCtx) { db := m.db(ctx.selectedDB) @@ -318,7 +373,7 @@ func (m *Miniredis) cmdXgroupCreate(c *server.Peer, cmd string, args []string) { c.WriteError(err.Error()) return } - if s == nil && len(args) == 5 && strings.ToUpper(args[4]) == "MKSTREAM" { + if s == nil && len(args) == 4 && strings.ToUpper(args[3]) == "MKSTREAM" { if s, err = db.newStream(stream); err != nil { c.WriteError(err.Error()) return @@ -338,6 +393,124 @@ func (m *Miniredis) cmdXgroupCreate(c *server.Peer, cmd string, args []string) { }) } +// XGROUP DESTROY +func (m *Miniredis) cmdXgroupDestroy(c *server.Peer, cmd string, args []string) { + if len(args) != 2 { + setDirty(c) + c.WriteError(errWrongNumber("DESTROY")) + return + } + stream, groupName := args[0], args[1] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + s, err := db.stream(stream) + if err != nil { + c.WriteError(err.Error()) + return + } + if s == nil { + c.WriteError(msgXgroupKeyNotFound) + return + } + + if _, ok := s.groups[groupName]; !ok { + c.WriteInt(0) + return + } + delete(s.groups, groupName) + c.WriteInt(1) + }) +} + +// XGROUP CREATECONSUMER +func (m *Miniredis) cmdXgroupCreateconsumer(c *server.Peer, cmd string, args []string) { + if len(args) != 3 { + setDirty(c) + c.WriteError(errWrongNumber("CREATECONSUMER")) + return + } + key, groupName, consumerName := args[0], args[1], args[2] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + s, err := db.stream(key) + if err != nil { + c.WriteError(err.Error()) + return + } + if s == nil { + c.WriteError(msgXgroupKeyNotFound) + return + } + + g, ok := s.groups[groupName] + if !ok { + err := fmt.Sprintf("NOGROUP No such consumer group '%s' for key name '%s'", groupName, key) + c.WriteError(err) + return + } + + if _, ok = g.consumers[consumerName]; ok { + c.WriteInt(0) + return + } + g.consumers[consumerName] = &consumer{} + c.WriteInt(1) + }) +} + +// XGROUP DELCONSUMER +func (m *Miniredis) cmdXgroupDelconsumer(c *server.Peer, cmd string, args []string) { + if len(args) != 3 { + setDirty(c) + c.WriteError(errWrongNumber("DELCONSUMER")) + return + } + key, groupName, consumerName := args[0], args[1], args[2] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + s, err := db.stream(key) + if err != nil { + c.WriteError(err.Error()) + return + } + if s == nil { + c.WriteError(msgXgroupKeyNotFound) + return + } + + g, ok := s.groups[groupName] + if !ok { + err := fmt.Sprintf("NOGROUP No such consumer group '%s' for key name '%s'", groupName, key) + c.WriteError(err) + return + } + + consumer, ok := g.consumers[consumerName] + if !ok { + c.WriteInt(0) + return + } + defer delete(g.consumers, consumerName) + + if consumer.numPendingEntries > 0 { + newPending := make([]pendingEntry, 0) + for _, entry := range g.pending { + if entry.consumer != consumerName { + newPending = append(newPending, entry) + } + } + g.pending = newPending + } + c.WriteInt(consumer.numPendingEntries) + }) +} + // XINFO func (m *Miniredis) cmdXinfo(c *server.Peer, cmd string, args []string) { if len(args) < 1 { @@ -345,22 +518,32 @@ func (m *Miniredis) cmdXinfo(c *server.Peer, cmd string, args []string) { c.WriteError(errWrongNumber(cmd)) return } + if !m.handleAuth(c) { + return + } + if m.checkPubsub(c, cmd) { + return + } + subCmd, args := strings.ToUpper(args[0]), args[1:] switch subCmd { case "STREAM": m.cmdXinfoStream(c, args) - case "CONSUMERS", "GROUPS", "HELP": + case "CONSUMERS": + m.cmdXinfoConsumers(c, args) + case "GROUPS": + m.cmdXinfoGroups(c, args) + case "HELP": err := fmt.Sprintf("'XINFO %s' not supported", strings.Join(args, " ")) setDirty(c) c.WriteError(err) default: setDirty(c) c.WriteError(fmt.Sprintf( - "ERR Unknown subcommand or wrong number of arguments for '%s'. Try XINFO HELP.", + "ERR unknown subcommand or wrong number of arguments for '%s'. Try XINFO HELP.", subCmd, )) } - } // XINFO STREAM @@ -368,10 +551,11 @@ func (m *Miniredis) cmdXinfo(c *server.Peer, cmd string, args []string) { func (m *Miniredis) cmdXinfoStream(c *server.Peer, args []string) { if len(args) < 1 { setDirty(c) - c.WriteError(errWrongNumber("XINFO")) + c.WriteError(errWrongNumber("STREAM")) return } key := args[0] + withTx(m, c, func(c *server.Peer, ctx *connCtx) { db := m.db(ctx.selectedDB) @@ -391,6 +575,112 @@ func (m *Miniredis) cmdXinfoStream(c *server.Peer, args []string) { }) } +// XINFO GROUPS +func (m *Miniredis) cmdXinfoGroups(c *server.Peer, args []string) { + if len(args) != 1 { + setDirty(c) + c.WriteError(errWrongNumber("GROUPS")) + return + } + key := args[0] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + s, err := db.stream(key) + if err != nil { + c.WriteError(err.Error()) + return + } + if s == nil { + c.WriteError(msgKeyNotFound) + return + } + + c.WriteLen(len(s.groups)) + for name, g := range s.groups { + c.WriteMapLen(6) + + c.WriteBulk("name") + c.WriteBulk(name) + c.WriteBulk("consumers") + c.WriteInt(len(g.consumers)) + c.WriteBulk("pending") + c.WriteInt(len(g.activePending())) + c.WriteBulk("last-delivered-id") + c.WriteBulk(g.lastID) + c.WriteBulk("entries-read") + c.WriteNull() + c.WriteBulk("lag") + c.WriteInt(len(g.stream.entries)) + } + }) +} + +// XINFO CONSUMERS +// Please note that this is only a partial implementation, for it does not +// return each consumer's "idle" value, which indicates "the number of +// milliseconds that have passed since the consumer last interacted with the +// server." +func (m *Miniredis) cmdXinfoConsumers(c *server.Peer, args []string) { + if len(args) != 2 { + setDirty(c) + c.WriteError(errWrongNumber("CONSUMERS")) + return + } + key, groupName := args[0], args[1] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + s, err := db.stream(key) + if err != nil { + c.WriteError(err.Error()) + return + } + if s == nil { + c.WriteError(msgKeyNotFound) + return + } + + g, ok := s.groups[groupName] + if !ok { + err := fmt.Sprintf("NOGROUP No such consumer group '%s' for key name '%s'", groupName, key) + c.WriteError(err) + return + } + + var consumerNames []string + for name := range g.consumers { + consumerNames = append(consumerNames, name) + } + sort.Strings(consumerNames) + + c.WriteLen(len(consumerNames)) + for _, name := range consumerNames { + cons := g.consumers[name] + + c.WriteMapLen(4) + c.WriteBulk("name") + c.WriteBulk(name) + c.WriteBulk("pending") + c.WriteInt(cons.numPendingEntries) + // TODO: these times aren't set for all commands + c.WriteBulk("idle") + c.WriteInt(m.sinceMilli(cons.lastSeen)) + c.WriteBulk("inactive") + c.WriteInt(m.sinceMilli(cons.lastSuccess)) + } + }) +} + +func (m *Miniredis) sinceMilli(t time.Time) int { + if t.IsZero() { + return -1 + } + return int(m.effectiveNow().Sub(t).Milliseconds()) +} + // XREADGROUP func (m *Miniredis) cmdXreadgroup(c *server.Peer, cmd string, args []string) { // XREADGROUP GROUP group consumer STREAMS key ID @@ -399,6 +689,12 @@ func (m *Miniredis) cmdXreadgroup(c *server.Peer, cmd string, args []string) { c.WriteError(errWrongNumber(cmd)) return } + if !m.handleAuth(c) { + return + } + if m.checkPubsub(c, cmd) { + return + } var opts struct { group string @@ -505,6 +801,12 @@ parsing: c, opts.blockTimeout, func(c *server.Peer, ctx *connCtx) bool { + if ctx.nested { + setDirty(c) + c.WriteError("ERR XREADGROUP command is not allowed with BLOCK option from scripts") + return false + } + db := m.db(ctx.selectedDB) res, err := xreadgroup( db, @@ -574,6 +876,12 @@ func (m *Miniredis) cmdXack(c *server.Peer, cmd string, args []string) { c.WriteError(errWrongNumber(cmd)) return } + if !m.handleAuth(c) { + return + } + if m.checkPubsub(c, cmd) { + return + } key, group, ids := args[0], args[1], args[2:] @@ -605,6 +913,12 @@ func (m *Miniredis) cmdXdel(c *server.Peer, cmd string, args []string) { c.WriteError(errWrongNumber(cmd)) return } + if !m.handleAuth(c) { + return + } + if m.checkPubsub(c, cmd) { + return + } stream, ids := args[0], args[1:] @@ -625,7 +939,7 @@ func (m *Miniredis) cmdXdel(c *server.Peer, cmd string, args []string) { c.WriteError(err.Error()) return } - db.keyVersion[stream]++ + db.incr(stream) c.WriteInt(n) }) } @@ -637,15 +951,23 @@ func (m *Miniredis) cmdXread(c *server.Peer, cmd string, args []string) { c.WriteError(errWrongNumber(cmd)) return } - - var opts struct { - count int - streams []string - ids []string - block bool - blockTimeout time.Duration + if !m.handleAuth(c) { + return } - var err error + if m.checkPubsub(c, cmd) { + return + } + + var ( + opts struct { + count int + streams []string + ids []string + block bool + blockTimeout time.Duration + } + err error + ) parsing: for len(args) > 0 { @@ -676,11 +998,21 @@ parsing: } opts.streams, opts.ids = args[0:len(args)/2], args[len(args)/2:] - for _, id := range opts.ids { + for i, id := range opts.ids { if _, err := parseStreamID(id); id != `$` && err != nil { setDirty(c) c.WriteError(msgInvalidStreamID) return + } else if id == "$" { + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(getCtx(c).selectedDB) + stream, ok := db.streamKeys[opts.streams[i]] + if ok { + opts.ids[i] = stream.lastID() + } else { + opts.ids[i] = "0-0" + } + }) } } args = nil @@ -690,7 +1022,6 @@ parsing: break parsing } } - if err != nil { setDirty(c) c.WriteError(err.Error()) @@ -710,6 +1041,12 @@ parsing: c, opts.blockTimeout, func(c *server.Peer, ctx *connCtx) bool { + if ctx.nested { + setDirty(c) + c.WriteError("ERR XREAD command is not allowed with BLOCK option from scripts") + return false + } + db := m.db(ctx.selectedDB) res := xread(db, opts.streams, opts.ids, opts.count) if len(res) == 0 { @@ -796,46 +1133,68 @@ func (m *Miniredis) cmdXpending(c *server.Peer, cmd string, args []string) { c.WriteError(errWrongNumber(cmd)) return } - - key, group, args := args[0], args[1], args[2:] - summary := true - if len(args) > 0 && strings.ToUpper(args[0]) == "IDLE" { - setDirty(c) - c.WriteError("ERR IDLE is unsupported") + if !m.handleAuth(c) { return } - var ( + if m.checkPubsub(c, cmd) { + return + } + + var opts struct { + key string + group string + summary bool + idle time.Duration start, end string count int consumer *string - ) + } + + opts.key, opts.group, args = args[0], args[1], args[2:] + opts.summary = true if len(args) >= 3 { - summary = false + opts.summary = false + + if strings.ToUpper(args[0]) == "IDLE" { + idleMs, err := strconv.ParseInt(args[1], 10, 64) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + opts.idle = time.Duration(idleMs) * time.Millisecond + + args = args[2:] + if len(args) < 3 { + setDirty(c) + c.WriteError(msgSyntaxError) + return + } + } - start_, err := formatStreamRangeBound(args[0], true, false) + var err error + opts.start, err = formatStreamRangeBound(args[0], true, false) if err != nil { + setDirty(c) c.WriteError(msgInvalidStreamID) return } - start = start_ - end_, err := formatStreamRangeBound(args[1], false, false) + opts.end, err = formatStreamRangeBound(args[1], false, false) if err != nil { + setDirty(c) c.WriteError(msgInvalidStreamID) return } - end = end_ - n, err := strconv.Atoi(args[2]) // negative is allowed + opts.count, err = strconv.Atoi(args[2]) // negative is allowed if err != nil { + setDirty(c) c.WriteError(msgInvalidInt) return } - count = n args = args[3:] if len(args) == 1 { - var c string - c, args = args[0], args[1:] - consumer = &c + opts.consumer, args = &args[0], args[1:] } } if len(args) != 0 { @@ -846,26 +1205,27 @@ func (m *Miniredis) cmdXpending(c *server.Peer, cmd string, args []string) { withTx(m, c, func(c *server.Peer, ctx *connCtx) { db := m.db(ctx.selectedDB) - g, err := db.streamGroup(key, group) + g, err := db.streamGroup(opts.key, opts.group) if err != nil { c.WriteError(err.Error()) return } if g == nil { - c.WriteError(errReadgroup(key, group).Error()) + c.WriteError(errReadgroup(opts.key, opts.group).Error()) return } - if summary { + if opts.summary { writeXpendingSummary(c, *g) return } - writeXpending(m.effectiveNow(), c, *g, start, end, count, consumer) + writeXpending(m.effectiveNow(), c, *g, opts.idle, opts.start, opts.end, opts.count, opts.consumer) }) } func writeXpendingSummary(c *server.Peer, g streamGroup) { - if len(g.pending) == 0 { + pend := g.activePending() + if len(pend) == 0 { c.WriteLen(4) c.WriteInt(0) c.WriteNull() @@ -880,9 +1240,9 @@ func writeXpendingSummary(c *server.Peer, g streamGroup) { // - highest ID // - all consumers with > 0 pending items c.WriteLen(4) - c.WriteInt(len(g.pending)) - c.WriteBulk(g.pending[0].id) - c.WriteBulk(g.pending[len(g.pending)-1].id) + c.WriteInt(len(pend)) + c.WriteBulk(pend[0].id) + c.WriteBulk(pend[len(pend)-1].id) cons := map[string]int{} for id := range g.consumers { cnt := g.pendingCount(id) @@ -907,13 +1267,14 @@ func writeXpending( now time.Time, c *server.Peer, g streamGroup, + idle time.Duration, start, end string, count int, consumer *string, ) { if len(g.pending) == 0 || count < 0 { - c.WriteLen(-1) + c.WriteLen(0) return } @@ -942,12 +1303,15 @@ func writeXpending( if streamCmp(p.id, end) > 0 { continue } - res = append(res, entry{ - id: p.id, - consumer: p.consumer, - millis: int(now.Sub(p.lastDelivery).Milliseconds()), - count: p.deliveryCount, - }) + timeSinceLastDelivery := now.Sub(p.lastDelivery) + if timeSinceLastDelivery >= idle { + res = append(res, entry{ + id: p.id, + consumer: p.consumer, + millis: int(timeSinceLastDelivery.Milliseconds()), + count: p.deliveryCount, + }) + } } c.WriteLen(len(res)) for _, e := range res { @@ -966,6 +1330,12 @@ func (m *Miniredis) cmdXtrim(c *server.Peer, cmd string, args []string) { c.WriteError(errWrongNumber(cmd)) return } + if !m.handleAuth(c) { + return + } + if m.checkPubsub(c, cmd) { + return + } var opts struct { stream string @@ -1052,16 +1422,8 @@ func (m *Miniredis) cmdXtrim(c *server.Peer, cmd string, args []string) { s.trim(opts.maxLen) c.WriteInt(entriesBefore - len(s.entries)) case "MINID": - var delete []string - for _, entry := range s.entries { - if entry.ID < opts.threshold { - delete = append(delete, entry.ID) - } else { - break - } - } - s.delete(delete) - c.WriteInt(len(delete)) + n := s.trimBefore(opts.threshold) + c.WriteInt(n) } }) } @@ -1074,28 +1436,42 @@ func (m *Miniredis) cmdXautoclaim(c *server.Peer, cmd string, args []string) { c.WriteError(errWrongNumber(cmd)) return } + if !m.handleAuth(c) { + return + } + if m.checkPubsub(c, cmd) { + return + } - key, group, consumer := args[0], args[1], args[2] + var opts struct { + key string + group string + consumer string + minIdleTime time.Duration + start string + justId bool + count int + } + opts.key, opts.group, opts.consumer = args[0], args[1], args[2] n, err := strconv.Atoi(args[3]) if err != nil { setDirty(c) c.WriteError("ERR Invalid min-idle-time argument for XAUTOCLAIM") return } - minIdleTime := time.Millisecond * time.Duration(n) + opts.minIdleTime = time.Millisecond * time.Duration(n) start_, err := formatStreamRangeBound(args[4], true, false) if err != nil { c.WriteError(msgInvalidStreamID) return } - start := start_ + opts.start = start_ args = args[5:] - count := 100 - var justId bool + opts.count = 100 parsing: for len(args) > 0 { switch strings.ToUpper(args[0]) { @@ -1105,7 +1481,7 @@ parsing: break parsing } - count, err = strconv.Atoi(args[1]) + opts.count, err = strconv.Atoi(args[1]) if err != nil { break parsing } @@ -1113,7 +1489,7 @@ parsing: args = args[2:] case "JUSTID": args = args[1:] - justId = true + opts.justId = true default: err = errors.New(msgSyntaxError) break parsing @@ -1128,18 +1504,18 @@ parsing: withTx(m, c, func(c *server.Peer, ctx *connCtx) { db := m.db(ctx.selectedDB) - g, err := db.streamGroup(key, group) + g, err := db.streamGroup(opts.key, opts.group) if err != nil { c.WriteError(err.Error()) return } if g == nil { - c.WriteError(errReadgroup(key, group).Error()) + c.WriteError(errReadgroup(opts.key, opts.group).Error()) return } - nextCallId, entries := xautoclaim(m.effectiveNow(), *g, minIdleTime, start, count, consumer) - writeXautoclaim(c, nextCallId, entries, justId) + nextCallId, entries := xautoclaim(m.effectiveNow(), *g, opts.minIdleTime, opts.start, opts.count, opts.consumer) + writeXautoclaim(c, nextCallId, entries, opts.justId) }) } @@ -1156,14 +1532,19 @@ func xautoclaim( return nextCallId, nil } - msgs := g.pendingAfter(start) + msgs := g.pendingAfterOrEqual(start) var res []StreamEntry for i, p := range msgs { if minIdleTime > 0 && now.Before(p.lastDelivery.Add(minIdleTime)) { continue } - g.consumers[consumerID] = consumer{} + + prevConsumerID := p.consumer + if _, ok := g.consumers[consumerID]; !ok { + g.consumers[consumerID] = &consumer{} + } p.consumer = consumerID + _, entry := g.stream.get(p.id) // not found. Weird? if entry == nil { @@ -1172,8 +1553,13 @@ func xautoclaim( // (Introduced in Redis 7.0) continue } + p.deliveryCount += 1 p.lastDelivery = now + + g.consumers[prevConsumerID].numPendingEntries-- + g.consumers[consumerID].numPendingEntries++ + msgs[i] = p res = append(res, *entry) @@ -1188,7 +1574,7 @@ func xautoclaim( } func writeXautoclaim(c *server.Peer, nextCallId string, res []StreamEntry, justId bool) { - c.WriteLen(2) + c.WriteLen(3) c.WriteBulk(nextCallId) c.WriteLen(len(res)) for _, entry := range res { @@ -1204,6 +1590,209 @@ func writeXautoclaim(c *server.Peer, nextCallId string, res []StreamEntry, justI c.WriteBulk(v) } } + // TODO: see "Redis 7" note + c.WriteLen(0) +} + +// XCLAIM +func (m *Miniredis) cmdXclaim(c *server.Peer, cmd string, args []string) { + if len(args) < 5 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + if m.checkPubsub(c, cmd) { + return + } + + var opts struct { + key string + groupName string + consumerName string + minIdleTime time.Duration + newLastDelivery time.Time + ids []string + retryCount *int + force bool + justId bool + } + + opts.key, opts.groupName, opts.consumerName = args[0], args[1], args[2] + + minIdleTimeMillis, err := strconv.Atoi(args[3]) + if err != nil { + setDirty(c) + c.WriteError("ERR Invalid min-idle-time argument for XCLAIM") + return + } + opts.minIdleTime = time.Millisecond * time.Duration(minIdleTimeMillis) + + opts.newLastDelivery = m.effectiveNow() + opts.ids = append(opts.ids, args[4]) + + args = args[5:] + for len(args) > 0 { + arg := strings.ToUpper(args[0]) + if arg == "IDLE" || + arg == "TIME" || + arg == "RETRYCOUNT" || + arg == "FORCE" || + arg == "JUSTID" { + break + } + opts.ids = append(opts.ids, arg) + args = args[1:] + } + + for len(args) > 0 { + arg := strings.ToUpper(args[0]) + switch arg { + case "IDLE": + idleMs, err := strconv.ParseInt(args[1], 10, 64) + if err != nil { + setDirty(c) + c.WriteError("ERR Invalid IDLE option argument for XCLAIM") + return + } + if idleMs < 0 { + idleMs = 0 + } + opts.newLastDelivery = m.effectiveNow().Add(time.Millisecond * time.Duration(-idleMs)) + args = args[2:] + case "TIME": + timeMs, err := strconv.ParseInt(args[1], 10, 64) + if err != nil { + setDirty(c) + c.WriteError("ERR Invalid TIME option argument for XCLAIM") + return + } + opts.newLastDelivery = time.UnixMilli(timeMs) + args = args[2:] + case "RETRYCOUNT": + retryCount, err := strconv.Atoi(args[1]) + if err != nil { + setDirty(c) + c.WriteError("ERR Invalid RETRYCOUNT option argument for XCLAIM") + return + } + opts.retryCount = &retryCount + args = args[2:] + case "FORCE": + opts.force = true + args = args[1:] + case "JUSTID": + opts.justId = true + args = args[1:] + default: + setDirty(c) + c.WriteError(fmt.Sprintf("ERR Unrecognized XCLAIM option '%s'", args[0])) + return + } + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + g, err := db.streamGroup(opts.key, opts.groupName) + if err != nil { + c.WriteError(err.Error()) + return + } + if g == nil { + c.WriteError(errReadgroup(opts.key, opts.groupName).Error()) + return + } + + claimedEntryIDs := m.xclaim(g, opts.consumerName, opts.minIdleTime, opts.newLastDelivery, opts.ids, opts.retryCount, opts.force) + writeXclaim(c, g.stream, claimedEntryIDs, opts.justId) + }) +} + +func (m *Miniredis) xclaim( + group *streamGroup, + consumerName string, + minIdleTime time.Duration, + newLastDelivery time.Time, + ids []string, + retryCount *int, + force bool, +) (claimedEntryIDs []string) { + for _, id := range ids { + pelPos, pelEntry := group.searchPending(id) + if pelEntry == nil { + group.setLastSeen(consumerName, m.effectiveNow()) + if !force { + continue + } + + if pelPos < len(group.pending) { + group.pending = append(group.pending[:pelPos+1], group.pending[pelPos:]...) + } else { + group.pending = append(group.pending, pendingEntry{}) + } + pelEntry = &group.pending[pelPos] + + *pelEntry = pendingEntry{ + id: id, + consumer: consumerName, + deliveryCount: 1, + } + group.setLastSuccess(consumerName, m.effectiveNow()) + } else { + group.consumers[pelEntry.consumer].numPendingEntries-- + pelEntry.consumer = consumerName + } + + if retryCount != nil { + pelEntry.deliveryCount = *retryCount + } else { + pelEntry.deliveryCount++ + } + pelEntry.lastDelivery = newLastDelivery + + // redis7: don't report entries which are deleted by now + if _, e := group.stream.get(id); e == nil { + continue + } + + claimedEntryIDs = append(claimedEntryIDs, id) + } + if len(claimedEntryIDs) == 0 { + group.setLastSeen(consumerName, m.effectiveNow()) + return + } + + if _, ok := group.consumers[consumerName]; !ok { + group.consumers[consumerName] = &consumer{} + } + consumer := group.consumers[consumerName] + consumer.numPendingEntries += len(claimedEntryIDs) + + group.setLastSuccess(consumerName, m.effectiveNow()) + return +} + +func writeXclaim(c *server.Peer, stream *streamKey, claimedEntryIDs []string, justId bool) { + c.WriteLen(len(claimedEntryIDs)) + for _, id := range claimedEntryIDs { + if justId { + c.WriteBulk(id) + continue + } + + _, entry := stream.get(id) + if entry == nil { + c.WriteNull() + continue + } + + c.WriteLen(2) + c.WriteBulk(entry.ID) + c.WriteStrings(entry.Values) + } } func parseBlock(cmd string, args []string, block *bool, timeout *time.Duration) error { diff --git a/vendor/github.com/alicebob/miniredis/v2/cmd_string.go b/vendor/github.com/alicebob/miniredis/v2/cmd_string.go index 29c2c2273..08e67746b 100644 --- a/vendor/github.com/alicebob/miniredis/v2/cmd_string.go +++ b/vendor/github.com/alicebob/miniredis/v2/cmd_string.go @@ -131,16 +131,27 @@ func (m *Miniredis) cmdSet(c *server.Peer, cmd string, args []string) { withTx(m, c, func(c *server.Peer, ctx *connCtx) { db := m.db(ctx.selectedDB) + readonly := false if opts.nx { if db.exists(opts.key) { - c.WriteNull() - return + if opts.get { + // special case for SET NX GET + readonly = true + } else { + c.WriteNull() + return + } } } if opts.xx { if !db.exists(opts.key) { - c.WriteNull() - return + if opts.get { + // special case for SET XX GET + readonly = true + } else { + c.WriteNull() + return + } } } if opts.keepttl { @@ -149,19 +160,22 @@ func (m *Miniredis) cmdSet(c *server.Peer, cmd string, args []string) { } } if opts.get { - if t, ok := db.keys[opts.key]; ok && t != "string" { + if t, ok := db.keys[opts.key]; ok && t != keyTypeString { c.WriteError(msgWrongType) return } } + old, existed := db.stringKeys[opts.key] - db.del(opts.key, true) // be sure to remove existing values of other type keys. - // a vanilla SET clears the expire - if opts.ttl >= 0 { // EXAT/PXAT can expire right away - db.stringSet(opts.key, opts.value) - } - if opts.ttl != 0 { - db.ttl[opts.key] = opts.ttl + if !readonly { + db.del(opts.key, true) // be sure to remove existing values of other type keys. + // a vanilla SET clears the expire + if opts.ttl >= 0 { // EXAT/PXAT can expire right away + db.stringSet(opts.key, opts.value) + } + if opts.ttl != 0 { + db.ttl[opts.key] = opts.ttl + } } if opts.get { if !existed { @@ -227,26 +241,29 @@ func (m *Miniredis) cmdPsetex(c *server.Peer, cmd string, args []string) { return } - key := args[0] - ttl, err := strconv.Atoi(args[1]) - if err != nil { - setDirty(c) - c.WriteError(msgInvalidInt) + var opts struct { + key string + ttl int + value string + } + + opts.key = args[0] + if ok := optInt(c, args[1], &opts.ttl); !ok { return } - if ttl <= 0 { + if opts.ttl <= 0 { setDirty(c) c.WriteError(msgInvalidPSETEXTime) return } - value := args[2] + opts.value = args[2] withTx(m, c, func(c *server.Peer, ctx *connCtx) { db := m.db(ctx.selectedDB) - db.del(key, true) // Clear any existing keys. - db.stringSet(key, value) - db.ttl[key] = time.Duration(ttl) * time.Millisecond + db.del(opts.key, true) // Clear any existing keys. + db.stringSet(opts.key, opts.value) + db.ttl[opts.key] = time.Duration(opts.ttl) * time.Millisecond c.WriteOK() }) } @@ -386,7 +403,7 @@ func (m *Miniredis) cmdGet(c *server.Peer, cmd string, args []string) { c.WriteNull() return } - if db.t(key) != "string" { + if db.t(key) != keyTypeString { c.WriteError(msgWrongType) return } @@ -473,7 +490,7 @@ func (m *Miniredis) cmdGetex(c *server.Peer, cmd string, args []string) { db.ttl[opts.key] = opts.ttl } - if db.t(opts.key) != "string" { + if db.t(opts.key) != keyTypeString { c.WriteError(msgWrongType) return } @@ -501,7 +518,7 @@ func (m *Miniredis) cmdGetset(c *server.Peer, cmd string, args []string) { withTx(m, c, func(c *server.Peer, ctx *connCtx) { db := m.db(ctx.selectedDB) - if t, ok := db.keys[key]; ok && t != "string" { + if t, ok := db.keys[key]; ok && t != keyTypeString { c.WriteError(msgWrongType) return } @@ -543,7 +560,7 @@ func (m *Miniredis) cmdGetdel(c *server.Peer, cmd string, args []string) { return } - if db.t(key) != "string" { + if db.t(key) != keyTypeString { c.WriteError(msgWrongType) return } @@ -573,7 +590,7 @@ func (m *Miniredis) cmdMget(c *server.Peer, cmd string, args []string) { c.WriteLen(len(args)) for _, k := range args { - if t, ok := db.keys[k]; !ok || t != "string" { + if t, ok := db.keys[k]; !ok || t != keyTypeString { c.WriteNull() continue } @@ -606,7 +623,7 @@ func (m *Miniredis) cmdIncr(c *server.Peer, cmd string, args []string) { db := m.db(ctx.selectedDB) key := args[0] - if t, ok := db.keys[key]; ok && t != "string" { + if t, ok := db.keys[key]; ok && t != keyTypeString { c.WriteError(msgWrongType) return } @@ -634,23 +651,24 @@ func (m *Miniredis) cmdIncrby(c *server.Peer, cmd string, args []string) { return } - key := args[0] - delta, err := strconv.Atoi(args[1]) - if err != nil { - setDirty(c) - c.WriteError(msgInvalidInt) + var opts struct { + key string + delta int + } + opts.key = args[0] + if ok := optInt(c, args[1], &opts.delta); !ok { return } withTx(m, c, func(c *server.Peer, ctx *connCtx) { db := m.db(ctx.selectedDB) - if t, ok := db.keys[key]; ok && t != "string" { + if t, ok := db.keys[opts.key]; ok && t != keyTypeString { c.WriteError(msgWrongType) return } - v, err := db.stringIncr(key, delta) + v, err := db.stringIncr(opts.key, opts.delta) if err != nil { c.WriteError(err.Error()) return @@ -685,7 +703,7 @@ func (m *Miniredis) cmdIncrbyfloat(c *server.Peer, cmd string, args []string) { withTx(m, c, func(c *server.Peer, ctx *connCtx) { db := m.db(ctx.selectedDB) - if t, ok := db.keys[key]; ok && t != "string" { + if t, ok := db.keys[key]; ok && t != keyTypeString { c.WriteError(msgWrongType) return } @@ -718,7 +736,7 @@ func (m *Miniredis) cmdDecr(c *server.Peer, cmd string, args []string) { db := m.db(ctx.selectedDB) key := args[0] - if t, ok := db.keys[key]; ok && t != "string" { + if t, ok := db.keys[key]; ok && t != keyTypeString { c.WriteError(msgWrongType) return } @@ -746,23 +764,24 @@ func (m *Miniredis) cmdDecrby(c *server.Peer, cmd string, args []string) { return } - key := args[0] - delta, err := strconv.Atoi(args[1]) - if err != nil { - setDirty(c) - c.WriteError(msgInvalidInt) + var opts struct { + key string + delta int + } + opts.key = args[0] + if ok := optInt(c, args[1], &opts.delta); !ok { return } withTx(m, c, func(c *server.Peer, ctx *connCtx) { db := m.db(ctx.selectedDB) - if t, ok := db.keys[key]; ok && t != "string" { + if t, ok := db.keys[opts.key]; ok && t != keyTypeString { c.WriteError(msgWrongType) return } - v, err := db.stringIncr(key, -delta) + v, err := db.stringIncr(opts.key, -opts.delta) if err != nil { c.WriteError(err.Error()) return @@ -791,7 +810,7 @@ func (m *Miniredis) cmdStrlen(c *server.Peer, cmd string, args []string) { withTx(m, c, func(c *server.Peer, ctx *connCtx) { db := m.db(ctx.selectedDB) - if t, ok := db.keys[key]; ok && t != "string" { + if t, ok := db.keys[key]; ok && t != keyTypeString { c.WriteError(msgWrongType) return } @@ -819,7 +838,7 @@ func (m *Miniredis) cmdAppend(c *server.Peer, cmd string, args []string) { withTx(m, c, func(c *server.Peer, ctx *connCtx) { db := m.db(ctx.selectedDB) - if t, ok := db.keys[key]; ok && t != "string" { + if t, ok := db.keys[key]; ok && t != keyTypeString { c.WriteError(msgWrongType) return } @@ -845,30 +864,29 @@ func (m *Miniredis) cmdGetrange(c *server.Peer, cmd string, args []string) { return } - key := args[0] - start, err := strconv.Atoi(args[1]) - if err != nil { - setDirty(c) - c.WriteError(msgInvalidInt) + var opts struct { + key string + start int + end int + } + opts.key = args[0] + if ok := optInt(c, args[1], &opts.start); !ok { return } - end, err := strconv.Atoi(args[2]) - if err != nil { - setDirty(c) - c.WriteError(msgInvalidInt) + if ok := optInt(c, args[2], &opts.end); !ok { return } withTx(m, c, func(c *server.Peer, ctx *connCtx) { db := m.db(ctx.selectedDB) - if t, ok := db.keys[key]; ok && t != "string" { + if t, ok := db.keys[opts.key]; ok && t != keyTypeString { c.WriteError(msgWrongType) return } - v := db.stringKeys[key] - c.WriteBulk(withRange(v, start, end)) + v := db.stringKeys[opts.key] + c.WriteBulk(withRange(v, opts.start, opts.end)) }) } @@ -886,36 +904,39 @@ func (m *Miniredis) cmdSetrange(c *server.Peer, cmd string, args []string) { return } - key := args[0] - pos, err := strconv.Atoi(args[1]) - if err != nil { - setDirty(c) - c.WriteError(msgInvalidInt) + var opts struct { + key string + pos int + subst string + } + opts.key = args[0] + if ok := optInt(c, args[1], &opts.pos); !ok { return } - if pos < 0 { + if opts.pos < 0 { setDirty(c) c.WriteError("ERR offset is out of range") return } - subst := args[2] + opts.subst = args[2] withTx(m, c, func(c *server.Peer, ctx *connCtx) { db := m.db(ctx.selectedDB) - if t, ok := db.keys[key]; ok && t != "string" { + if t, ok := db.keys[opts.key]; ok && t != keyTypeString { c.WriteError(msgWrongType) return } - v := []byte(db.stringKeys[key]) - if len(v) < pos+len(subst) { - newV := make([]byte, pos+len(subst)) + v := []byte(db.stringKeys[opts.key]) + end := opts.pos + len(opts.subst) + if len(v) < end { + newV := make([]byte, end) copy(newV, v) v = newV } - copy(v[pos:pos+len(subst)], subst) - db.stringSet(key, string(v)) + copy(v[opts.pos:end], opts.subst) + db.stringSet(opts.key, string(v)) c.WriteInt(len(v)) }) } @@ -935,28 +956,20 @@ func (m *Miniredis) cmdBitcount(c *server.Peer, cmd string, args []string) { } var opts struct { - useRange bool - start, end int - key string + useRange bool + start int + end int + key string } opts.key, args = args[0], args[1:] if len(args) >= 2 { opts.useRange = true - var err error - n, err := strconv.Atoi(args[0]) - if err != nil { - setDirty(c) - c.WriteError(msgInvalidInt) + if ok := optInt(c, args[0], &opts.start); !ok { return } - opts.start = n - n, err = strconv.Atoi(args[1]) - if err != nil { - setDirty(c) - c.WriteError(msgInvalidInt) + if ok := optInt(c, args[1], &opts.end); !ok { return } - opts.end = n args = args[2:] } @@ -967,7 +980,7 @@ func (m *Miniredis) cmdBitcount(c *server.Peer, cmd string, args []string) { c.WriteInt(0) return } - if db.t(opts.key) != "string" { + if db.t(opts.key) != keyTypeString { c.WriteError(msgWrongType) return } @@ -1001,26 +1014,29 @@ func (m *Miniredis) cmdBitop(c *server.Peer, cmd string, args []string) { return } - var ( - op = strings.ToUpper(args[0]) - target = args[1] - input = args[2:] - ) + var opts struct { + op string + target string + input []string + } + opts.op = strings.ToUpper(args[0]) + opts.target = args[1] + opts.input = args[2:] // 'op' is tested when the transaction is executed. withTx(m, c, func(c *server.Peer, ctx *connCtx) { db := m.db(ctx.selectedDB) - switch op { + switch opts.op { case "AND", "OR", "XOR": - first := input[0] - if t, ok := db.keys[first]; ok && t != "string" { + first := opts.input[0] + if t, ok := db.keys[first]; ok && t != keyTypeString { c.WriteError(msgWrongType) return } res := []byte(db.stringKeys[first]) - for _, vk := range input[1:] { - if t, ok := db.keys[vk]; ok && t != "string" { + for _, vk := range opts.input[1:] { + if t, ok := db.keys[vk]; ok && t != keyTypeString { c.WriteError(msgWrongType) return } @@ -1029,24 +1045,24 @@ func (m *Miniredis) cmdBitop(c *server.Peer, cmd string, args []string) { "AND": func(a, b byte) byte { return a & b }, "OR": func(a, b byte) byte { return a | b }, "XOR": func(a, b byte) byte { return a ^ b }, - }[op] + }[opts.op] res = sliceBinOp(cb, res, []byte(v)) } - db.del(target, false) // Keep TTL + db.del(opts.target, false) // Keep TTL if len(res) == 0 { - db.del(target, true) + db.del(opts.target, true) } else { - db.stringSet(target, string(res)) + db.stringSet(opts.target, string(res)) } c.WriteInt(len(res)) case "NOT": // NOT only takes a single argument. - if len(input) != 1 { + if len(opts.input) != 1 { c.WriteError("ERR BITOP NOT must be called with a single source key.") return } - key := input[0] - if t, ok := db.keys[key]; ok && t != "string" { + key := opts.input[0] + if t, ok := db.keys[key]; ok && t != keyTypeString { c.WriteError(msgWrongType) return } @@ -1054,11 +1070,11 @@ func (m *Miniredis) cmdBitop(c *server.Peer, cmd string, args []string) { for i := range value { value[i] = ^value[i] } - db.del(target, false) // Keep TTL + db.del(opts.target, false) // Keep TTL if len(value) == 0 { - db.del(target, true) + db.del(opts.target, true) } else { - db.stringSet(target, string(value)) + db.stringSet(opts.target, string(value)) } c.WriteInt(len(value)) default: @@ -1108,7 +1124,7 @@ func (m *Miniredis) cmdBitpos(c *server.Peer, cmd string, args []string) { withTx(m, c, func(c *server.Peer, ctx *connCtx) { db := m.db(ctx.selectedDB) - if t, ok := db.keys[opts.Key]; ok && t != "string" { + if t, ok := db.keys[opts.Key]; ok && t != keyTypeString { c.WriteError(msgWrongType) return } else if !ok { @@ -1182,9 +1198,15 @@ func (m *Miniredis) cmdGetbit(c *server.Peer, cmd string, args []string) { return } - key := args[0] - bit, err := strconv.Atoi(args[1]) - if err != nil { + var opts struct { + key string + bit int + } + opts.key = args[0] + if ok := optIntErr(c, args[1], &opts.bit, "ERR bit offset is not an integer or out of range"); !ok { + return + } + if opts.bit < 0 { setDirty(c) c.WriteError("ERR bit offset is not an integer or out of range") return @@ -1193,13 +1215,13 @@ func (m *Miniredis) cmdGetbit(c *server.Peer, cmd string, args []string) { withTx(m, c, func(c *server.Peer, ctx *connCtx) { db := m.db(ctx.selectedDB) - if t, ok := db.keys[key]; ok && t != "string" { + if t, ok := db.keys[opts.key]; ok && t != keyTypeString { c.WriteError(msgWrongType) return } - value := db.stringKeys[key] + value := db.stringKeys[opts.key] - ourByteNr := bit / 8 + ourByteNr := opts.bit / 8 var ourByte byte if ourByteNr > len(value)-1 { ourByte = '\x00' @@ -1207,7 +1229,7 @@ func (m *Miniredis) cmdGetbit(c *server.Peer, cmd string, args []string) { ourByte = value[ourByteNr] } res := 0 - if toBits(ourByte)[bit%8] { + if toBits(ourByte)[opts.bit%8] { res = 1 } c.WriteInt(res) @@ -1228,15 +1250,24 @@ func (m *Miniredis) cmdSetbit(c *server.Peer, cmd string, args []string) { return } - key := args[0] - bit, err := strconv.Atoi(args[1]) - if err != nil || bit < 0 { + var opts struct { + key string + bit int + newBit int + } + opts.key = args[0] + if ok := optIntErr(c, args[1], &opts.bit, "ERR bit offset is not an integer or out of range"); !ok { + return + } + if opts.bit < 0 { setDirty(c) c.WriteError("ERR bit offset is not an integer or out of range") return } - newBit, err := strconv.Atoi(args[2]) - if err != nil || (newBit != 0 && newBit != 1) { + if ok := optIntErr(c, args[2], &opts.newBit, "ERR bit is not an integer or out of range"); !ok { + return + } + if opts.newBit != 0 && opts.newBit != 1 { setDirty(c) c.WriteError("ERR bit is not an integer or out of range") return @@ -1245,14 +1276,14 @@ func (m *Miniredis) cmdSetbit(c *server.Peer, cmd string, args []string) { withTx(m, c, func(c *server.Peer, ctx *connCtx) { db := m.db(ctx.selectedDB) - if t, ok := db.keys[key]; ok && t != "string" { + if t, ok := db.keys[opts.key]; ok && t != keyTypeString { c.WriteError(msgWrongType) return } - value := []byte(db.stringKeys[key]) + value := []byte(db.stringKeys[opts.key]) - ourByteNr := bit / 8 - ourBitNr := bit % 8 + ourByteNr := opts.bit / 8 + ourBitNr := opts.bit % 8 if ourByteNr > len(value)-1 { // Too short. Expand. newValue := make([]byte, ourByteNr+1) @@ -1263,12 +1294,12 @@ func (m *Miniredis) cmdSetbit(c *server.Peer, cmd string, args []string) { if toBits(value[ourByteNr])[ourBitNr] { old = 1 } - if newBit == 0 { + if opts.newBit == 0 { value[ourByteNr] &^= 1 << uint8(7-ourBitNr) } else { value[ourByteNr] |= 1 << uint8(7-ourBitNr) } - db.stringSet(key, string(value)) + db.stringSet(opts.key, string(value)) c.WriteInt(old) }) diff --git a/vendor/github.com/alicebob/miniredis/v2/cmd_transactions.go b/vendor/github.com/alicebob/miniredis/v2/cmd_transactions.go index 9cbcaf3b3..94729e004 100644 --- a/vendor/github.com/alicebob/miniredis/v2/cmd_transactions.go +++ b/vendor/github.com/alicebob/miniredis/v2/cmd_transactions.go @@ -30,7 +30,7 @@ func (m *Miniredis) cmdMulti(c *server.Peer, cmd string, args []string) { ctx := getCtx(c) if ctx.nested { - c.WriteError(msgNotFromScripts) + c.WriteError(msgNotFromScripts(ctx.nestedSHA)) return } if inTx(ctx) { @@ -59,7 +59,7 @@ func (m *Miniredis) cmdExec(c *server.Peer, cmd string, args []string) { ctx := getCtx(c) if ctx.nested { - c.WriteError(msgNotFromScripts) + c.WriteError(msgNotFromScripts(ctx.nestedSHA)) return } if !inTx(ctx) { @@ -137,7 +137,7 @@ func (m *Miniredis) cmdWatch(c *server.Peer, cmd string, args []string) { ctx := getCtx(c) if ctx.nested { - c.WriteError(msgNotFromScripts) + c.WriteError(msgNotFromScripts(ctx.nestedSHA)) return } if inTx(ctx) { diff --git a/vendor/github.com/alicebob/miniredis/v2/db.go b/vendor/github.com/alicebob/miniredis/v2/db.go index d6df96a2a..6af7ba3a6 100644 --- a/vendor/github.com/alicebob/miniredis/v2/db.go +++ b/vendor/github.com/alicebob/miniredis/v2/db.go @@ -3,6 +3,7 @@ package miniredis import ( "errors" "fmt" + "math" "math/big" "sort" "strconv" @@ -13,8 +14,12 @@ var ( errInvalidEntryID = errors.New("stream ID is invalid") ) +// exists also updates the lru func (db *RedisDB) exists(k string) bool { _, ok := db.keys[k] + if ok { + db.lru[k] = db.master.effectiveNow() + } return ok } @@ -23,6 +28,12 @@ func (db *RedisDB) t(k string) string { return db.keys[k] } +// incr increases the version and the lru timestamp +func (db *RedisDB) incr(k string) { + db.lru[k] = db.master.effectiveNow() + db.keyVersion[k]++ +} + // allKeys returns all keys. Sorted. func (db *RedisDB) allKeys() []string { res := make([]string, 0, len(db.keys)) @@ -36,6 +47,7 @@ func (db *RedisDB) allKeys() []string { // flush removes all keys and values. func (db *RedisDB) flush() { db.keys = map[string]string{} + db.lru = map[string]time.Time{} db.stringKeys = map[string]string{} db.hashKeys = map[string]hashKey{} db.listKeys = map[string]listKey{} @@ -58,27 +70,27 @@ func (db *RedisDB) move(key string, to *RedisDB) bool { } to.keys[key] = db.keys[key] switch t { - case "string": + case keyTypeString: to.stringKeys[key] = db.stringKeys[key] - case "hash": + case keyTypeHash: to.hashKeys[key] = db.hashKeys[key] - case "list": + case keyTypeList: to.listKeys[key] = db.listKeys[key] - case "set": + case keyTypeSet: to.setKeys[key] = db.setKeys[key] - case "zset": + case keyTypeSortedSet: to.sortedsetKeys[key] = db.sortedsetKeys[key] - case "stream": + case keyTypeStream: to.streamKeys[key] = db.streamKeys[key] - case "hll": + case keyTypeHll: to.hllKeys[key] = db.hllKeys[key] default: panic("unhandled key type") } - to.keyVersion[key]++ if v, ok := db.ttl[key]; ok { to.ttl[key] = v } + to.incr(key) db.del(key, true) return true } @@ -86,28 +98,28 @@ func (db *RedisDB) move(key string, to *RedisDB) bool { func (db *RedisDB) rename(from, to string) { db.del(to, true) switch db.t(from) { - case "string": + case keyTypeString: db.stringKeys[to] = db.stringKeys[from] - case "hash": + case keyTypeHash: db.hashKeys[to] = db.hashKeys[from] - case "list": + case keyTypeList: db.listKeys[to] = db.listKeys[from] - case "set": + case keyTypeSet: db.setKeys[to] = db.setKeys[from] - case "zset": + case keyTypeSortedSet: db.sortedsetKeys[to] = db.sortedsetKeys[from] - case "stream": + case keyTypeStream: db.streamKeys[to] = db.streamKeys[from] - case "hll": + case keyTypeHll: db.hllKeys[to] = db.hllKeys[from] default: panic("missing case") } db.keys[to] = db.keys[from] - db.keyVersion[to]++ if v, ok := db.ttl[from]; ok { db.ttl[to] = v } + db.incr(to) db.del(from, true) } @@ -118,24 +130,25 @@ func (db *RedisDB) del(k string, delTTL bool) { } t := db.t(k) delete(db.keys, k) + delete(db.lru, k) db.keyVersion[k]++ if delTTL { delete(db.ttl, k) } switch t { - case "string": + case keyTypeString: delete(db.stringKeys, k) - case "hash": + case keyTypeHash: delete(db.hashKeys, k) - case "list": + case keyTypeList: delete(db.listKeys, k) - case "set": + case keyTypeSet: delete(db.setKeys, k) - case "zset": + case keyTypeSortedSet: delete(db.sortedsetKeys, k) - case "stream": + case keyTypeStream: delete(db.streamKeys, k) - case "hll": + case keyTypeHll: delete(db.hllKeys, k) default: panic("Unknown key type: " + t) @@ -144,7 +157,7 @@ func (db *RedisDB) del(k string, delTTL bool) { // stringGet returns the string key or "" on error/nonexists. func (db *RedisDB) stringGet(k string) string { - if t, ok := db.keys[k]; !ok || t != "string" { + if t, ok := db.keys[k]; !ok || t != keyTypeString { return "" } return db.stringKeys[k] @@ -153,9 +166,9 @@ func (db *RedisDB) stringGet(k string) string { // stringSet force set()s a key. Does not touch expire. func (db *RedisDB) stringSet(k, v string) { db.del(k, false) - db.keys[k] = "string" + db.keys[k] = keyTypeString db.stringKeys[k] = v - db.keyVersion[k]++ + db.incr(k) } // change int key value @@ -168,6 +181,17 @@ func (db *RedisDB) stringIncr(k string, delta int) (int, error) { return 0, ErrIntValueError } } + + if delta > 0 { + if math.MaxInt-delta < v { + return 0, ErrIntValueOverflowError + } + } else { + if math.MinInt-delta > v { + return 0, ErrIntValueOverflowError + } + } + v += delta db.stringSet(k, strconv.Itoa(v)) return v, nil @@ -193,11 +217,11 @@ func (db *RedisDB) stringIncrfloat(k string, delta *big.Float) (*big.Float, erro func (db *RedisDB) listLpush(k, v string) int { l, ok := db.listKeys[k] if !ok { - db.keys[k] = "list" + db.keys[k] = keyTypeList } l = append([]string{v}, l...) db.listKeys[k] = l - db.keyVersion[k]++ + db.incr(k) return len(l) } @@ -211,18 +235,18 @@ func (db *RedisDB) listLpop(k string) string { } else { db.listKeys[k] = l } - db.keyVersion[k]++ + db.incr(k) return el } func (db *RedisDB) listPush(k string, v ...string) int { l, ok := db.listKeys[k] if !ok { - db.keys[k] = "list" + db.keys[k] = keyTypeList } l = append(l, v...) db.listKeys[k] = l - db.keyVersion[k]++ + db.incr(k) return len(l) } @@ -234,16 +258,16 @@ func (db *RedisDB) listPop(k string) string { db.del(k, true) } else { db.listKeys[k] = l - db.keyVersion[k]++ + db.incr(k) } return el } // setset replaces a whole set. func (db *RedisDB) setSet(k string, set setKey) { - db.keys[k] = "set" + db.keys[k] = keyTypeSet db.setKeys[k] = set - db.keyVersion[k]++ + db.incr(k) } // setadd adds members to a set. Returns nr of new keys. @@ -251,7 +275,7 @@ func (db *RedisDB) setAdd(k string, elems ...string) int { s, ok := db.setKeys[k] if !ok { s = setKey{} - db.keys[k] = "set" + db.keys[k] = keyTypeSet } added := 0 for _, e := range elems { @@ -261,7 +285,7 @@ func (db *RedisDB) setAdd(k string, elems ...string) int { s[e] = struct{}{} } db.setKeys[k] = s - db.keyVersion[k]++ + db.incr(k) return added } @@ -283,7 +307,7 @@ func (db *RedisDB) setRem(k string, fields ...string) int { } else { db.setKeys[k] = s } - db.keyVersion[k]++ + db.incr(k) return removed } @@ -337,10 +361,10 @@ func (db *RedisDB) hashGet(key, field string) string { // hashSet returns the number of new keys func (db *RedisDB) hashSet(k string, fv ...string) int { - if t, ok := db.keys[k]; ok && t != "hash" { + if t, ok := db.keys[k]; ok && t != keyTypeHash { db.del(k, true) } - db.keys[k] = "hash" + db.keys[k] = keyTypeHash if _, ok := db.hashKeys[k]; !ok { db.hashKeys[k] = map[string]string{} } @@ -349,7 +373,7 @@ func (db *RedisDB) hashSet(k string, fv ...string) int { f, v := fv[idx], fv[idx+1] _, ok := db.hashKeys[k][f] db.hashKeys[k][f] = v - db.keyVersion[k]++ + db.incr(k) if !ok { new++ } @@ -400,8 +424,8 @@ func (db *RedisDB) sortedSet(key string) map[string]float64 { // ssetSet sets a complete sorted set. func (db *RedisDB) ssetSet(key string, sset sortedSet) { - db.keys[key] = "zset" - db.keyVersion[key]++ + db.keys[key] = keyTypeSortedSet + db.incr(key) db.sortedsetKeys[key] = sset } @@ -410,12 +434,12 @@ func (db *RedisDB) ssetAdd(key string, score float64, member string) bool { ss, ok := db.sortedsetKeys[key] if !ok { ss = newSortedSet() - db.keys[key] = "zset" + db.keys[key] = keyTypeSortedSet } _, ok = ss[member] ss[member] = score db.sortedsetKeys[key] = ss - db.keyVersion[key]++ + db.incr(key) return !ok } @@ -468,6 +492,16 @@ func (db *RedisDB) ssetScore(key, member string) float64 { return ss[member] } +// ssetMScore returns multiple scores of a list of members in a sorted set. +func (db *RedisDB) ssetMScore(key string, members []string) []float64 { + scores := make([]float64, 0, len(members)) + ss := db.sortedsetKeys[key] + for _, member := range members { + scores = append(scores, ss[member]) + } + return scores +} + // ssetRem is sorted set key delete. func (db *RedisDB) ssetRem(key, member string) bool { ss := db.sortedsetKeys[key] @@ -492,14 +526,14 @@ func (db *RedisDB) ssetIncrby(k, m string, delta float64) float64 { ss, ok := db.sortedsetKeys[k] if !ok { ss = newSortedSet() - db.keys[k] = "zset" + db.keys[k] = keyTypeSortedSet db.sortedsetKeys[k] = ss } v, _ := ss.get(m) v += delta ss.set(v, m) - db.keyVersion[k]++ + db.incr(k) return v } @@ -507,7 +541,7 @@ func (db *RedisDB) ssetIncrby(k, m string, delta float64) float64 { func (db *RedisDB) setDiff(keys []string) (setKey, error) { key := keys[0] keys = keys[1:] - if db.exists(key) && db.t(key) != "set" { + if db.exists(key) && db.t(key) != keyTypeSet { return nil, ErrWrongType } s := setKey{} @@ -518,7 +552,7 @@ func (db *RedisDB) setDiff(keys []string) (setKey, error) { if !db.exists(sk) { continue } - if db.t(sk) != "set" { + if db.t(sk) != keyTypeSet { return nil, ErrWrongType } for e := range db.setKeys[sk] { @@ -533,7 +567,7 @@ func (db *RedisDB) setDiff(keys []string) (setKey, error) { func (db *RedisDB) setInter(keys []string) (setKey, error) { // all keys must either not exist, or be of type "set". for _, key := range keys { - if db.exists(key) && db.t(key) != "set" { + if db.exists(key) && db.t(key) != keyTypeSet { return nil, ErrWrongType } } @@ -543,7 +577,7 @@ func (db *RedisDB) setInter(keys []string) (setKey, error) { if !db.exists(key) { return nil, nil } - if db.t(key) != "set" { + if db.t(key) != keyTypeSet { return nil, ErrWrongType } s := setKey{} @@ -554,7 +588,7 @@ func (db *RedisDB) setInter(keys []string) (setKey, error) { if !db.exists(sk) { return setKey{}, nil } - if db.t(sk) != "set" { + if db.t(sk) != keyTypeSet { return nil, ErrWrongType } other := db.setKeys[sk] @@ -568,6 +602,54 @@ func (db *RedisDB) setInter(keys []string) (setKey, error) { return s, nil } +// setIntercard implements the logic behind SINTER* +// len keys needs to be > 0 +func (db *RedisDB) setIntercard(keys []string, limit int) (int, error) { + // all keys must either not exist, or be of type "set". + allExist := true + for _, key := range keys { + exists := db.exists(key) + allExist = allExist && exists + if exists && db.t(key) != "set" { + return 0, ErrWrongType + } + } + + if !allExist { + return 0, nil + } + + smallestKey := keys[0] + smallestIdx := 0 + for i, key := range keys { + if len(db.setKeys[key]) < len(db.setKeys[smallestKey]) { + smallestKey = key + smallestIdx = i + } + } + keys[smallestIdx] = keys[len(keys)-1] + keys = keys[:len(keys)-1] + + count := 0 + for item := range db.setKeys[smallestKey] { + inIntersection := true + for _, key := range keys { + if _, ok := db.setKeys[key][item]; !ok { + inIntersection = false + break + } + } + if inIntersection { + count++ + if count == limit { + break + } + } + } + + return count, nil +} + // setUnion implements the logic behind SUNION* func (db *RedisDB) setUnion(keys []string) (setKey, error) { key := keys[0] @@ -600,16 +682,16 @@ func (db *RedisDB) newStream(key string) (*streamKey, error) { return nil, fmt.Errorf("ErrAlreadyExists") } - db.keys[key] = "stream" + db.keys[key] = keyTypeStream s := newStreamKey() db.streamKeys[key] = s - db.keyVersion[key]++ + db.incr(key) return s, nil } // return existing stream, or nil. func (db *RedisDB) stream(key string) (*streamKey, error) { - if db.exists(key) && db.t(key) != "stream" { + if db.exists(key) && db.t(key) != keyTypeStream { return nil, ErrWrongType } @@ -646,7 +728,7 @@ func (db *RedisDB) hllAdd(k string, elems ...string) int { s, ok := db.hllKeys[k] if !ok { s = newHll() - db.keys[k] = "hll" + db.keys[k] = keyTypeHll } hllAltered := 0 for _, e := range elems { @@ -655,7 +737,7 @@ func (db *RedisDB) hllAdd(k string, elems ...string) int { } } db.hllKeys[k] = s - db.keyVersion[k]++ + db.incr(k) return hllAltered } @@ -663,7 +745,7 @@ func (db *RedisDB) hllAdd(k string, elems ...string) int { func (db *RedisDB) hllCount(keys []string) (int, error) { countOverall := 0 for _, key := range keys { - if db.exists(key) && db.t(key) != "hll" { + if db.exists(key) && db.t(key) != keyTypeHll { return 0, ErrNotValidHllValue } if !db.exists(key) { @@ -678,7 +760,7 @@ func (db *RedisDB) hllCount(keys []string) (int, error) { // hllMerge merges all the hlls provided as keys to the first key. Creates a new hll in the first key if it contains nothing func (db *RedisDB) hllMerge(keys []string) error { for _, key := range keys { - if db.exists(key) && db.t(key) != "hll" { + if db.exists(key) && db.t(key) != keyTypeHll { return ErrNotValidHllValue } } @@ -701,8 +783,8 @@ func (db *RedisDB) hllMerge(keys []string) error { } db.hllKeys[destKey] = destHll - db.keys[destKey] = "hll" - db.keyVersion[destKey]++ + db.keys[destKey] = keyTypeHll + db.incr(destKey) return nil } diff --git a/vendor/github.com/alicebob/miniredis/v2/direct.go b/vendor/github.com/alicebob/miniredis/v2/direct.go index cd2323c63..88ef361e9 100644 --- a/vendor/github.com/alicebob/miniredis/v2/direct.go +++ b/vendor/github.com/alicebob/miniredis/v2/direct.go @@ -21,6 +21,9 @@ var ( // ErrIntValueError can returned by INCRBY ErrIntValueError = errors.New(msgInvalidInt) + // ErrIntValueOverflowError can be returned by INCR, DECR, INCRBY, DECRBY + ErrIntValueOverflowError = errors.New(msgIntOverflow) + // ErrFloatValueError can returned by INCRBYFLOAT ErrFloatValueError = errors.New(msgInvalidFloat) ) @@ -87,7 +90,7 @@ func (db *RedisDB) Get(k string) (string, error) { if !db.exists(k) { return "", ErrKeyNotFound } - if db.t(k) != "string" { + if db.t(k) != keyTypeString { return "", ErrWrongType } return db.stringGet(k), nil @@ -105,7 +108,7 @@ func (db *RedisDB) Set(k, v string) error { defer db.master.Unlock() defer db.master.signal.Broadcast() - if db.exists(k) && db.t(k) != "string" { + if db.exists(k) && db.t(k) != keyTypeString { return ErrWrongType } db.del(k, true) // Remove expire @@ -124,7 +127,7 @@ func (db *RedisDB) Incr(k string, delta int) (int, error) { defer db.master.Unlock() defer db.master.signal.Broadcast() - if db.exists(k) && db.t(k) != "string" { + if db.exists(k) && db.t(k) != keyTypeString { return 0, ErrWrongType } @@ -148,7 +151,7 @@ func (db *RedisDB) Incrfloat(k string, delta float64) (float64, error) { defer db.master.Unlock() defer db.master.signal.Broadcast() - if db.exists(k) && db.t(k) != "string" { + if db.exists(k) && db.t(k) != keyTypeString { return 0, ErrWrongType } @@ -177,7 +180,7 @@ func (db *RedisDB) List(k string) ([]string, error) { if !db.exists(k) { return nil, ErrKeyNotFound } - if db.t(k) != "list" { + if db.t(k) != keyTypeList { return nil, ErrWrongType } return db.listKeys[k], nil @@ -194,7 +197,7 @@ func (db *RedisDB) Lpush(k, v string) (int, error) { defer db.master.Unlock() defer db.master.signal.Broadcast() - if db.exists(k) && db.t(k) != "list" { + if db.exists(k) && db.t(k) != keyTypeList { return 0, ErrWrongType } return db.listLpush(k, v), nil @@ -214,7 +217,7 @@ func (db *RedisDB) Lpop(k string) (string, error) { if !db.exists(k) { return "", ErrKeyNotFound } - if db.t(k) != "list" { + if db.t(k) != keyTypeList { return "", ErrWrongType } return db.listLpop(k), nil @@ -237,7 +240,7 @@ func (db *RedisDB) Push(k string, v ...string) (int, error) { defer db.master.Unlock() defer db.master.signal.Broadcast() - if db.exists(k) && db.t(k) != "list" { + if db.exists(k) && db.t(k) != keyTypeList { return 0, ErrWrongType } return db.listPush(k, v...), nil @@ -262,7 +265,7 @@ func (db *RedisDB) Pop(k string) (string, error) { if !db.exists(k) { return "", ErrKeyNotFound } - if db.t(k) != "list" { + if db.t(k) != keyTypeList { return "", ErrWrongType } @@ -286,7 +289,7 @@ func (db *RedisDB) SetAdd(k string, elems ...string) (int, error) { defer db.master.Unlock() defer db.master.signal.Broadcast() - if db.exists(k) && db.t(k) != "set" { + if db.exists(k) && db.t(k) != keyTypeSet { return 0, ErrWrongType } return db.setAdd(k, elems...), nil @@ -311,7 +314,7 @@ func (db *RedisDB) Members(k string) ([]string, error) { if !db.exists(k) { return nil, ErrKeyNotFound } - if db.t(k) != "set" { + if db.t(k) != keyTypeSet { return nil, ErrWrongType } return db.setMembers(k), nil @@ -336,7 +339,7 @@ func (db *RedisDB) IsMember(k, v string) (bool, error) { if !db.exists(k) { return false, ErrKeyNotFound } - if db.t(k) != "set" { + if db.t(k) != keyTypeSet { return false, ErrWrongType } return db.setIsMember(k, v), nil @@ -355,7 +358,7 @@ func (db *RedisDB) HKeys(key string) ([]string, error) { if !db.exists(key) { return nil, ErrKeyNotFound } - if db.t(key) != "hash" { + if db.t(key) != keyTypeHash { return nil, ErrWrongType } return db.hashFields(key), nil @@ -421,7 +424,7 @@ func (db *RedisDB) SetTTL(k string, ttl time.Duration) { defer db.master.signal.Broadcast() db.ttl[k] = ttl - db.keyVersion[k]++ + db.incr(k) } // Type gives the type of a key, or "" @@ -506,7 +509,7 @@ func (db *RedisDB) hdel(k, f string) { return } delete(db.hashKeys[k], f) - db.keyVersion[k]++ + db.incr(k) } // HIncrBy increases the integer value of a hash field by delta (int). @@ -566,7 +569,7 @@ func (db *RedisDB) SRem(k string, fields ...string) (int, error) { if !db.exists(k) { return 0, ErrKeyNotFound } - if db.t(k) != "set" { + if db.t(k) != keyTypeSet { return 0, ErrWrongType } return db.setRem(k, fields...), nil @@ -583,7 +586,7 @@ func (db *RedisDB) ZAdd(k string, score float64, member string) (bool, error) { defer db.master.Unlock() defer db.master.signal.Broadcast() - if db.exists(k) && db.t(k) != "zset" { + if db.exists(k) && db.t(k) != keyTypeSortedSet { return false, ErrWrongType } return db.ssetAdd(k, score, member), nil @@ -602,7 +605,7 @@ func (db *RedisDB) ZMembers(k string) ([]string, error) { if !db.exists(k) { return nil, ErrKeyNotFound } - if db.t(k) != "zset" { + if db.t(k) != keyTypeSortedSet { return nil, ErrWrongType } return db.ssetMembers(k), nil @@ -621,7 +624,7 @@ func (db *RedisDB) SortedSet(k string) (map[string]float64, error) { if !db.exists(k) { return nil, ErrKeyNotFound } - if db.t(k) != "zset" { + if db.t(k) != keyTypeSortedSet { return nil, ErrWrongType } return db.sortedSet(k), nil @@ -641,7 +644,7 @@ func (db *RedisDB) ZRem(k, member string) (bool, error) { if !db.exists(k) { return false, ErrKeyNotFound } - if db.t(k) != "zset" { + if db.t(k) != keyTypeSortedSet { return false, ErrWrongType } return db.ssetRem(k, member), nil @@ -660,12 +663,30 @@ func (db *RedisDB) ZScore(k, member string) (float64, error) { if !db.exists(k) { return 0, ErrKeyNotFound } - if db.t(k) != "zset" { + if db.t(k) != keyTypeSortedSet { return 0, ErrWrongType } return db.ssetScore(k, member), nil } +// ZScore gives scores of a list of members in a sorted set. +func (m *Miniredis) ZMScore(k string, members ...string) ([]float64, error) { + return m.DB(m.selectedDB).ZMScore(k, members) +} + +func (db *RedisDB) ZMScore(k string, members []string) ([]float64, error) { + db.master.Lock() + defer db.master.Unlock() + + if !db.exists(k) { + return nil, ErrKeyNotFound + } + if db.t(k) != keyTypeSortedSet { + return nil, ErrWrongType + } + return db.ssetMScore(k, members), nil +} + // XAdd adds an entry to a stream. `id` can be left empty or be '*'. // If a value is given normal XADD rules apply. Values should be an even // length. @@ -762,7 +783,7 @@ func (db *RedisDB) HllAdd(k string, elems ...string) (int, error) { db.master.Lock() defer db.master.Unlock() - if db.exists(k) && db.t(k) != "hll" { + if db.exists(k) && db.t(k) != keyTypeHll { return 0, ErrWrongType } return db.hllAdd(k, elems...), nil diff --git a/vendor/github.com/alicebob/miniredis/v2/fpconv/LICENSE.txt b/vendor/github.com/alicebob/miniredis/v2/fpconv/LICENSE.txt new file mode 100644 index 000000000..0a0af2e8f --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/v2/fpconv/LICENSE.txt @@ -0,0 +1,26 @@ +This code is derived from the C code in redis-7.2.0/deps/fpconv/*, which has +this license: + +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +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, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN 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/vendor/github.com/alicebob/miniredis/v2/fpconv/Makefile b/vendor/github.com/alicebob/miniredis/v2/fpconv/Makefile new file mode 100644 index 000000000..d32d4bdcd --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/v2/fpconv/Makefile @@ -0,0 +1,6 @@ +.PHONY: test fuzz +test: + go test + +fuzz: + go test -fuzz=Fuzz diff --git a/vendor/github.com/alicebob/miniredis/v2/fpconv/README.md b/vendor/github.com/alicebob/miniredis/v2/fpconv/README.md new file mode 100644 index 000000000..c210e6033 --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/v2/fpconv/README.md @@ -0,0 +1,3 @@ +This is a translation of the actual C code in Redis (7.2) which does the float +-> string conversion. +Strconv does a close enough job, but we can use the exact same logic, so why not. diff --git a/vendor/github.com/alicebob/miniredis/v2/fpconv/dtoa.go b/vendor/github.com/alicebob/miniredis/v2/fpconv/dtoa.go new file mode 100644 index 000000000..251fc4f3b --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/v2/fpconv/dtoa.go @@ -0,0 +1,286 @@ +package fpconv + +import ( + "math" +) + +var ( + fracmask uint64 = 0x000FFFFFFFFFFFFF + expmask uint64 = 0x7FF0000000000000 + hiddenbit uint64 = 0x0010000000000000 + signmask uint64 = 0x8000000000000000 + expbias int64 = 1023 + 52 + zeros = []rune("0000000000000000000000") + + tens = []uint64{ + 10000000000000000000, + 1000000000000000000, + 100000000000000000, + 10000000000000000, + 1000000000000000, + 100000000000000, + 10000000000000, + 1000000000000, + 100000000000, + 10000000000, + 1000000000, + 100000000, + 10000000, + 1000000, + 100000, + 10000, + 1000, + 100, + 10, + 1} +) + +func absv(n int) int { + if n < 0 { + return -n + } + return n +} + +func minv(a, b int) int { + if a < b { + return a + } + return b +} + +func Dtoa(d float64) string { + var ( + dest [25]rune // Note C has 24, which is broken + digits [18]rune + + str_len int = 0 + neg = false + ) + + if get_dbits(d)&signmask != 0 { + dest[0] = '-' + str_len++ + neg = true + } + + if spec := filter_special(d, dest[str_len:]); spec != 0 { + return string(dest[:str_len+spec]) + } + + var ( + k int = 0 + ndigits int = grisu2(d, &digits, &k) + ) + + str_len += emit_digits(&digits, ndigits, dest[str_len:], k, neg) + return string(dest[:str_len]) +} + +func filter_special(fp float64, dest []rune) int { + if fp == 0.0 { + dest[0] = '0' + return 1 + } + + if math.IsNaN(fp) { + dest[0] = 'n' + dest[1] = 'a' + dest[2] = 'n' + return 3 + } + if math.IsInf(fp, 0) { + dest[0] = 'i' + dest[1] = 'n' + dest[2] = 'f' + return 3 + } + return 0 +} + +func grisu2(d float64, digits *[18]rune, K *int) int { + w := build_fp(d) + + lower, upper := get_normalized_boundaries(w) + + w = normalize(w) + + var k int64 + cp := find_cachedpow10(upper.exp, &k) + + w = multiply(w, cp) + upper = multiply(upper, cp) + lower = multiply(lower, cp) + + lower.frac++ + upper.frac-- + + *K = int(-k) + + return generate_digits(w, upper, lower, digits[:], K) +} + +func emit_digits(digits *[18]rune, ndigits int, dest []rune, K int, neg bool) int { + exp := int(absv(K + ndigits - 1)) + + /* write plain integer */ + if K >= 0 && (exp < (ndigits + 7)) { + copy(dest, digits[:ndigits]) + copy(dest[ndigits:], zeros[:K]) + + return ndigits + K + } + + /* write decimal w/o scientific notation */ + if K < 0 && (K > -7 || exp < 4) { + offset := int(ndigits - absv(K)) + /* fp < 1.0 -> write leading zero */ + if offset <= 0 { + offset = -offset + dest[0] = '0' + dest[1] = '.' + copy(dest[2:], zeros[:offset]) + copy(dest[offset+2:], digits[:ndigits]) + + return ndigits + 2 + offset + + /* fp > 1.0 */ + } else { + copy(dest, digits[:offset]) + dest[offset] = '.' + copy(dest[offset+1:], digits[offset:offset+ndigits-offset]) + + return ndigits + 1 + } + } + /* write decimal w/ scientific notation */ + l := 18 // was: 18-neg + if neg { + l-- + } + ndigits = minv(ndigits, l) + + var idx int = 0 + dest[idx] = digits[0] + idx++ + + if ndigits > 1 { + dest[idx] = '.' + idx++ + copy(dest[idx:], digits[+1:ndigits-1+1]) + idx += ndigits - 1 + } + + dest[idx] = 'e' + idx++ + + sign := '+' + if K+ndigits-1 < 0 { + sign = '-' + } + dest[idx] = sign + idx++ + + var cent rune = 0 + + if exp > 99 { + cent = rune(exp / 100) + dest[idx] = cent + '0' + idx++ + exp -= int(cent) * 100 + } + if exp > 9 { + dec := rune(exp / 10) + dest[idx] = dec + '0' + idx++ + exp -= int(dec) * 10 + } else if cent != 0 { + dest[idx] = '0' + idx++ + } + + dest[idx] = rune(exp%10) + '0' + idx++ + + return idx +} + +func generate_digits(fp, upper, lower Fp, digits []rune, K *int) int { + var ( + wfrac = uint64(upper.frac - fp.frac) + delta = uint64(upper.frac - lower.frac) + ) + + one := Fp{ + frac: 1 << -upper.exp, + exp: upper.exp, + } + + part1 := uint64(upper.frac >> -one.exp) + part2 := uint64(upper.frac & (one.frac - 1)) + + var ( + idx = 0 + kappa = 10 + index = 10 + ) + /* 1000000000 */ + for ; kappa > 0; index++ { + div := tens[index] + digit := part1 / div + + if digit != 0 || idx != 0 { + digits[idx] = rune(digit) + '0' + idx++ + } + + part1 -= digit * div + kappa-- + + tmp := (part1 << -one.exp) + part2 + if tmp <= delta { + *K += kappa + round_digit(digits, idx, delta, tmp, div<<-one.exp, wfrac) + + return idx + } + } + + /* 10 */ + index = 18 + for { + var unit uint64 = tens[index] + part2 *= 10 + delta *= 10 + kappa-- + + digit := part2 >> -one.exp + if digit != 0 || idx != 0 { + digits[idx] = rune(digit) + '0' + idx++ + } + + part2 &= uint64(one.frac) - 1 + if part2 < delta { + *K += kappa + round_digit(digits, idx, delta, part2, uint64(one.frac), wfrac*unit) + + return idx + } + + index-- + } +} + +func round_digit(digits []rune, + ndigits int, + delta uint64, + rem uint64, + kappa uint64, + frac uint64) { + for rem < frac && delta-rem >= kappa && + (rem+kappa < frac || frac-rem > rem+kappa-frac) { + digits[ndigits-1]-- + rem += kappa + } +} diff --git a/vendor/github.com/alicebob/miniredis/v2/fpconv/fp.go b/vendor/github.com/alicebob/miniredis/v2/fpconv/fp.go new file mode 100644 index 000000000..490646363 --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/v2/fpconv/fp.go @@ -0,0 +1,96 @@ +package fpconv + +import ( + "math" +) + +type ( + Fp struct { + frac uint64 + exp int64 + } +) + +func build_fp(d float64) Fp { + bits := get_dbits(d) + + fp := Fp{ + frac: bits & fracmask, + exp: int64((bits & expmask) >> 52), + } + + if fp.exp != 0 { + fp.frac += hiddenbit + fp.exp -= expbias + } else { + fp.exp = -expbias + 1 + } + + return fp +} + +func normalize(fp Fp) Fp { + for (fp.frac & hiddenbit) == 0 { + fp.frac <<= 1 + fp.exp-- + } + + var shift int64 = 64 - 52 - 1 + fp.frac <<= shift + fp.exp -= shift + return fp +} + +func multiply(a, b Fp) Fp { + lomask := uint64(0x00000000FFFFFFFF) + + var ( + ah_bl = uint64((a.frac >> 32) * (b.frac & lomask)) + al_bh = uint64((a.frac & lomask) * (b.frac >> 32)) + al_bl = uint64((a.frac & lomask) * (b.frac & lomask)) + ah_bh = uint64((a.frac >> 32) * (b.frac >> 32)) + ) + + tmp := uint64((ah_bl & lomask) + (al_bh & lomask) + (al_bl >> 32)) + /* round up */ + tmp += uint64(1) << 31 + + return Fp{ + ah_bh + (ah_bl >> 32) + (al_bh >> 32) + (tmp >> 32), + a.exp + b.exp + 64, + } +} + +func get_dbits(d float64) uint64 { + return math.Float64bits(d) +} + +func get_normalized_boundaries(fp Fp) (Fp, Fp) { + upper := Fp{ + frac: (fp.frac << 1) + 1, + exp: fp.exp - 1, + } + for (upper.frac & (hiddenbit << 1)) == 0 { + upper.frac <<= 1 + upper.exp-- + } + + var u_shift int64 = 64 - 52 - 2 + + upper.frac <<= u_shift + upper.exp = upper.exp - u_shift + + l_shift := int64(1) + if fp.frac == hiddenbit { + l_shift = 2 + } + + lower := Fp{ + frac: (fp.frac << l_shift) - 1, + exp: fp.exp - l_shift, + } + + lower.frac <<= lower.exp - upper.exp + lower.exp = upper.exp + return lower, upper +} diff --git a/vendor/github.com/alicebob/miniredis/v2/fpconv/powers.go b/vendor/github.com/alicebob/miniredis/v2/fpconv/powers.go new file mode 100644 index 000000000..24725f914 --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/v2/fpconv/powers.go @@ -0,0 +1,82 @@ +package fpconv + +var ( + npowers int64 = 87 + steppowers int64 = 8 + firstpower int64 = -348 /* 10 ^ -348 */ + + expmax = -32 + expmin = -60 + + powers_ten = []Fp{ + {18054884314459144840, -1220}, {13451937075301367670, -1193}, + {10022474136428063862, -1166}, {14934650266808366570, -1140}, + {11127181549972568877, -1113}, {16580792590934885855, -1087}, + {12353653155963782858, -1060}, {18408377700990114895, -1034}, + {13715310171984221708, -1007}, {10218702384817765436, -980}, + {15227053142812498563, -954}, {11345038669416679861, -927}, + {16905424996341287883, -901}, {12595523146049147757, -874}, + {9384396036005875287, -847}, {13983839803942852151, -821}, + {10418772551374772303, -794}, {15525180923007089351, -768}, + {11567161174868858868, -741}, {17236413322193710309, -715}, + {12842128665889583758, -688}, {9568131466127621947, -661}, + {14257626930069360058, -635}, {10622759856335341974, -608}, + {15829145694278690180, -582}, {11793632577567316726, -555}, + {17573882009934360870, -529}, {13093562431584567480, -502}, + {9755464219737475723, -475}, {14536774485912137811, -449}, + {10830740992659433045, -422}, {16139061738043178685, -396}, + {12024538023802026127, -369}, {17917957937422433684, -343}, + {13349918974505688015, -316}, {9946464728195732843, -289}, + {14821387422376473014, -263}, {11042794154864902060, -236}, + {16455045573212060422, -210}, {12259964326927110867, -183}, + {18268770466636286478, -157}, {13611294676837538539, -130}, + {10141204801825835212, -103}, {15111572745182864684, -77}, + {11258999068426240000, -50}, {16777216000000000000, -24}, + {12500000000000000000, 3}, {9313225746154785156, 30}, + {13877787807814456755, 56}, {10339757656912845936, 83}, + {15407439555097886824, 109}, {11479437019748901445, 136}, + {17105694144590052135, 162}, {12744735289059618216, 189}, + {9495567745759798747, 216}, {14149498560666738074, 242}, + {10542197943230523224, 269}, {15709099088952724970, 295}, + {11704190886730495818, 322}, {17440603504673385349, 348}, + {12994262207056124023, 375}, {9681479787123295682, 402}, + {14426529090290212157, 428}, {10748601772107342003, 455}, + {16016664761464807395, 481}, {11933345169920330789, 508}, + {17782069995880619868, 534}, {13248674568444952270, 561}, + {9871031767461413346, 588}, {14708983551653345445, 614}, + {10959046745042015199, 641}, {16330252207878254650, 667}, + {12166986024289022870, 694}, {18130221999122236476, 720}, + {13508068024458167312, 747}, {10064294952495520794, 774}, + {14996968138956309548, 800}, {11173611982879273257, 827}, + {16649979327439178909, 853}, {12405201291620119593, 880}, + {9242595204427927429, 907}, {13772540099066387757, 933}, + {10261342003245940623, 960}, {15290591125556738113, 986}, + {11392378155556871081, 1013}, {16975966327722178521, 1039}, + {12648080533535911531, 1066}, + } +) + +func find_cachedpow10(exp int64, k *int64) Fp { + one_log_ten := 0.30102999566398114 + + approx := int64(float64(-(exp + npowers)) * one_log_ten) + idx := int((approx - firstpower) / steppowers) + + for { + current := int(exp + powers_ten[idx].exp + 64) + + if current < expmin { + idx++ + continue + } + + if current > expmax { + idx-- + continue + } + + *k = (firstpower + int64(idx)*steppowers) + + return powers_ten[idx] + } +} diff --git a/vendor/github.com/alicebob/miniredis/v2/geo.go b/vendor/github.com/alicebob/miniredis/v2/geo.go index bc8e92927..3028a1670 100644 --- a/vendor/github.com/alicebob/miniredis/v2/geo.go +++ b/vendor/github.com/alicebob/miniredis/v2/geo.go @@ -21,12 +21,10 @@ func hsin(theta float64) float64 { } // distance function returns the distance (in meters) between two points of -// a given longitude and latitude relatively accurately (using a spherical -// approximation of the Earth) through the Haversin Distance Formula for -// great arc distance on a sphere with accuracy for small distances -// +// a given longitude and latitude relatively accurately (using a spherical +// approximation of the Earth) through the Haversin Distance Formula for +// great arc distance on a sphere with accuracy for small distances // point coordinates are supplied in degrees and converted into rad. in the func -// // distance returned is meters // http://en.wikipedia.org/wiki/Haversine_formula // Source: https://gist.github.com/cdipaolo/d3f8db3848278b49db68 diff --git a/vendor/github.com/alicebob/gopher-json/LICENSE b/vendor/github.com/alicebob/miniredis/v2/gopher-json/LICENSE similarity index 100% rename from vendor/github.com/alicebob/gopher-json/LICENSE rename to vendor/github.com/alicebob/miniredis/v2/gopher-json/LICENSE diff --git a/vendor/github.com/alicebob/miniredis/v2/gopher-json/README.md b/vendor/github.com/alicebob/miniredis/v2/gopher-json/README.md new file mode 100644 index 000000000..0459a1d8e --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/v2/gopher-json/README.md @@ -0,0 +1 @@ +Copied from https://github.com/layeh/gopher-json and https://github.com/alicebob/gopher-json diff --git a/vendor/github.com/alicebob/gopher-json/json.go b/vendor/github.com/alicebob/miniredis/v2/gopher-json/json.go similarity index 100% rename from vendor/github.com/alicebob/gopher-json/json.go rename to vendor/github.com/alicebob/miniredis/v2/gopher-json/json.go diff --git a/vendor/github.com/alicebob/miniredis/v2/lua.go b/vendor/github.com/alicebob/miniredis/v2/lua.go index 42222dce8..f62373921 100644 --- a/vendor/github.com/alicebob/miniredis/v2/lua.go +++ b/vendor/github.com/alicebob/miniredis/v2/lua.go @@ -18,7 +18,7 @@ var luaRedisConstants = map[string]lua.LValue{ "LOG_WARNING": lua.LNumber(3), } -func mkLua(srv *server.Server, c *server.Peer) (map[string]lua.LGFunction, map[string]lua.LValue) { +func mkLua(srv *server.Server, c *server.Peer, sha string) (map[string]lua.LGFunction, map[string]lua.LValue) { mkCall := func(failFast bool) func(l *lua.LState) int { // one server.Ctx for a single Lua run pCtx := &connCtx{} @@ -26,12 +26,13 @@ func mkLua(srv *server.Server, c *server.Peer) (map[string]lua.LGFunction, map[s pCtx.authenticated = true } pCtx.nested = true + pCtx.nestedSHA = sha pCtx.selectedDB = getCtx(c).selectedDB return func(l *lua.LState) int { top := l.GetTop() if top == 0 { - l.Error(lua.LString("Please specify at least one argument for redis.call()"), 1) + l.Error(lua.LString(fmt.Sprintf("Please specify at least one argument for this redis lib call script: %s, &c.", sha)), 1) return 0 } var args []string @@ -42,12 +43,12 @@ func mkLua(srv *server.Server, c *server.Peer) (map[string]lua.LGFunction, map[s case lua.LString: args = append(args, string(a)) default: - l.Error(lua.LString("Lua redis() command arguments must be strings or integers"), 1) + l.Error(lua.LString(fmt.Sprintf("Lua redis lib command arguments must be strings or integers script: %s, &c.", sha)), 1) return 0 } } if len(args) == 0 { - l.Error(lua.LString(msgNotFromScripts), 1) + l.Error(lua.LString(msgNotFromScripts(sha)), 1) return 0 } @@ -63,7 +64,7 @@ func mkLua(srv *server.Server, c *server.Peer) (map[string]lua.LGFunction, map[s if failFast { // call() mode if strings.Contains(err.Error(), "ERR unknown command") { - l.Error(lua.LString("Unknown Redis command called from Lua script"), 1) + l.Error(lua.LString(fmt.Sprintf("Unknown Redis command called from script script: %s, &c.", sha)), 1) } else { l.Error(lua.LString(err.Error()), 1) } @@ -112,7 +113,18 @@ func mkLua(srv *server.Server, c *server.Peer) (map[string]lua.LGFunction, map[s return 0 } res := &lua.LTable{} - res.RawSetString("err", lua.LString(msg)) + parts := strings.SplitN(msg.String(), " ", 2) + // '-' at the beginging will be added as a part of error response + if parts[0] != "" && parts[0][0] == '-' { + parts[0] = parts[0][1:] + } + var final_msg string + if len(parts) == 2 { + final_msg = fmt.Sprintf("%s %s", parts[0], parts[1]) + } else { + final_msg = fmt.Sprintf("ERR %s", parts[0]) + } + res.RawSetString("err", lua.LString(final_msg)) l.Push(res) return 1 }, @@ -146,9 +158,34 @@ func mkLua(srv *server.Server, c *server.Peer) (map[string]lua.LGFunction, map[s return 1 }, "replicate_commands": func(l *lua.LState) int { + // always succeeds since 7.0.0 + l.Push(lua.LTrue) + return 1 + }, + "set_repl": func(l *lua.LState) int { + top := l.GetTop() + if top != 1 { + l.Error(lua.LString("wrong number of arguments"), 1) + return 0 + } // ignored return 1 }, + "setresp": func(l *lua.LState) int { + level := l.CheckInt(1) + toresp3 := false + switch level { + case 2: + toresp3 = false + case 3: + toresp3 = true + default: + l.Error(lua.LString("RESP version must be 2 or 3"), 1) + return 0 + } + c.SwitchResp3 = &toresp3 + return 0 + }, }, luaRedisConstants } @@ -205,7 +242,7 @@ func luaToRedis(l *lua.LState, c *server.Peer, value lua.LValue) { luaToRedis(l, c, r) } default: - panic("....") + panic(fmt.Sprintf("wat: %T", t)) } } @@ -217,6 +254,8 @@ func redisToLua(l *lua.LState, res []interface{}) *lua.LTable { v = lua.LFalse } else { switch et := e.(type) { + case int: + v = lua.LNumber(et) case int64: v = lua.LNumber(et) case []uint8: diff --git a/vendor/github.com/alicebob/miniredis/v2/miniredis.go b/vendor/github.com/alicebob/miniredis/v2/miniredis.go index 697dec065..6996872c4 100644 --- a/vendor/github.com/alicebob/miniredis/v2/miniredis.go +++ b/vendor/github.com/alicebob/miniredis/v2/miniredis.go @@ -13,7 +13,6 @@ // // For direct use you can select a Redis database with either `s.Select(12); // s.Get("foo")` or `s.DB(12).Get("foo")`. -// package miniredis import ( @@ -26,6 +25,7 @@ import ( "sync" "time" + "github.com/alicebob/miniredis/v2/proto" "github.com/alicebob/miniredis/v2/server" ) @@ -48,6 +48,7 @@ type RedisDB struct { sortedsetKeys map[string]sortedSet // ZADD &c. keys streamKeys map[string]*streamKey // XADD &c. keys ttl map[string]time.Duration // effective TTL values + lru map[string]time.Time // last recently used ( read or written to ) keyVersion map[string]uint // used to watch values } @@ -77,6 +78,7 @@ type dbKey struct { } // connCtx has all state for a single connection. +// (this struct was named before context.Context existed) type connCtx struct { selectedDB int // selected DB authenticated bool // auth enabled and a valid AUTH seen @@ -85,6 +87,7 @@ type connCtx struct { watch map[dbKey]uint // WATCHed keys subscriber *Subscriber // client is in PUBSUB mode if not nil nested bool // this is called via Lua + nestedSHA string // set to the SHA of the nesting function } // NewMiniRedis makes a new, non-started, Miniredis object. @@ -104,6 +107,7 @@ func newRedisDB(id int, m *Miniredis) RedisDB { id: id, master: m, keys: map[string]string{}, + lru: map[string]time.Time{}, stringKeys: map[string]string{}, hashKeys: map[string]hashKey{}, listKeys: map[string]listKey{}, @@ -132,6 +136,7 @@ func RunTLS(cfg *tls.Config) (*Miniredis, error) { type Tester interface { Fatalf(string, ...interface{}) Cleanup(func()) + Logf(format string, args ...interface{}) } // RunT start a new miniredis, pass it a testing.T. It also registers the cleanup after your test is done. @@ -145,6 +150,22 @@ func RunT(t Tester) *Miniredis { return m } +func runWithClient(t Tester) (*Miniredis, *proto.Client) { + m := RunT(t) + + c, err := proto.Dial(m.Addr()) + if err != nil { + t.Fatalf("could not connect to miniredis: %s", err) + } + t.Cleanup(func() { + if err = c.Close(); err != nil { + t.Logf("error closing connection to miniredis: %s", err) + } + }) + + return m, c +} + // Start starts a server. It listens on a random port on localhost. See also // Addr(). func (m *Miniredis) Start() error { @@ -174,6 +195,15 @@ func (m *Miniredis) StartAddr(addr string) error { return m.start(s) } +// StartAddrTLS runs miniredis with a given addr, TLS version. +func (m *Miniredis) StartAddrTLS(addr string, cfg *tls.Config) error { + s, err := server.NewServerTLS(addr, cfg) + if err != nil { + return err + } + return m.start(s) +} + func (m *Miniredis) start(s *server.Server) error { m.Lock() defer m.Unlock() @@ -194,8 +224,9 @@ func (m *Miniredis) start(s *server.Server) error { commandsScripting(m) commandsGeo(m) commandsCluster(m) - commandsCommand(m) commandsHll(m) + commandsClient(m) + commandsObject(m) return nil } @@ -347,9 +378,9 @@ func (m *Miniredis) Server() *server.Server { // Dump limits the maximum length of each key:value to "DumpMaxLineLen" characters. // To increase that, call something like: // -// miniredis.DumpMaxLineLen = 1024 -// mr, _ = miniredis.Run() -// mr.Dump() +// miniredis.DumpMaxLineLen = 1024 +// mr, _ = miniredis.Run() +// mr.Dump() func (m *Miniredis) Dump() string { m.Lock() defer m.Unlock() @@ -373,25 +404,25 @@ func (m *Miniredis) Dump() string { r += fmt.Sprintf("- %s\n", k) t := db.t(k) switch t { - case "string": + case keyTypeString: r += fmt.Sprintf("%s%s\n", indent, v(db.stringKeys[k])) - case "hash": + case keyTypeHash: for _, hk := range db.hashFields(k) { r += fmt.Sprintf("%s%s: %s\n", indent, hk, v(db.hashGet(k, hk))) } - case "list": + case keyTypeList: for _, lk := range db.listKeys[k] { r += fmt.Sprintf("%s%s\n", indent, v(lk)) } - case "set": + case keyTypeSet: for _, mk := range db.setMembers(k) { r += fmt.Sprintf("%s%s\n", indent, v(mk)) } - case "zset": + case keyTypeSortedSet: for _, el := range db.ssetElements(k) { r += fmt.Sprintf("%s%f: %s\n", indent, el.score, v(el.member)) } - case "stream": + case keyTypeStream: for _, entry := range db.streamKeys[k].entries { r += fmt.Sprintf("%s%s\n", indent, entry.ID) ev := entry.Values @@ -399,7 +430,7 @@ func (m *Miniredis) Dump() string { r += fmt.Sprintf("%s%s%s: %s\n", indent, indent, v(ev[2*i]), v(ev[2*i+1])) } } - case "hll": + case keyTypeHll: for _, entry := range db.hllKeys { r += fmt.Sprintf("%s%s\n", indent, v(string(entry.Bytes()))) } @@ -419,8 +450,10 @@ func (m *Miniredis) SetTime(t time.Time) { } // make every command return this message. For example: -// LOADING Redis is loading the dataset in memory -// MASTERDOWN Link with MASTER is down and replica-serve-stale-data is set to 'no'. +// +// LOADING Redis is loading the dataset in memory +// MASTERDOWN Link with MASTER is down and replica-serve-stale-data is set to 'no'. +// // Clear it with an empty string. Don't add newlines. func (m *Miniredis) SetError(msg string) { cb := server.Hook(nil) @@ -433,6 +466,18 @@ func (m *Miniredis) SetError(msg string) { m.srv.SetPreHook(cb) } +// isValidCMD returns true if command is valid and can be executed. +func (m *Miniredis) isValidCMD(c *server.Peer, cmd string) bool { + if !m.handleAuth(c) { + return false + } + if m.checkPubsub(c, cmd) { + return false + } + + return true +} + // handleAuth returns false if connection has no access. It sends the reply. func (m *Miniredis) handleAuth(c *server.Peer) bool { if getCtx(c).nested { @@ -658,25 +703,25 @@ func (m *Miniredis) copy( } switch srcDB.t(src) { - case "string": + case keyTypeString: destDB.stringKeys[dst] = srcDB.stringKeys[src] - case "hash": + case keyTypeHash: destDB.hashKeys[dst] = copyHashKey(srcDB.hashKeys[src]) - case "list": - destDB.listKeys[dst] = srcDB.listKeys[src] - case "set": + case keyTypeList: + destDB.listKeys[dst] = copyListKey(srcDB.listKeys[src]) + case keyTypeSet: destDB.setKeys[dst] = copySetKey(srcDB.setKeys[src]) - case "zset": + case keyTypeSortedSet: destDB.sortedsetKeys[dst] = copySortedSet(srcDB.sortedsetKeys[src]) - case "stream": + case keyTypeStream: destDB.streamKeys[dst] = srcDB.streamKeys[src].copy() - case "hll": + case keyTypeHll: destDB.hllKeys[dst] = srcDB.hllKeys[src].copy() default: panic("missing case") } destDB.keys[dst] = srcDB.keys[src] - destDB.keyVersion[dst]++ + destDB.incr(dst) if v, ok := srcDB.ttl[src]; ok { destDB.ttl[dst] = v } @@ -691,6 +736,12 @@ func copyHashKey(orig hashKey) hashKey { return cpy } +func copyListKey(orig listKey) listKey { + cpy := make(listKey, len(orig)) + copy(cpy, orig) + return cpy +} + func copySetKey(orig setKey) setKey { cpy := setKey{} for k, v := range orig { diff --git a/vendor/github.com/alicebob/miniredis/v2/opts.go b/vendor/github.com/alicebob/miniredis/v2/opts.go index 016d26820..5b29c78c2 100644 --- a/vendor/github.com/alicebob/miniredis/v2/opts.go +++ b/vendor/github.com/alicebob/miniredis/v2/opts.go @@ -1,7 +1,10 @@ package miniredis import ( + "errors" + "math" "strconv" + "time" "github.com/alicebob/miniredis/v2/server" ) @@ -10,12 +13,48 @@ import ( // Writes "invalid integer" error to c if it's not a valid integer. Returns // whether or not things were okay. func optInt(c *server.Peer, src string, dest *int) bool { + return optIntErr(c, src, dest, msgInvalidInt) +} + +func optIntErr(c *server.Peer, src string, dest *int, errMsg string) bool { n, err := strconv.Atoi(src) if err != nil { setDirty(c) - c.WriteError(msgInvalidInt) + c.WriteError(errMsg) return false } *dest = n return true } + +// optIntSimple sets dest or returns an error +func optIntSimple(src string, dest *int) error { + n, err := strconv.Atoi(src) + if err != nil { + return errors.New(msgInvalidInt) + } + *dest = n + return nil +} + +func optDuration(c *server.Peer, src string, dest *time.Duration) bool { + n, err := strconv.ParseFloat(src, 64) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidTimeout) + return false + } + if n < 0 { + setDirty(c) + c.WriteError(msgTimeoutNegative) + return false + } + if math.IsInf(n, 0) { + setDirty(c) + c.WriteError(msgTimeoutIsOutOfRange) + return false + } + + *dest = time.Duration(n*1_000_000) * time.Microsecond + return true +} diff --git a/vendor/github.com/alicebob/miniredis/v2/proto/Makefile b/vendor/github.com/alicebob/miniredis/v2/proto/Makefile new file mode 100644 index 000000000..b9ef39496 --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/v2/proto/Makefile @@ -0,0 +1,2 @@ +test: + go test diff --git a/vendor/github.com/alicebob/miniredis/v2/proto/client.go b/vendor/github.com/alicebob/miniredis/v2/proto/client.go new file mode 100644 index 000000000..92f57baf1 --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/v2/proto/client.go @@ -0,0 +1,60 @@ +package proto + +import ( + "bufio" + "crypto/tls" + "net" +) + +type Client struct { + c net.Conn + r *bufio.Reader +} + +func Dial(addr string) (*Client, error) { + c, err := net.Dial("tcp", addr) + if err != nil { + return nil, err + } + + return &Client{ + c: c, + r: bufio.NewReader(c), + }, nil +} + +func DialTLS(addr string, cfg *tls.Config) (*Client, error) { + c, err := tls.Dial("tcp", addr, cfg) + if err != nil { + return nil, err + } + + return &Client{ + c: c, + r: bufio.NewReader(c), + }, nil +} + +func (c *Client) Close() error { + return c.c.Close() +} + +func (c *Client) Do(cmd ...string) (string, error) { + if err := Write(c.c, cmd); err != nil { + return "", err + } + return Read(c.r) +} + +func (c *Client) Read() (string, error) { + return Read(c.r) +} + +// Do() + ReadStrings() +func (c *Client) DoStrings(cmd ...string) ([]string, error) { + res, err := c.Do(cmd...) + if err != nil { + return nil, err + } + return ReadStrings(res) +} diff --git a/vendor/github.com/alicebob/miniredis/v2/proto/proto.go b/vendor/github.com/alicebob/miniredis/v2/proto/proto.go new file mode 100644 index 000000000..e378faf18 --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/v2/proto/proto.go @@ -0,0 +1,288 @@ +package proto + +import ( + "bufio" + "errors" + "fmt" + "io" + "strconv" + "strings" +) + +var ( + ErrProtocol = errors.New("unsupported protocol") + ErrUnexpected = errors.New("not what you asked for") +) + +func readLine(r *bufio.Reader) (string, error) { + line, err := r.ReadString('\n') + if err != nil { + return "", err + } + if len(line) < 3 { + return "", ErrProtocol + } + return line, nil +} + +// Read an array, with all elements are the raw redis commands +// Also reads sets and maps. +func ReadArray(b string) ([]string, error) { + r := bufio.NewReader(strings.NewReader(b)) + line, err := readLine(r) + if err != nil { + return nil, err + } + + elems := 0 + switch line[0] { + default: + return nil, ErrUnexpected + case '*', '>', '~': + // *: array + // >: push data + // ~: set + length, err := strconv.Atoi(line[1 : len(line)-2]) + if err != nil { + return nil, err + } + elems = length + case '%': + // we also read maps. + length, err := strconv.Atoi(line[1 : len(line)-2]) + if err != nil { + return nil, err + } + elems = length * 2 + } + + var res []string + for i := 0; i < elems; i++ { + next, err := Read(r) + if err != nil { + return nil, err + } + res = append(res, next) + } + return res, nil +} + +func ReadString(b string) (string, error) { + r := bufio.NewReader(strings.NewReader(b)) + line, err := readLine(r) + if err != nil { + return "", err + } + + switch line[0] { + default: + return "", ErrUnexpected + case '$': + // bulk strings are: `$5\r\nhello\r\n` + length, err := strconv.Atoi(line[1 : len(line)-2]) + if err != nil { + return "", err + } + if length < 0 { + // -1 is a nil response + return line, nil + } + var ( + buf = make([]byte, length+2) + pos = 0 + ) + for pos < length+2 { + n, err := r.Read(buf[pos:]) + if err != nil { + return "", err + } + pos += n + } + return string(buf[:len(buf)-2]), nil + } +} + +func readInline(b string) (string, error) { + if len(b) < 3 { + return "", ErrUnexpected + } + return b[1 : len(b)-2], nil +} + +func ReadError(b string) (string, error) { + if len(b) < 1 { + return "", ErrUnexpected + } + + switch b[0] { + default: + return "", ErrUnexpected + case '-': + return readInline(b) + } +} + +func ReadStrings(b string) ([]string, error) { + elems, err := ReadArray(b) + if err != nil { + return nil, err + } + var res []string + for _, e := range elems { + s, err := ReadString(e) + if err != nil { + return nil, err + } + res = append(res, s) + } + return res, nil +} + +// Read a single command, returning it raw. Used to read replies from redis. +// Understands RESP3 proto. +func Read(r *bufio.Reader) (string, error) { + line, err := readLine(r) + if err != nil { + return "", err + } + + switch line[0] { + default: + return "", ErrProtocol + case '+', '-', ':', ',', '_': + // +: inline string + // -: errors + // :: integer + // ,: float + // _: null + // Simple line based replies. + return line, nil + case '$': + // bulk strings are: `$5\r\nhello\r\n` + length, err := strconv.Atoi(line[1 : len(line)-2]) + if err != nil { + return "", err + } + if length < 0 { + // -1 is a nil response + return line, nil + } + var ( + buf = make([]byte, length+2) + pos = 0 + ) + for pos < length+2 { + n, err := r.Read(buf[pos:]) + if err != nil { + return "", err + } + pos += n + } + return line + string(buf), nil + case '*', '>', '~': + // arrays are: `*6\r\n...` + // pushdata is: `>6\r\n...` + // sets are: `~6\r\n...` + length, err := strconv.Atoi(line[1 : len(line)-2]) + if err != nil { + return "", err + } + for i := 0; i < length; i++ { + next, err := Read(r) + if err != nil { + return "", err + } + line += next + } + return line, nil + case '%': + // maps are: `%3\r\n...` + length, err := strconv.Atoi(line[1 : len(line)-2]) + if err != nil { + return "", err + } + for i := 0; i < length*2; i++ { + next, err := Read(r) + if err != nil { + return "", err + } + line += next + } + return line, nil + } +} + +// Write a command in RESP3 proto. Used to write commands to redis. +// Currently only supports string arrays. +func Write(w io.Writer, cmd []string) error { + if _, err := fmt.Fprintf(w, "*%d\r\n", len(cmd)); err != nil { + return err + } + for _, c := range cmd { + if _, err := fmt.Fprintf(w, "$%d\r\n%s\r\n", len(c), c); err != nil { + return err + } + } + return nil +} + +// Parse into interfaces. `b` must contain exactly a single command (which can be nested). +func Parse(b string) (interface{}, error) { + if len(b) < 1 { + return nil, ErrUnexpected + } + + switch b[0] { + default: + return "", ErrProtocol + case '+': + return readInline(b) + case '-': + e, err := readInline(b) + if err != nil { + return nil, err + } + return errors.New(e), nil + case ':': + e, err := readInline(b) + if err != nil { + return nil, err + } + return strconv.Atoi(e) + case '$': + return ReadString(b) + case '*': + elems, err := ReadArray(b) + if err != nil { + return nil, err + } + var res []interface{} + for _, elem := range elems { + e, err := Parse(elem) + if err != nil { + return nil, err + } + res = append(res, e) + } + return res, nil + case '%': + elems, err := ReadArray(b) + if err != nil { + return nil, err + } + var res = map[interface{}]interface{}{} + for len(elems) > 1 { + key, err := Parse(elems[0]) + if err != nil { + return nil, err + } + value, err := Parse(elems[1]) + if err != nil { + return nil, err + } + res[key] = value + elems = elems[2:] + } + return res, nil + } +} diff --git a/vendor/github.com/alicebob/miniredis/v2/proto/types.go b/vendor/github.com/alicebob/miniredis/v2/proto/types.go new file mode 100644 index 000000000..0b3b7c9af --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/v2/proto/types.go @@ -0,0 +1,102 @@ +package proto + +import ( + "fmt" + "strings" +) + +// Byte-safe string +func String(s string) string { + return fmt.Sprintf("$%d\r\n%s\r\n", len(s), s) +} + +// Inline string +func Inline(s string) string { + return inline('+', s) +} + +// Error +func Error(s string) string { + return inline('-', s) +} + +func inline(r rune, s string) string { + return fmt.Sprintf("%s%s\r\n", string(r), s) +} + +// Int +func Int(n int) string { + return fmt.Sprintf(":%d\r\n", n) +} + +// Float +func Float(n float64) string { + return fmt.Sprintf(",%g\r\n", n) +} + +const ( + Nil = "$-1\r\n" + NilResp3 = "_\r\n" + NilList = "*-1\r\n" +) + +// Array assembles the args in a list. Args should be raw redis commands. +// Example: Array(String("foo"), String("bar")) +func Array(args ...string) string { + return fmt.Sprintf("*%d\r\n", len(args)) + strings.Join(args, "") +} + +// Push assembles the args for push-data. Args should be raw redis commands. +// Example: Push(String("foo"), String("bar")) +func Push(args ...string) string { + return fmt.Sprintf(">%d\r\n", len(args)) + strings.Join(args, "") +} + +// Strings is a helper to build 1 dimensional string arrays. +func Strings(args ...string) string { + var strings []string + for _, a := range args { + strings = append(strings, String(a)) + } + return Array(strings...) +} + +// Ints is a helper to build 1 dimensional int arrays. +func Ints(args ...int) string { + var ints []string + for _, a := range args { + ints = append(ints, Int(a)) + } + return Array(ints...) +} + +// Map assembles the args in a map. Args should be raw redis commands. +// Must be an even number of arguments. +// Example: Map(String("foo"), String("bar")) +func Map(args ...string) string { + return fmt.Sprintf("%%%d\r\n", len(args)/2) + strings.Join(args, "") +} + +// StringMap is is a wrapper to get a map of (bulk)strings. +func StringMap(args ...string) string { + var strings []string + for _, a := range args { + strings = append(strings, String(a)) + } + return Map(strings...) +} + +// Set assembles the args in a map. Args should be raw redis commands. +// Example: Set(String("foo"), String("bar")) +func Set(args ...string) string { + return fmt.Sprintf("~%d\r\n", len(args)) + strings.Join(args, "") +} + +// StringSet is is a wrapper to get a set of (bulk)strings. +func StringSet(args ...string) string { + var strings []string + for _, a := range args { + strings = append(strings, String(a)) + } + return Set(strings...) +} diff --git a/vendor/github.com/alicebob/miniredis/v2/redis.go b/vendor/github.com/alicebob/miniredis/v2/redis.go index d4a0cd8d4..2bf3bae24 100644 --- a/vendor/github.com/alicebob/miniredis/v2/redis.go +++ b/vendor/github.com/alicebob/miniredis/v2/redis.go @@ -12,27 +12,43 @@ import ( "github.com/alicebob/miniredis/v2/server" ) +const ( + keyTypeString = "string" + keyTypeHash = "hash" + keyTypeList = "list" + keyTypeSet = "set" + keyTypeHll = "hll" + keyTypeSortedSet = "zset" + keyTypeStream = "stream" +) + const ( msgWrongType = "WRONGTYPE Operation against a key holding the wrong kind of value" msgNotValidHllValue = "WRONGTYPE Key is not a valid HyperLogLog string value." msgInvalidInt = "ERR value is not an integer or out of range" + msgIntOverflow = "ERR increment or decrement would overflow" msgInvalidFloat = "ERR value is not a valid float" msgInvalidMinMax = "ERR min or max is not a float" msgInvalidRangeItem = "ERR min or max not valid string range item" msgInvalidTimeout = "ERR timeout is not a float or out of range" + msgInvalidRange = "ERR value is out of range, must be positive" msgSyntaxError = "ERR syntax error" msgKeyNotFound = "ERR no such key" msgOutOfRange = "ERR index out of range" msgInvalidCursor = "ERR invalid cursor" msgXXandNX = "ERR XX and NX options at the same time are not compatible" - msgNegTimeout = "ERR timeout is negative" + msgTimeoutNegative = "ERR timeout is negative" + msgTimeoutIsOutOfRange = "ERR timeout is out of range" msgInvalidSETime = "ERR invalid expire time in set" msgInvalidSETEXTime = "ERR invalid expire time in setex" msgInvalidPSETEXTime = "ERR invalid expire time in psetex" msgInvalidKeysNumber = "ERR Number of keys can't be greater than number of args" msgNegativeKeysNumber = "ERR Number of keys can't be negative" - msgFScriptUsage = "ERR Unknown subcommand or wrong number of arguments for '%s'. Try SCRIPT HELP." - msgFPubsubUsage = "ERR Unknown subcommand or wrong number of arguments for '%s'. Try PUBSUB HELP." + msgFScriptUsage = "ERR unknown subcommand or wrong number of arguments for '%s'. Try SCRIPT HELP." + msgFScriptUsageSimple = "ERR unknown subcommand '%s'. Try SCRIPT HELP." + msgFPubsubUsage = "ERR unknown subcommand or wrong number of arguments for '%s'. Try PUBSUB HELP." + msgFPubsubUsageSimple = "ERR unknown subcommand '%s'. Try PUBSUB HELP." + msgFObjectUsage = "ERR unknown subcommand '%s'. Try OBJECT HELP." msgScriptFlush = "ERR SCRIPT FLUSH only support SYNC|ASYNC option" msgSingleElementPair = "ERR INCR option supports a single increment-element pair" msgGTLTandNX = "ERR GT, LT, and/or NX options at the same time are not compatible" @@ -40,15 +56,19 @@ const ( msgStreamIDTooSmall = "ERR The ID specified in XADD is equal or smaller than the target stream top item" msgStreamIDZero = "ERR The ID specified in XADD must be greater than 0-0" msgNoScriptFound = "NOSCRIPT No matching script. Please use EVAL." - msgUnsupportedUnit = "ERR unsupported unit provided. please use m, km, ft, mi" - msgNotFromScripts = "This Redis command is not allowed from scripts" - msgXreadUnbalanced = "ERR Unbalanced XREAD list of streams: for each stream key an ID or '$' must be specified." + msgUnsupportedUnit = "ERR unsupported unit provided. please use M, KM, FT, MI" + msgXreadUnbalanced = "ERR Unbalanced 'xread' list of streams: for each stream key an ID or '$' must be specified." msgXgroupKeyNotFound = "ERR The XGROUP subcommand requires the key to exist. Note that for CREATE you may want to use the MKSTREAM option to create an empty stream automatically." msgXtrimInvalidStrategy = "ERR unsupported XTRIM strategy. Please use MAXLEN, MINID" msgXtrimInvalidMaxLen = "ERR value is not an integer or out of range" msgXtrimInvalidLimit = "ERR syntax error, LIMIT cannot be used without the special ~ option" msgDBIndexOutOfRange = "ERR DB index is out of range" msgLimitCombination = "ERR syntax error, LIMIT is only supported in combination with either BYSCORE or BYLEX" + msgRankIsZero = "ERR RANK can't be zero: use 1 to start from the first match, 2 from the second ... or use negative to start from the end of the list" + msgCountIsNegative = "ERR COUNT can't be negative" + msgMaxLengthIsNegative = "ERR MAXLEN can't be negative" + msgLimitIsNegative = "ERR LIMIT can't be negative" + msgMemorySubcommand = "ERR unknown subcommand '%s'. Try MEMORY HELP." ) func errWrongNumber(cmd string) string { @@ -67,6 +87,10 @@ func errXreadgroup(key, group string) error { return fmt.Errorf("NOGROUP No such key '%s' or consumer group '%s' in XREADGROUP with GROUP option", key, group) } +func msgNotFromScripts(sha string) string { + return fmt.Sprintf("This Redis command is not allowed from script script: %s, &c", sha) +} + // withTx wraps the non-argument-checking part of command handling code in // transaction logic. func withTx( @@ -131,17 +155,25 @@ func blocking( m.signal.Broadcast() // main loop might miss this signal }() - m.Lock() - defer m.Unlock() + if !ctx.nested { + // this is a call via Lua's .call(). It's already locked. + m.Lock() + defer m.Unlock() + } for { - done := cb(c, ctx) - if done { + if c.Closed() { return } if m.Ctx.Err() != nil { return } + + done := cb(c, ctx) + if done { + return + } + if timedOut { onTimeout(c) return diff --git a/vendor/github.com/alicebob/miniredis/v2/server/server.go b/vendor/github.com/alicebob/miniredis/v2/server/server.go index 60e391f22..b5f1b61ce 100644 --- a/vendor/github.com/alicebob/miniredis/v2/server/server.go +++ b/vendor/github.com/alicebob/miniredis/v2/server/server.go @@ -4,11 +4,12 @@ import ( "bufio" "crypto/tls" "fmt" - "math" "net" "strings" "sync" "unicode" + + "github.com/alicebob/miniredis/v2/fpconv" ) func errUnknownCommand(cmd string, args []string) string { @@ -158,24 +159,34 @@ func (s *Server) servePeer(c net.Conn) { peer := &Peer{ w: bufio.NewWriter(c), } + defer func() { for _, f := range peer.onDisconnect { f() } }() - for { - args, err := readArray(r) - if err != nil { - return + readCh := make(chan []string) + + go func() { + defer close(readCh) + + for { + args, err := readArray(r) + if err != nil { + peer.Close() + return + } + + readCh <- args } + }() + + for args := range readCh { s.Dispatch(peer, args) peer.Flush() - s.mu.Lock() - closed := peer.closed - s.mu.Unlock() - if closed { + if peer.Closed() { c.Close() } } @@ -205,6 +216,10 @@ func (s *Server) Dispatch(c *Peer, args []string) { s.infoCmds++ s.mu.Unlock() cb(c, cmdUp, args) + if c.SwitchResp3 != nil { + c.Resp3 = *c.SwitchResp3 + c.SwitchResp3 = nil + } } // TotalCommands is total (known) commands since this the server started @@ -234,9 +249,11 @@ type Peer struct { w *bufio.Writer closed bool Resp3 bool + SwitchResp3 *bool // we'll switch to this version _after_ the command Ctx interface{} // anything goes, server won't touch this onDisconnect []func() // list of callbacks mu sync.Mutex // for Block() + ClientName string // client name set by CLIENT SETNAME } func NewPeer(w *bufio.Writer) *Peer { @@ -259,6 +276,13 @@ func (c *Peer) Close() { c.closed = true } +// Return true if the peer connection closed. +func (c *Peer) Closed() bool { + c.mu.Lock() + defer c.mu.Unlock() + return c.closed +} + // Register a function to execute on disconnect. There can be multiple // functions registered. func (c *Peer) OnDisconnect(f func()) { @@ -459,29 +483,8 @@ func (w *Writer) Flush() { w.w.Flush() } -// formatFloat formats a float the way redis does (sort-of) +// formatFloat formats a float the way redis does. +// Redis uses a method called "grisu2", which we ported from C. func formatFloat(v float64) string { - if math.IsInf(v, 1) { - return "inf" - } - if math.IsInf(v, -1) { - return "-inf" - } - return stripZeros(fmt.Sprintf("%.12f", v)) -} - -func stripZeros(sv string) string { - for strings.Contains(sv, ".") { - if sv[len(sv)-1] != '0' { - break - } - // Remove trailing 0s. - sv = sv[:len(sv)-1] - // Ends with a '.'. - if sv[len(sv)-1] == '.' { - sv = sv[:len(sv)-1] - break - } - } - return sv + return fpconv.Dtoa(v) } diff --git a/vendor/github.com/alicebob/miniredis/v2/size/readme.md b/vendor/github.com/alicebob/miniredis/v2/size/readme.md new file mode 100644 index 000000000..89220e459 --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/v2/size/readme.md @@ -0,0 +1,2 @@ + +Credits to DmitriyVTitov on his package https://github.com/DmitriyVTitov/size diff --git a/vendor/github.com/alicebob/miniredis/v2/size/size.go b/vendor/github.com/alicebob/miniredis/v2/size/size.go new file mode 100644 index 000000000..43fee6e21 --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/v2/size/size.go @@ -0,0 +1,138 @@ +package size + +import ( + "reflect" + "unsafe" +) + +// Of returns the size of 'v' in bytes. +// If there is an error during calculation, Of returns -1. +func Of(v interface{}) int { + // Cache with every visited pointer so we don't count two pointers + // to the same memory twice. + cache := make(map[uintptr]bool) + return sizeOf(reflect.Indirect(reflect.ValueOf(v)), cache) +} + +// sizeOf returns the number of bytes the actual data represented by v occupies in memory. +// If there is an error, sizeOf returns -1. +func sizeOf(v reflect.Value, cache map[uintptr]bool) int { + switch v.Kind() { + + case reflect.Array: + sum := 0 + for i := 0; i < v.Len(); i++ { + s := sizeOf(v.Index(i), cache) + if s < 0 { + return -1 + } + sum += s + } + + return sum + (v.Cap()-v.Len())*int(v.Type().Elem().Size()) + + case reflect.Slice: + // return 0 if this node has been visited already + if cache[v.Pointer()] { + return 0 + } + cache[v.Pointer()] = true + + sum := 0 + for i := 0; i < v.Len(); i++ { + s := sizeOf(v.Index(i), cache) + if s < 0 { + return -1 + } + sum += s + } + + sum += (v.Cap() - v.Len()) * int(v.Type().Elem().Size()) + + return sum + int(v.Type().Size()) + + case reflect.Struct: + sum := 0 + for i, n := 0, v.NumField(); i < n; i++ { + s := sizeOf(v.Field(i), cache) + if s < 0 { + return -1 + } + sum += s + } + + // Look for struct padding. + padding := int(v.Type().Size()) + for i, n := 0, v.NumField(); i < n; i++ { + padding -= int(v.Field(i).Type().Size()) + } + + return sum + padding + + case reflect.String: + s := v.String() + hdr := (*reflect.StringHeader)(unsafe.Pointer(&s)) + if cache[hdr.Data] { + return int(v.Type().Size()) + } + cache[hdr.Data] = true + return len(s) + int(v.Type().Size()) + + case reflect.Ptr: + // return Ptr size if this node has been visited already (infinite recursion) + if cache[v.Pointer()] { + return int(v.Type().Size()) + } + cache[v.Pointer()] = true + if v.IsNil() { + return int(reflect.New(v.Type()).Type().Size()) + } + s := sizeOf(reflect.Indirect(v), cache) + if s < 0 { + return -1 + } + return s + int(v.Type().Size()) + + case reflect.Bool, + reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, + reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Int, reflect.Uint, + reflect.Chan, + reflect.Uintptr, + reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128, + reflect.Func: + return int(v.Type().Size()) + + case reflect.Map: + // return 0 if this node has been visited already (infinite recursion) + if cache[v.Pointer()] { + return 0 + } + cache[v.Pointer()] = true + sum := 0 + keys := v.MapKeys() + for i := range keys { + val := v.MapIndex(keys[i]) + // calculate size of key and value separately + sv := sizeOf(val, cache) + if sv < 0 { + return -1 + } + sum += sv + sk := sizeOf(keys[i], cache) + if sk < 0 { + return -1 + } + sum += sk + } + // Include overhead due to unused map buckets. 10.79 comes + // from https://golang.org/src/runtime/map.go. + return sum + int(v.Type().Size()) + int(float64(len(keys))*10.79) + + case reflect.Interface: + return sizeOf(v.Elem(), cache) + int(v.Type().Size()) + + } + + return -1 +} diff --git a/vendor/github.com/alicebob/miniredis/v2/stream.go b/vendor/github.com/alicebob/miniredis/v2/stream.go index 574f9016d..f2dd466d5 100644 --- a/vendor/github.com/alicebob/miniredis/v2/stream.go +++ b/vendor/github.com/alicebob/miniredis/v2/stream.go @@ -9,6 +9,7 @@ import ( "sort" "strconv" "strings" + "sync" "time" ) @@ -17,6 +18,7 @@ type streamKey struct { entries []StreamEntry groups map[string]*streamGroup lastAllocatedID string + mu sync.Mutex } // a StreamEntry is an entry in a stream. The ID is always of the form @@ -31,11 +33,14 @@ type streamGroup struct { stream *streamKey lastID string pending []pendingEntry - consumers map[string]consumer + consumers map[string]*consumer } type consumer struct { - // TODO: "last seen" timestamp + numPendingEntries int + // these timestamps aren't tracked perfectly + lastSeen time.Time // "idle" XINFO key + lastSuccess time.Time // "inactive" XINFO key } type pendingEntry struct { @@ -51,6 +56,7 @@ func newStreamKey() *streamKey { } } +// generateID doesn't lock the mutex func (s *streamKey) generateID(now time.Time) string { ts := uint64(now.UnixNano()) / 1_000_000 @@ -60,7 +66,7 @@ func (s *streamKey) generateID(now time.Time) string { next = fmt.Sprintf("%d-%d", last[0], last[1]+1) } - lastID := s.lastID() + lastID := s.lastIDUnlocked() if streamCmp(lastID, next) >= 0 { last, _ := parseStreamID(lastID) next = fmt.Sprintf("%d-%d", last[0], last[1]+1) @@ -70,7 +76,16 @@ func (s *streamKey) generateID(now time.Time) string { return next } +// lastID locks the mutex func (s *streamKey) lastID() string { + s.mu.Lock() + defer s.mu.Unlock() + + return s.lastIDUnlocked() +} + +// lastID doesn't lock the mutex +func (s *streamKey) lastIDUnlocked() string { if len(s.entries) == 0 { return "0-0" } @@ -79,6 +94,9 @@ func (s *streamKey) lastID() string { } func (s *streamKey) copy() *streamKey { + s.mu.Lock() + defer s.mu.Unlock() + cpy := &streamKey{ entries: s.entries, } @@ -193,17 +211,20 @@ func reversedStreamEntries(o []StreamEntry) []StreamEntry { } func (s *streamKey) createGroup(group, id string) error { + s.mu.Lock() + defer s.mu.Unlock() + if _, ok := s.groups[group]; ok { return errors.New("BUSYGROUP Consumer Group name already exists") } if id == "$" { - id = s.lastID() + id = s.lastIDUnlocked() } s.groups[group] = &streamGroup{ stream: s, lastID: id, - consumers: map[string]consumer{}, + consumers: map[string]*consumer{}, } return nil } @@ -212,6 +233,9 @@ func (s *streamKey) createGroup(group, id string) error { // If id is empty or "*" the ID will be generated automatically. // `values` should have an even length. func (s *streamKey) add(entryID string, values []string, now time.Time) (string, error) { + s.mu.Lock() + defer s.mu.Unlock() + if entryID == "" || entryID == "*" { entryID = s.generateID(now) } @@ -223,7 +247,7 @@ func (s *streamKey) add(entryID string, values []string, now time.Time) (string, if entryID == "0-0" { return "", errors.New(msgStreamIDZero) } - if streamCmp(s.lastID(), entryID) != -1 { + if streamCmp(s.lastIDUnlocked(), entryID) != -1 { return "", errors.New(msgStreamIDTooSmall) } @@ -235,13 +259,36 @@ func (s *streamKey) add(entryID string, values []string, now time.Time) (string, } func (s *streamKey) trim(n int) { + s.mu.Lock() + defer s.mu.Unlock() + if len(s.entries) > n { s.entries = s.entries[len(s.entries)-n:] } } +// trimBefore deletes entries with an id less than the provided id +// and returns the number of entries deleted +func (s *streamKey) trimBefore(id string) int { + s.mu.Lock() + var delete []string + for _, entry := range s.entries { + if streamCmp(entry.ID, id) < 0 { + delete = append(delete, entry.ID) + } else { + break + } + } + s.mu.Unlock() + s.delete(delete) + return len(delete) +} + // all entries after "id" func (s *streamKey) after(id string) []StreamEntry { + s.mu.Lock() + defer s.mu.Unlock() + pos := sort.Search(len(s.entries), func(i int) bool { return streamCmp(id, s.entries[i].ID) < 0 }) @@ -251,6 +298,9 @@ func (s *streamKey) after(id string) []StreamEntry { // get a stream entry by ID // Also returns the position in the entries slice, if found. func (s *streamKey) get(id string) (int, *StreamEntry) { + s.mu.Lock() + defer s.mu.Unlock() + pos := sort.Search(len(s.entries), func(i int) bool { return streamCmp(id, s.entries[i].ID) <= 0 }) @@ -279,16 +329,39 @@ func (g *streamGroup) readGroup( } if !noack { + shouldAppend := len(g.pending) == 0 for _, msg := range msgs { - g.pending = append(g.pending, pendingEntry{ + if !shouldAppend { + shouldAppend = streamCmp(msg.ID, g.pending[len(g.pending)-1].id) == 1 + } + + var entry *pendingEntry + if shouldAppend { + g.pending = append(g.pending, pendingEntry{}) + entry = &g.pending[len(g.pending)-1] + } else { + var pos int + pos, entry = g.searchPending(msg.ID) + if entry == nil { + g.pending = append(g.pending[:pos+1], g.pending[pos:]...) + entry = &g.pending[pos] + } else { + g.consumers[entry.consumer].numPendingEntries-- + } + } + + *entry = pendingEntry{ id: msg.ID, consumer: consumerID, deliveryCount: 1, lastDelivery: now, - }) + } } } - g.consumers[consumerID] = consumer{} + if _, ok := g.consumers[consumerID]; !ok { + g.consumers[consumerID] = &consumer{} + } + g.consumers[consumerID].numPendingEntries += len(msgs) g.lastID = msgs[len(msgs)-1].ID return msgs } @@ -314,6 +387,16 @@ func (g *streamGroup) readGroup( return res } +func (g *streamGroup) searchPending(id string) (int, *pendingEntry) { + pos := sort.Search(len(g.pending), func(i int) bool { + return streamCmp(id, g.pending[i].id) <= 0 + }) + if pos >= len(g.pending) || g.pending[pos].id != id { + return pos, nil + } + return pos, &g.pending[pos] +} + func (g *streamGroup) ack(ids []string) (int, error) { count := 0 for _, id := range ids { @@ -321,14 +404,19 @@ func (g *streamGroup) ack(ids []string) (int, error) { return 0, errors.New(msgInvalidStreamID) } - pos := sort.Search(len(g.pending), func(i int) bool { - return streamCmp(id, g.pending[i].id) <= 0 - }) - if len(g.pending) <= pos || g.pending[pos].id != id { + pos, entry := g.searchPending(id) + if entry == nil { continue } + consumer := g.consumers[entry.consumer] + consumer.numPendingEntries-- + g.pending = append(g.pending[:pos], g.pending[pos+1:]...) + // don't count deleted entries + if _, e := g.stream.get(id); e == nil { + continue + } count++ } return count, nil @@ -352,6 +440,13 @@ func (s *streamKey) delete(ids []string) (int, error) { return count, nil } +func (g *streamGroup) pendingAfterOrEqual(id string) []pendingEntry { + pos := sort.Search(len(g.pending), func(i int) bool { + return streamCmp(id, g.pending[i].id) <= 0 + }) + return g.pending[pos:] +} + func (g *streamGroup) pendingAfter(id string) []pendingEntry { pos := sort.Search(len(g.pending), func(i int) bool { return streamCmp(id, g.pending[i].id) < 0 @@ -361,7 +456,7 @@ func (g *streamGroup) pendingAfter(id string) []pendingEntry { func (g *streamGroup) pendingCount(consumer string) int { n := 0 - for _, p := range g.pending { + for _, p := range g.activePending() { if p.consumer == consumer { n++ } @@ -369,10 +464,25 @@ func (g *streamGroup) pendingCount(consumer string) int { return n } +// pending entries without the entries deleted from the group +func (g *streamGroup) activePending() []pendingEntry { + var pe []pendingEntry + for _, p := range g.pending { + // drop deleted ones + if _, e := g.stream.get(p.id); e == nil { + continue + } + p := p + pe = append(pe, p) + } + return pe +} + func (g *streamGroup) copy() *streamGroup { - cns := map[string]consumer{} + cns := map[string]*consumer{} for k, v := range g.consumers { - cns[k] = v + c := *v + cns[k] = &c } return &streamGroup{ // don't copy stream @@ -381,3 +491,17 @@ func (g *streamGroup) copy() *streamGroup { consumers: cns, } } + +func (g *streamGroup) setLastSeen(c string, t time.Time) { + cons, ok := g.consumers[c] + if !ok { + cons = &consumer{} + } + cons.lastSeen = t + g.consumers[c] = cons +} + +func (g *streamGroup) setLastSuccess(c string, t time.Time) { + g.setLastSeen(c, t) + g.consumers[c].lastSuccess = t +} diff --git a/vendor/github.com/yuin/gopher-lua/.gitignore b/vendor/github.com/yuin/gopher-lua/.gitignore new file mode 100644 index 000000000..485dee64b --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/.gitignore @@ -0,0 +1 @@ +.idea diff --git a/vendor/github.com/yuin/gopher-lua/.travis.yml b/vendor/github.com/yuin/gopher-lua/.travis.yml deleted file mode 100644 index 68df5e7b1..000000000 --- a/vendor/github.com/yuin/gopher-lua/.travis.yml +++ /dev/null @@ -1,18 +0,0 @@ -language: go - -go: - - "1.9.x" - - "1.10.x" - - "1.11.x" -env: - global: - GO111MODULE=off - -before_install: - - go get github.com/axw/gocov/gocov - - go get github.com/mattn/goveralls - - if ! go get code.google.com/p/go.tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi -install: - - go get -u -v $(go list -f '{{join .Imports "\n"}}{{"\n"}}{{join .TestImports "\n"}}' ./... | sort | uniq | grep '\.' | grep -v gopher-lua) -script: - - $HOME/gopath/bin/goveralls -service=travis-ci diff --git a/vendor/github.com/yuin/gopher-lua/README.rst b/vendor/github.com/yuin/gopher-lua/README.rst index b479e4635..cc9363110 100644 --- a/vendor/github.com/yuin/gopher-lua/README.rst +++ b/vendor/github.com/yuin/gopher-lua/README.rst @@ -3,14 +3,14 @@ GopherLua: VM and compiler for Lua in Go. =============================================================================== -.. image:: https://godoc.org/github.com/yuin/gopher-lua?status.svg - :target: http://godoc.org/github.com/yuin/gopher-lua +.. image:: https://pkg.go.dev/badge/github.com/yuin/gopher-lua.svg + :target: https://pkg.go.dev/github.com/yuin/gopher-lua -.. image:: https://travis-ci.org/yuin/gopher-lua.svg - :target: https://travis-ci.org/yuin/gopher-lua +.. image:: https://github.com/yuin/gopher-lua/workflows/test/badge.svg?branch=master&event=push + :target: https://github.com/yuin/gopher-lua/actions?query=workflow:test -.. image:: https://coveralls.io/repos/yuin/gopher-lua/badge.svg - :target: https://coveralls.io/r/yuin/gopher-lua +.. image:: https://coveralls.io/repos/github/yuin/gopher-lua/badge.svg?branch=master + :target: https://coveralls.io/github/yuin/gopher-lua .. image:: https://badges.gitter.im/Join%20Chat.svg :alt: Join the chat at https://gitter.im/yuin/gopher-lua @@ -19,7 +19,7 @@ GopherLua: VM and compiler for Lua in Go. | -GopherLua is a Lua5.1 VM and compiler written in Go. GopherLua has a same goal +GopherLua is a Lua5.1(+ `goto` statement in Lua5.2) VM and compiler written in Go. GopherLua has a same goal with Lua: **Be a scripting language with extensible semantics** . It provides Go APIs that allow you to easily embed a scripting language to your Go host programs. @@ -830,6 +830,8 @@ Miscellaneous notes - ``file:setvbuf`` does not support a line buffering. - Daylight saving time is not supported. - GopherLua has a function to set an environment variable : ``os.setenv(name, value)`` +- GopherLua support ``goto`` and ``::label::`` statement in Lua5.2. + - `goto` is a keyword and not a valid variable name. ---------------------------------------------------------------- Standalone interpreter @@ -870,6 +872,7 @@ Libraries for GopherLua - `gluaperiphery `_ : A periphery library for the GopherLua VM (GPIO, SPI, I2C, MMIO, and Serial peripheral I/O for Linux). - `glua-async `_ : An async/await implement for gopher-lua. - `gopherlua-debugger `_ : A debugger for gopher-lua +- `gluamahonia `_ : An encoding converter for gopher-lua ---------------------------------------------------------------- Donation ---------------------------------------------------------------- diff --git a/vendor/github.com/yuin/gopher-lua/_state.go b/vendor/github.com/yuin/gopher-lua/_state.go index 960e8810e..6e9febfb6 100644 --- a/vendor/github.com/yuin/gopher-lua/_state.go +++ b/vendor/github.com/yuin/gopher-lua/_state.go @@ -398,17 +398,18 @@ func (rg *registry) forceResize(newSize int) { copy(newSlice, rg.array[:rg.top]) // should we copy the area beyond top? there shouldn't be any valid values there so it shouldn't be necessary. rg.array = newSlice } -func (rg *registry) SetTop(top int) { - // +inline-call rg.checkSize top - oldtop := rg.top - rg.top = top - for i := oldtop; i < rg.top; i++ { + +func (rg *registry) SetTop(topi int) { // +inline-start + // +inline-call rg.checkSize topi + oldtopi := rg.top + rg.top = topi + for i := oldtopi; i < rg.top; i++ { rg.array[i] = LNil } // values beyond top don't need to be valid LValues, so setting them to nil is fine // setting them to nil rather than LNil lets us invoke the golang memclr opto - if rg.top < oldtop { - nilRange := rg.array[rg.top:oldtop] + if rg.top < oldtopi { + nilRange := rg.array[rg.top:oldtopi] for i := range nilRange { nilRange[i] = nil } @@ -416,7 +417,7 @@ func (rg *registry) SetTop(top int) { //for i := rg.top; i < oldtop; i++ { // rg.array[i] = LNil //} -} +} // +inline-end func (rg *registry) Top() int { return rg.top @@ -498,34 +499,34 @@ func (rg *registry) FillNil(regm, n int) { // +inline-start func (rg *registry) Insert(value LValue, reg int) { top := rg.Top() if reg >= top { - rg.Set(reg, value) + // +inline-call rg.Set reg value return } top-- for ; top >= reg; top-- { // FIXME consider using copy() here if Insert() is called enough - rg.Set(top+1, rg.Get(top)) + // +inline-call rg.Set top+1 rg.Get(top) } - rg.Set(reg, value) + // +inline-call rg.Set reg value } -func (rg *registry) Set(reg int, val LValue) { - newSize := reg + 1 +func (rg *registry) Set(regi int, vali LValue) { // +inline-start + newSize := regi + 1 // +inline-call rg.checkSize newSize - rg.array[reg] = val - if reg >= rg.top { - rg.top = reg + 1 + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 } -} +} // +inline-end -func (rg *registry) SetNumber(reg int, val LNumber) { - newSize := reg + 1 +func (rg *registry) SetNumber(regi int, vali LNumber) { // +inline-start + newSize := regi + 1 // +inline-call rg.checkSize newSize - rg.array[reg] = rg.alloc.LNumber2I(val) - if reg >= rg.top { - rg.top = reg + 1 + rg.array[regi] = rg.alloc.LNumber2I(vali) + if regi >= rg.top { + rg.top = regi + 1 } -} +} // +inline-end func (rg *registry) IsFull() bool { return rg.top >= cap(rg.array) @@ -769,6 +770,9 @@ func (ls *LState) isStarted() bool { func (ls *LState) kill() { ls.Dead = true + if ls.ctxCancelFn != nil { + ls.ctxCancelFn() + } } func (ls *LState) indexToReg(idx int) int { @@ -1402,6 +1406,7 @@ func (ls *LState) NewThread() (*LState, context.CancelFunc) { if ls.ctx != nil { thread.mainLoop = mainLoopWithContext thread.ctx, f = context.WithCancel(ls.ctx) + thread.ctxCancelFn = f } return thread, f } @@ -1851,6 +1856,9 @@ func (ls *LState) PCall(nargs, nret int, errfunc *LFunction) (err error) { err = rcv.(*ApiError) err.(*ApiError).StackTrace = ls.stackTrace(0) } + ls.stack.SetSp(sp) + ls.currentFrame = ls.stack.Last() + ls.reg.SetTop(base) } }() ls.Call(1, 1) diff --git a/vendor/github.com/yuin/gopher-lua/_vm.go b/vendor/github.com/yuin/gopher-lua/_vm.go index 874ed9aa4..ee2be0402 100644 --- a/vendor/github.com/yuin/gopher-lua/_vm.go +++ b/vendor/github.com/yuin/gopher-lua/_vm.go @@ -173,7 +173,8 @@ func init() { A := int(inst>>18) & 0xff //GETA RA := lbase + A B := int(inst & 0x1ff) //GETB - reg.Set(RA, reg.Get(lbase+B)) + v := reg.Get(lbase + B) + // +inline-call reg.Set RA v return 0 }, func(L *LState, inst uint32, baseframe *callFrame) int { //OP_MOVEN @@ -183,7 +184,8 @@ func init() { A := int(inst>>18) & 0xff //GETA B := int(inst & 0x1ff) //GETB C := int(inst>>9) & 0x1ff //GETC - reg.Set(lbase+A, reg.Get(lbase+B)) + v := reg.Get(lbase + B) + // +inline-call reg.Set lbase+A v code := cf.Fn.Proto.Code pc := cf.Pc for i := 0; i < C; i++ { @@ -191,7 +193,8 @@ func init() { pc++ A = int(inst>>18) & 0xff //GETA B = int(inst & 0x1ff) //GETB - reg.Set(lbase+A, reg.Get(lbase+B)) + v := reg.Get(lbase + B) + // +inline-call reg.Set lbase+A v } cf.Pc = pc return 0 @@ -203,7 +206,8 @@ func init() { A := int(inst>>18) & 0xff //GETA RA := lbase + A Bx := int(inst & 0x3ffff) //GETBX - reg.Set(RA, cf.Fn.Proto.Constants[Bx]) + v := cf.Fn.Proto.Constants[Bx] + // +inline-call reg.Set RA v return 0 }, func(L *LState, inst uint32, baseframe *callFrame) int { //OP_LOADBOOL @@ -215,9 +219,9 @@ func init() { B := int(inst & 0x1ff) //GETB C := int(inst>>9) & 0x1ff //GETC if B != 0 { - reg.Set(RA, LTrue) + // +inline-call reg.Set RA LTrue } else { - reg.Set(RA, LFalse) + // +inline-call reg.Set RA LFalse } if C != 0 { cf.Pc++ @@ -232,7 +236,7 @@ func init() { RA := lbase + A B := int(inst & 0x1ff) //GETB for i := RA; i <= lbase+B; i++ { - reg.Set(i, LNil) + // +inline-call reg.Set i LNil } return 0 }, @@ -243,7 +247,8 @@ func init() { A := int(inst>>18) & 0xff //GETA RA := lbase + A B := int(inst & 0x1ff) //GETB - reg.Set(RA, cf.Fn.Upvalues[B].Value()) + v := cf.Fn.Upvalues[B].Value() + // +inline-call reg.Set RA v return 0 }, func(L *LState, inst uint32, baseframe *callFrame) int { //OP_GETGLOBAL @@ -254,7 +259,8 @@ func init() { RA := lbase + A Bx := int(inst & 0x3ffff) //GETBX //reg.Set(RA, L.getField(cf.Fn.Env, cf.Fn.Proto.Constants[Bx])) - reg.Set(RA, L.getFieldString(cf.Fn.Env, cf.Fn.Proto.stringConstants[Bx])) + v := L.getFieldString(cf.Fn.Env, cf.Fn.Proto.stringConstants[Bx]) + // +inline-call reg.Set RA v return 0 }, func(L *LState, inst uint32, baseframe *callFrame) int { //OP_GETTABLE @@ -265,7 +271,8 @@ func init() { RA := lbase + A B := int(inst & 0x1ff) //GETB C := int(inst>>9) & 0x1ff //GETC - reg.Set(RA, L.getField(reg.Get(lbase+B), L.rkValue(C))) + v := L.getField(reg.Get(lbase+B), L.rkValue(C)) + // +inline-call reg.Set RA v return 0 }, func(L *LState, inst uint32, baseframe *callFrame) int { //OP_GETTABLEKS @@ -276,7 +283,8 @@ func init() { RA := lbase + A B := int(inst & 0x1ff) //GETB C := int(inst>>9) & 0x1ff //GETC - reg.Set(RA, L.getFieldString(reg.Get(lbase+B), L.rkString(C))) + v := L.getFieldString(reg.Get(lbase+B), L.rkString(C)) + // +inline-call reg.Set RA v return 0 }, func(L *LState, inst uint32, baseframe *callFrame) int { //OP_SETGLOBAL @@ -330,7 +338,8 @@ func init() { RA := lbase + A B := int(inst & 0x1ff) //GETB C := int(inst>>9) & 0x1ff //GETC - reg.Set(RA, newLTable(B, C)) + v := newLTable(B, C) + // +inline-call reg.Set RA v return 0 }, func(L *LState, inst uint32, baseframe *callFrame) int { //OP_SELF @@ -342,8 +351,9 @@ func init() { B := int(inst & 0x1ff) //GETB C := int(inst>>9) & 0x1ff //GETC selfobj := reg.Get(lbase + B) - reg.Set(RA, L.getFieldString(selfobj, L.rkString(C))) - reg.Set(RA+1, selfobj) + v := L.getFieldString(selfobj, L.rkString(C)) + // +inline-call reg.Set RA v + // +inline-call reg.Set RA+1 selfobj return 0 }, opArith, // OP_ADD @@ -361,17 +371,17 @@ func init() { B := int(inst & 0x1ff) //GETB unaryv := L.rkValue(B) if nm, ok := unaryv.(LNumber); ok { - reg.SetNumber(RA, -nm) + // +inline-call reg.Set RA -nm } else { op := L.metaOp1(unaryv, "__unm") if op.Type() == LTFunction { reg.Push(op) reg.Push(unaryv) L.Call(1, 1) - reg.Set(RA, reg.Pop()) + // +inline-call reg.Set RA reg.Pop() } else if str, ok1 := unaryv.(LString); ok1 { if num, err := parseNumber(string(str)); err == nil { - reg.Set(RA, -num) + // +inline-call reg.Set RA -num } else { L.RaiseError("__unm undefined") } @@ -389,9 +399,9 @@ func init() { RA := lbase + A B := int(inst & 0x1ff) //GETB if LVIsFalse(reg.Get(lbase + B)) { - reg.Set(RA, LTrue) + // +inline-call reg.Set RA LTrue } else { - reg.Set(RA, LFalse) + // +inline-call reg.Set RA LFalse } return 0 }, @@ -404,7 +414,7 @@ func init() { B := int(inst & 0x1ff) //GETB switch lv := L.rkValue(B).(type) { case LString: - reg.SetNumber(RA, LNumber(len(lv))) + // +inline-call reg.SetNumber RA LNumber(len(lv)) default: op := L.metaOp1(lv, "__len") if op.Type() == LTFunction { @@ -413,12 +423,13 @@ func init() { L.Call(1, 1) ret := reg.Pop() if ret.Type() == LTNumber { - reg.SetNumber(RA, ret.(LNumber)) + v, _ := ret.(LNumber) + // +inline-call reg.SetNumber RA v } else { - reg.SetNumber(RA, LNumber(0)) + // +inline-call reg.Set RA ret } } else if lv.Type() == LTTable { - reg.SetNumber(RA, LNumber(lv.(*LTable).Len())) + // +inline-call reg.SetNumber RA LNumber(lv.(*LTable).Len()) } else { L.RaiseError("__len undefined") } @@ -435,7 +446,8 @@ func init() { C := int(inst>>9) & 0x1ff //GETC RC := lbase + C RB := lbase + B - reg.Set(RA, stringConcat(L, RC-RB+1, RC)) + v := stringConcat(L, RC-RB+1, RC) + // +inline-call reg.Set RA v return 0 }, func(L *LState, inst uint32, baseframe *callFrame) int { //OP_JMP @@ -483,8 +495,8 @@ func init() { rhs := L.rkValue(C) ret := false - if v1, ok1 := lhs.assertFloat64(); ok1 { - if v2, ok2 := rhs.assertFloat64(); ok2 { + if v1, ok1 := lhs.(LNumber); ok1 { + if v2, ok2 := rhs.(LNumber); ok2 { ret = v1 <= v2 } else { L.RaiseError("attempt to compare %v with %v", lhs.Type().String(), rhs.Type().String()) @@ -538,7 +550,7 @@ func init() { B := int(inst & 0x1ff) //GETB C := int(inst>>9) & 0x1ff //GETC if value := reg.Get(lbase + B); LVAsBool(value) != (C == 0) { - reg.Set(RA, value) + // +inline-call reg.Set RA value } else { cf.Pc++ } @@ -560,7 +572,7 @@ func init() { nret := C - 1 var callable *LFunction var meta bool - if fn, ok := lv.assertFunction(); ok { + if fn, ok := lv.(*LFunction); ok { callable = fn meta = false } else { @@ -586,7 +598,7 @@ func init() { lv := reg.Get(RA) var callable *LFunction var meta bool - if fn, ok := lv.assertFunction(); ok { + if fn, ok := lv.(*LFunction); ok { callable = fn meta = false } else { @@ -673,17 +685,18 @@ func init() { lbase := cf.LocalBase A := int(inst>>18) & 0xff //GETA RA := lbase + A - if init, ok1 := reg.Get(RA).assertFloat64(); ok1 { - if limit, ok2 := reg.Get(RA + 1).assertFloat64(); ok2 { - if step, ok3 := reg.Get(RA + 2).assertFloat64(); ok3 { + if init, ok1 := reg.Get(RA).(LNumber); ok1 { + if limit, ok2 := reg.Get(RA + 1).(LNumber); ok2 { + if step, ok3 := reg.Get(RA + 2).(LNumber); ok3 { init += step - reg.SetNumber(RA, LNumber(init)) + v := LNumber(init) + // +inline-call reg.SetNumber RA v if (step > 0 && init <= limit) || (step <= 0 && init >= limit) { Sbx := int(inst&0x3ffff) - opMaxArgSbx //GETSBX cf.Pc += Sbx - reg.SetNumber(RA+3, LNumber(init)) + // +inline-call reg.SetNumber RA+3 v } else { - reg.SetTop(RA + 1) + // +inline-call reg.SetTop RA+1 } } else { L.RaiseError("for statement step must be a number") @@ -703,9 +716,9 @@ func init() { A := int(inst>>18) & 0xff //GETA RA := lbase + A Sbx := int(inst&0x3ffff) - opMaxArgSbx //GETSBX - if init, ok1 := reg.Get(RA).assertFloat64(); ok1 { - if step, ok2 := reg.Get(RA + 2).assertFloat64(); ok2 { - reg.SetNumber(RA, LNumber(init-step)) + if init, ok1 := reg.Get(RA).(LNumber); ok1 { + if step, ok2 := reg.Get(RA + 2).(LNumber); ok2 { + // +inline-call reg.SetNumber RA LNumber(init-step) } else { L.RaiseError("for statement step must be a number") } @@ -723,13 +736,13 @@ func init() { RA := lbase + A C := int(inst>>9) & 0x1ff //GETC nret := C - reg.SetTop(RA + 3 + 2) - reg.Set(RA+3+2, reg.Get(RA+2)) - reg.Set(RA+3+1, reg.Get(RA+1)) - reg.Set(RA+3, reg.Get(RA)) + // +inline-call reg.SetTop RA+3+2 + // +inline-call reg.Set RA+3+2 reg.Get(RA+2) + // +inline-call reg.Set RA+3+1 reg.Get(RA+1) + // +inline-call reg.Set RA+3 reg.Get(RA) L.callR(2, nret, RA+3) if value := reg.Get(RA + 3); value != LNil { - reg.Set(RA+2, value) + // +inline-call reg.Set RA+2 value pc := cf.Fn.Proto.Code[cf.Pc] cf.Pc += int(pc&0x3ffff) - opMaxArgSbx } @@ -776,7 +789,7 @@ func init() { Bx := int(inst & 0x3ffff) //GETBX proto := cf.Fn.Proto.FunctionPrototypes[Bx] closure := newLFunctionL(proto, cf.Fn.Env, int(proto.NumUpvalues)) - reg.Set(RA, closure) + // +inline-call reg.Set RA closure for i := 0; i < int(proto.NumUpvalues); i++ { inst = cf.Fn.Proto.Code[cf.Pc] cf.Pc++ @@ -826,12 +839,14 @@ func opArith(L *LState, inst uint32, baseframe *callFrame) int { //OP_ADD, OP_SU C := int(inst>>9) & 0x1ff //GETC lhs := L.rkValue(B) rhs := L.rkValue(C) - v1, ok1 := lhs.assertFloat64() - v2, ok2 := rhs.assertFloat64() + v1, ok1 := lhs.(LNumber) + v2, ok2 := rhs.(LNumber) if ok1 && ok2 { - reg.SetNumber(RA, numberArith(L, opcode, LNumber(v1), LNumber(v2))) + v := numberArith(L, opcode, LNumber(v1), LNumber(v2)) + // +inline-call reg.SetNumber RA v } else { - reg.Set(RA, objectArith(L, opcode, lhs, rhs)) + v := objectArith(L, opcode, lhs, rhs) + // +inline-call reg.Set RA v } return 0 } @@ -840,7 +855,7 @@ func luaModulo(lhs, rhs LNumber) LNumber { flhs := float64(lhs) frhs := float64(rhs) v := math.Mod(flhs, frhs) - if flhs < 0 || frhs < 0 && !(flhs < 0 && frhs < 0) { + if frhs > 0 && v < 0 || frhs < 0 && v > 0 { v += frhs } return LNumber(v) @@ -884,7 +899,7 @@ func objectArith(L *LState, opcode int, lhs, rhs LValue) LValue { event = "__pow" } op := L.metaOp2(lhs, rhs, event) - if op.Type() == LTFunction { + if _, ok := op.(*LFunction); ok { L.reg.Push(op) L.reg.Push(lhs) L.reg.Push(rhs) @@ -901,8 +916,8 @@ func objectArith(L *LState, opcode int, lhs, rhs LValue) LValue { rhs = rnum } } - if v1, ok1 := lhs.assertFloat64(); ok1 { - if v2, ok2 := rhs.assertFloat64(); ok2 { + if v1, ok1 := lhs.(LNumber); ok1 { + if v2, ok2 := rhs.(LNumber); ok2 { return numberArith(L, opcode, LNumber(v1), LNumber(v2)) } } @@ -951,8 +966,8 @@ func stringConcat(L *LState, total, last int) LValue { func lessThan(L *LState, lhs, rhs LValue) bool { // optimization for numbers - if v1, ok1 := lhs.assertFloat64(); ok1 { - if v2, ok2 := rhs.assertFloat64(); ok2 { + if v1, ok1 := lhs.(LNumber); ok1 { + if v2, ok2 := rhs.(LNumber); ok2 { return v1 < v2 } L.RaiseError("attempt to compare %v with %v", lhs.Type().String(), rhs.Type().String()) @@ -972,17 +987,18 @@ func lessThan(L *LState, lhs, rhs LValue) bool { } func equals(L *LState, lhs, rhs LValue, raw bool) bool { - if lhs.Type() != rhs.Type() { + lt := lhs.Type() + if lt != rhs.Type() { return false } ret := false - switch lhs.Type() { + switch lt { case LTNil: ret = true case LTNumber: - v1, _ := lhs.assertFloat64() - v2, _ := rhs.assertFloat64() + v1, _ := lhs.(LNumber) + v2, _ := rhs.(LNumber) ret = v1 == v2 case LTBool: ret = bool(lhs.(LBool)) == bool(rhs.(LBool)) diff --git a/vendor/github.com/yuin/gopher-lua/ast/expr.go b/vendor/github.com/yuin/gopher-lua/ast/expr.go index ccda32791..388852bab 100644 --- a/vendor/github.com/yuin/gopher-lua/ast/expr.go +++ b/vendor/github.com/yuin/gopher-lua/ast/expr.go @@ -52,6 +52,7 @@ type StringExpr struct { type Comma3Expr struct { ExprBase + AdjustRet bool } type IdentExpr struct { diff --git a/vendor/github.com/yuin/gopher-lua/ast/stmt.go b/vendor/github.com/yuin/gopher-lua/ast/stmt.go index 56ea6d1a2..e24880b6e 100644 --- a/vendor/github.com/yuin/gopher-lua/ast/stmt.go +++ b/vendor/github.com/yuin/gopher-lua/ast/stmt.go @@ -93,3 +93,15 @@ type ReturnStmt struct { type BreakStmt struct { StmtBase } + +type LabelStmt struct { + StmtBase + + Name string +} + +type GotoStmt struct { + StmtBase + + Label string +} diff --git a/vendor/github.com/yuin/gopher-lua/auxlib.go b/vendor/github.com/yuin/gopher-lua/auxlib.go index 61a3b8b61..a022bdd89 100644 --- a/vendor/github.com/yuin/gopher-lua/auxlib.go +++ b/vendor/github.com/yuin/gopher-lua/auxlib.go @@ -40,6 +40,11 @@ func (ls *LState) CheckNumber(n int) LNumber { if lv, ok := v.(LNumber); ok { return lv } + if lv, ok := v.(LString); ok { + if num, err := parseNumber(string(lv)); err == nil { + return num + } + } ls.TypeError(n, LTNumber) return 0 } @@ -413,7 +418,7 @@ func (ls *LState) DoString(source string) error { // ToStringMeta returns string representation of given LValue. // This method calls the `__tostring` meta method if defined. func (ls *LState) ToStringMeta(lv LValue) LValue { - if fn, ok := ls.metaOp1(lv, "__tostring").assertFunction(); ok { + if fn, ok := ls.metaOp1(lv, "__tostring").(*LFunction); ok { ls.Push(fn) ls.Push(lv) ls.Call(1, 1) diff --git a/vendor/github.com/yuin/gopher-lua/baselib.go b/vendor/github.com/yuin/gopher-lua/baselib.go index 06c90619e..aa2c08a94 100644 --- a/vendor/github.com/yuin/gopher-lua/baselib.go +++ b/vendor/github.com/yuin/gopher-lua/baselib.go @@ -260,7 +260,7 @@ func basePairs(L *LState) int { func basePCall(L *LState) int { L.CheckAny(1) v := L.Get(1) - if v.Type() != LTFunction { + if v.Type() != LTFunction && L.GetMetaField(v, "__call").Type() != LTFunction { L.Push(LFalse) L.Push(LString("attempt to call a " + v.Type().String() + " value")) return 2 @@ -321,11 +321,16 @@ func baseSelect(L *LState) int { switch lv := L.Get(1).(type) { case LNumber: idx := int(lv) - num := L.reg.Top() - L.indexToReg(int(lv)) - 1 + num := L.GetTop() if idx < 0 { - num++ + idx = num + idx + } else if idx > num { + idx = num } - return num + if 1 > idx { + L.ArgError(1, "index out of range") + } + return num - idx case LString: if string(lv) != "#" { L.ArgError(1, "invalid string '"+string(lv)+"'") diff --git a/vendor/github.com/yuin/gopher-lua/compile.go b/vendor/github.com/yuin/gopher-lua/compile.go index d3c665ae5..c3736777a 100644 --- a/vendor/github.com/yuin/gopher-lua/compile.go +++ b/vendor/github.com/yuin/gopher-lua/compile.go @@ -2,9 +2,10 @@ package lua import ( "fmt" - "github.com/yuin/gopher-lua/ast" "math" "reflect" + + "github.com/yuin/gopher-lua/ast" ) /* internal constants & structs {{{ */ @@ -94,7 +95,11 @@ func sline(pos ast.PositionHolder) int { } func eline(pos ast.PositionHolder) int { - return pos.LastLine() + line := pos.LastLine() + if line == 0 { + return pos.Line() + } + return line } func savereg(ec *expcontext, reg int) int { @@ -114,7 +119,7 @@ func isVarArgReturnExpr(expr ast.Expr) bool { case *ast.FuncCallExpr: return !ex.AdjustRet case *ast.Comma3Expr: - return true + return !ex.AdjustRet } return false } @@ -134,6 +139,28 @@ func lnumberValue(expr ast.Expr) (LNumber, bool) { /* utilities }}} */ +type gotoLabelDesc struct { // {{{ + Id int + Name string + Pc int + Line int + NumActiveLocalVars int +} + +func newLabelDesc(id int, name string, pc, line, n int) *gotoLabelDesc { + return &gotoLabelDesc{ + Id: id, + Name: name, + Pc: pc, + Line: line, + NumActiveLocalVars: n, + } +} + +func (l *gotoLabelDesc) SetNumActiveLocalVars(n int) { + l.NumActiveLocalVars = n +} // }}} + type CompileError struct { // {{{ context *funcContext Line int @@ -328,16 +355,18 @@ func (vp *varNamePool) Register(name string) int { /* FuncContext {{{ */ type codeBlock struct { - LocalVars *varNamePool - BreakLabel int - Parent *codeBlock - RefUpvalue bool - LineStart int - LastLine int + LocalVars *varNamePool + BreakLabel int + Parent *codeBlock + RefUpvalue bool + LineStart int + LastLine int + labels map[string]*gotoLabelDesc + firstGotoIndex int } -func newCodeBlock(localvars *varNamePool, blabel int, parent *codeBlock, pos ast.PositionHolder) *codeBlock { - bl := &codeBlock{localvars, blabel, parent, false, 0, 0} +func newCodeBlock(localvars *varNamePool, blabel int, parent *codeBlock, pos ast.PositionHolder, firstGotoIndex int) *codeBlock { + bl := &codeBlock{localvars, blabel, parent, false, 0, 0, map[string]*gotoLabelDesc{}, firstGotoIndex} if pos != nil { bl.LineStart = pos.Line() bl.LastLine = pos.LastLine() @@ -345,33 +374,136 @@ func newCodeBlock(localvars *varNamePool, blabel int, parent *codeBlock, pos ast return bl } +func (b *codeBlock) AddLabel(label *gotoLabelDesc) *gotoLabelDesc { + if old, ok := b.labels[label.Name]; ok { + return old + } + b.labels[label.Name] = label + return nil +} + +func (b *codeBlock) GetLabel(label string) *gotoLabelDesc { + if v, ok := b.labels[label]; ok { + return v + } + return nil +} + +func (b *codeBlock) LocalVarsCount() int { + count := 0 + for block := b; block != nil; block = block.Parent { + count += len(block.LocalVars.Names()) + } + return count +} + type funcContext struct { - Proto *FunctionProto - Code *codeStore - Parent *funcContext - Upvalues *varNamePool - Block *codeBlock - Blocks []*codeBlock - regTop int - labelId int - labelPc map[int]int + Proto *FunctionProto + Code *codeStore + Parent *funcContext + Upvalues *varNamePool + Block *codeBlock + Blocks []*codeBlock + regTop int + labelId int + labelPc map[int]int + gotosCount int + unresolvedGotos map[int]*gotoLabelDesc } func newFuncContext(sourcename string, parent *funcContext) *funcContext { fc := &funcContext{ - Proto: newFunctionProto(sourcename), - Code: &codeStore{make([]uint32, 0, 1024), make([]int, 0, 1024), 0}, - Parent: parent, - Upvalues: newVarNamePool(0), - Block: newCodeBlock(newVarNamePool(0), labelNoJump, nil, nil), - regTop: 0, - labelId: 1, - labelPc: map[int]int{}, + Proto: newFunctionProto(sourcename), + Code: &codeStore{make([]uint32, 0, 1024), make([]int, 0, 1024), 0}, + Parent: parent, + Upvalues: newVarNamePool(0), + Block: newCodeBlock(newVarNamePool(0), labelNoJump, nil, nil, 0), + regTop: 0, + labelId: 1, + labelPc: map[int]int{}, + gotosCount: 0, + unresolvedGotos: map[int]*gotoLabelDesc{}, } fc.Blocks = []*codeBlock{fc.Block} return fc } +func (fc *funcContext) CheckUnresolvedGoto() { + for i := fc.Block.firstGotoIndex; i < fc.gotosCount; i++ { + gotoLabel, ok := fc.unresolvedGotos[i] + if !ok { + continue + } + raiseCompileError(fc, fc.Proto.LastLineDefined, "no visible label '%s' for at line %d", gotoLabel.Name, gotoLabel.Line) + } +} + +func (fc *funcContext) AddUnresolvedGoto(label *gotoLabelDesc) { + fc.unresolvedGotos[fc.gotosCount] = label + fc.gotosCount++ +} + +func (fc *funcContext) AddNamedLabel(label *gotoLabelDesc) { + if old := fc.Block.AddLabel(label); old != nil { + raiseCompileError(fc, label.Line+1, "label '%s' already defined on line %d", label.Name, old.Line) + } + fc.SetLabelPc(label.Id, label.Pc) +} + +func (fc *funcContext) GetNamedLabel(name string) *gotoLabelDesc { + return fc.Block.GetLabel(name) +} + +func (fc *funcContext) ResolveGoto(from, to *gotoLabelDesc, index int) { + if from.NumActiveLocalVars < to.NumActiveLocalVars { + varName := fc.Block.LocalVars.Names()[len(fc.Block.LocalVars.Names())-1] + raiseCompileError(fc, to.Line+1, " at line %d jumps into the scope of local '%s'", to.Name, from.Line, varName) + } + fc.Code.SetSbx(from.Pc, to.Id) + delete(fc.unresolvedGotos, index) +} + +func (fc *funcContext) FindLabel(block *codeBlock, gotoLabel *gotoLabelDesc, i int) bool { + target := block.GetLabel(gotoLabel.Name) + if target != nil { + if gotoLabel.NumActiveLocalVars > target.NumActiveLocalVars && block.RefUpvalue { + fc.Code.SetA(gotoLabel.Pc-1, target.NumActiveLocalVars) + } + fc.ResolveGoto(gotoLabel, target, i) + return true + } + return false +} + +func (fc *funcContext) ResolveCurrentBlockGotosWithParentBlock() { + blockActiveLocalVars := fc.Block.Parent.LocalVarsCount() + for i := fc.Block.firstGotoIndex; i < fc.gotosCount; i++ { + gotoLabel, ok := fc.unresolvedGotos[i] + if !ok { + continue + } + if gotoLabel.NumActiveLocalVars > blockActiveLocalVars { + if fc.Block.RefUpvalue { + fc.Code.SetA(gotoLabel.Pc-1, blockActiveLocalVars) + } + gotoLabel.SetNumActiveLocalVars(blockActiveLocalVars) + } + fc.FindLabel(fc.Block.Parent, gotoLabel, i) + } +} + +func (fc *funcContext) ResolveForwardGoto(target *gotoLabelDesc) { + for i := fc.Block.firstGotoIndex; i <= fc.gotosCount; i++ { + gotoLabel, ok := fc.unresolvedGotos[i] + if !ok { + continue + } + if gotoLabel.Name == target.Name { + fc.ResolveGoto(gotoLabel, target, i) + } + } +} + func (fc *funcContext) NewLabel() int { ret := fc.labelId fc.labelId++ @@ -400,6 +532,13 @@ func (fc *funcContext) ConstIndex(value LValue) int { } return v } +func (fc *funcContext) BlockLocalVarsCount() int { + count := 0 + for block := fc.Block; block != nil; block = block.Parent { + count += len(block.LocalVars.Names()) + } + return count +} func (fc *funcContext) RegisterLocalVar(name string) int { ret := fc.Block.LocalVars.Register(name) @@ -431,7 +570,7 @@ func (fc *funcContext) LocalVars() []varNamePoolValue { } func (fc *funcContext) EnterBlock(blabel int, pos ast.PositionHolder) { - fc.Block = newCodeBlock(newVarNamePool(fc.RegTop()), blabel, fc.Block, pos) + fc.Block = newCodeBlock(newVarNamePool(fc.RegTop()), blabel, fc.Block, pos, fc.gotosCount) fc.Blocks = append(fc.Blocks, fc.Block) } @@ -447,6 +586,10 @@ func (fc *funcContext) CloseUpvalues() int { func (fc *funcContext) LeaveBlock() int { closed := fc.CloseUpvalues() fc.EndScope() + + if fc.Block.Parent != nil { + fc.ResolveCurrentBlockGotosWithParentBlock() + } fc.Block = fc.Block.Parent fc.SetRegTop(fc.Block.LocalVars.LastIndex()) return closed @@ -471,9 +614,17 @@ func (fc *funcContext) RegTop() int { /* FuncContext }}} */ -func compileChunk(context *funcContext, chunk []ast.Stmt) { // {{{ - for _, stmt := range chunk { - compileStmt(context, stmt) +func compileChunk(context *funcContext, chunk []ast.Stmt, untilFollows bool) { // {{{ + for i, stmt := range chunk { + lastStmt := true + for j := i + 1; j < len(chunk); j++ { + _, ok := chunk[j].(*ast.LabelStmt) + if !ok { + lastStmt = false + break + } + } + compileStmt(context, stmt, lastStmt && !untilFollows) } } // }}} @@ -485,13 +636,21 @@ func compileBlock(context *funcContext, chunk []ast.Stmt) { // {{{ ph.SetLine(sline(chunk[0])) ph.SetLastLine(eline(chunk[len(chunk)-1])) context.EnterBlock(labelNoJump, ph) - for _, stmt := range chunk { - compileStmt(context, stmt) + for i, stmt := range chunk { + lastStmt := true + for j := i + 1; j < len(chunk); j++ { + _, ok := chunk[j].(*ast.LabelStmt) + if !ok { + lastStmt = false + break + } + } + compileStmt(context, stmt, lastStmt) } context.LeaveBlock() } // }}} -func compileStmt(context *funcContext, stmt ast.Stmt) { // {{{ +func compileStmt(context *funcContext, stmt ast.Stmt, isLastStmt bool) { // {{{ switch st := stmt.(type) { case *ast.AssignStmt: compileAssignStmt(context, st) @@ -501,7 +660,7 @@ func compileStmt(context *funcContext, stmt ast.Stmt) { // {{{ compileFuncCallExpr(context, context.RegTop(), st.Expr.(*ast.FuncCallExpr), ecnone(-1)) case *ast.DoBlockStmt: context.EnterBlock(labelNoJump, st) - compileChunk(context, st.Stmts) + compileChunk(context, st.Stmts, false) context.LeaveBlock() case *ast.WhileStmt: compileWhileStmt(context, st) @@ -519,14 +678,17 @@ func compileStmt(context *funcContext, stmt ast.Stmt) { // {{{ compileNumberForStmt(context, st) case *ast.GenericForStmt: compileGenericForStmt(context, st) + case *ast.LabelStmt: + compileLabelStmt(context, st, isLastStmt) + case *ast.GotoStmt: + compileGotoStmt(context, st) } } // }}} func compileAssignStmtLeft(context *funcContext, stmt *ast.AssignStmt) (int, []*assigncontext) { // {{{ reg := context.RegTop() acs := make([]*assigncontext, 0, len(stmt.Lhs)) - for i, lhs := range stmt.Lhs { - islast := i == len(stmt.Lhs)-1 + for _, lhs := range stmt.Lhs { switch st := lhs.(type) { case *ast.IdentExpr: identtype := getIdentRefType(context, context, st) @@ -537,9 +699,7 @@ func compileAssignStmtLeft(context *funcContext, stmt *ast.AssignStmt) (int, []* case ecUpvalue: context.Upvalues.RegisterUnique(st.Value) case ecLocal: - if islast { - ec.reg = context.FindLocalVar(st.Value) - } + ec.reg = context.FindLocalVar(st.Value) } acs = append(acs, &assigncontext{ec, 0, 0, false, false}) case *ast.AttrGetExpr: @@ -723,8 +883,12 @@ func compileReturnStmt(context *funcContext, stmt *ast.ReturnStmt) { // {{{ return } case *ast.FuncCallExpr: - reg += compileExpr(context, reg, ex, ecnone(-2)) - code.SetOpCode(code.LastPC(), OP_TAILCALL) + if ex.AdjustRet { // return (func()) + reg += compileExpr(context, reg, ex, ecnone(0)) + } else { + reg += compileExpr(context, reg, ex, ecnone(-2)) + code.SetOpCode(code.LastPC(), OP_TAILCALL) + } code.AddABC(OP_RETURN, a, 0, 0, sline(stmt)) return } @@ -821,7 +985,7 @@ func compileWhileStmt(context *funcContext, stmt *ast.WhileStmt) { // {{{ compileBranchCondition(context, context.RegTop(), stmt.Condition, thenlabel, elselabel, false) context.SetLabelPc(thenlabel, context.Code.LastPC()) context.EnterBlock(elselabel, stmt) - compileChunk(context, stmt.Stmts) + compileChunk(context, stmt.Stmts, false) context.CloseUpvalues() context.Code.AddASbx(OP_JMP, 0, condlabel, eline(stmt)) context.LeaveBlock() @@ -836,7 +1000,7 @@ func compileRepeatStmt(context *funcContext, stmt *ast.RepeatStmt) { // {{{ context.SetLabelPc(initlabel, context.Code.LastPC()) context.SetLabelPc(elselabel, context.Code.LastPC()) context.EnterBlock(thenlabel, stmt) - compileChunk(context, stmt.Stmts) + compileChunk(context, stmt.Stmts, true) compileBranchCondition(context, context.RegTop(), stmt.Condition, thenlabel, elselabel, false) context.SetLabelPc(thenlabel, context.Code.LastPC()) @@ -912,7 +1076,7 @@ func compileNumberForStmt(context *funcContext, stmt *ast.NumberForStmt) { // {{ context.RegisterLocalVar(stmt.Name) bodypc := code.LastPC() - compileChunk(context, stmt.Stmts) + compileChunk(context, stmt.Stmts, false) context.LeaveBlock() @@ -945,7 +1109,7 @@ func compileGenericForStmt(context *funcContext, stmt *ast.GenericForStmt) { // } context.SetLabelPc(bodylabel, code.LastPC()) - compileChunk(context, stmt.Stmts) + compileChunk(context, stmt.Stmts, false) context.LeaveBlock() @@ -956,6 +1120,24 @@ func compileGenericForStmt(context *funcContext, stmt *ast.GenericForStmt) { // context.SetLabelPc(endlabel, code.LastPC()) } // }}} +func compileLabelStmt(context *funcContext, stmt *ast.LabelStmt, isLastStmt bool) { // {{{ + labelId := context.NewLabel() + label := newLabelDesc(labelId, stmt.Name, context.Code.LastPC(), sline(stmt), context.BlockLocalVarsCount()) + context.AddNamedLabel(label) + if isLastStmt { + label.SetNumActiveLocalVars(context.Block.Parent.LocalVarsCount()) + } + context.ResolveForwardGoto(label) +} // }}} + +func compileGotoStmt(context *funcContext, stmt *ast.GotoStmt) { // {{{ + context.Code.AddABC(OP_CLOSE, 0, 0, 0, sline(stmt)) + context.Code.AddASbx(OP_JMP, 0, labelNoJump, sline(stmt)) + label := newLabelDesc(-1, stmt.Label, context.Code.LastPC(), sline(stmt), context.BlockLocalVarsCount()) + context.AddUnresolvedGoto(label) + context.FindLabel(context.Block, label, context.gotosCount-1) +} // }}} + func compileExpr(context *funcContext, reg int, expr ast.Expr, ec *expcontext) int { // {{{ code := context.Code sreg := savereg(ec, reg) @@ -1145,10 +1327,11 @@ func compileFunctionExpr(context *funcContext, funcexpr *ast.FunctionExpr, ec *e context.Proto.IsVarArg |= VarArgIsVarArg } - compileChunk(context, funcexpr.Stmts) + compileChunk(context, funcexpr.Stmts, false) context.Code.AddABC(OP_RETURN, 0, 1, 0, eline(funcexpr)) context.EndScope() + context.CheckUnresolvedGoto() context.Proto.Code = context.Code.List() context.Proto.DbgSourcePositions = context.Code.PosList() context.Proto.DbgUpvalues = context.Upvalues.Names() @@ -1464,7 +1647,17 @@ func compileLogicalOpExprAux(context *funcContext, reg int, expr ast.Expr, ec *e a := reg sreg := savereg(ec, a) - if !hasnextcond && thenlabel == elselabel { + isLastAnd := elselabel == lb.e && thenlabel != elselabel + isLastOr := thenlabel == lb.e && hasnextcond + + if ident, ok := expr.(*ast.IdentExpr); ok && (isLastAnd || isLastOr) && getIdentRefType(context, context, ident) == ecLocal { + b := context.FindLocalVar(ident.Value) + op := OP_TESTSET + if sreg == b { + op = OP_TEST + } + code.AddABC(op, sreg, b, 0^flip, sline(expr)) + } else if !hasnextcond && thenlabel == elselabel { reg += compileExpr(context, reg, expr, &expcontext{ec.ctype, intMax(a, sreg), ec.varargopt}) last := context.Code.Last() if opGetOpCode(last) == OP_MOVE && opGetArgA(last) == a { @@ -1474,7 +1667,7 @@ func compileLogicalOpExprAux(context *funcContext, reg int, expr ast.Expr, ec *e } } else { reg += compileExpr(context, reg, expr, ecnone(0)) - if sreg == a { + if !hasnextcond { code.AddABC(OP_TEST, a, 0, 0^flip, sline(expr)) } else { code.AddABC(OP_TESTSET, sreg, a, 0^flip, sline(expr)) @@ -1665,6 +1858,10 @@ func Compile(chunk []ast.Stmt, name string) (proto *FunctionProto, err error) { err = nil parlist := &ast.ParList{HasVargs: true, Names: []string{}} funcexpr := &ast.FunctionExpr{ParList: parlist, Stmts: chunk} + if len(chunk) > 0 { + funcexpr.SetLastLine(sline(chunk[0])) + funcexpr.SetLastLine(eline(chunk[len(chunk)-1]) + 1) + } context := newFuncContext(name, nil) compileFunctionExpr(context, funcexpr, ecnone(0)) proto = context.Proto diff --git a/vendor/github.com/yuin/gopher-lua/config.go b/vendor/github.com/yuin/gopher-lua/config.go index f58b59393..d63218895 100644 --- a/vendor/github.com/yuin/gopher-lua/config.go +++ b/vendor/github.com/yuin/gopher-lua/config.go @@ -22,15 +22,22 @@ var LuaPath = "LUA_PATH" var LuaLDir string var LuaPathDefault string var LuaOS string +var LuaDirSep string +var LuaPathSep = ";" +var LuaPathMark = "?" +var LuaExecDir = "!" +var LuaIgMark = "-" func init() { if os.PathSeparator == '/' { // unix-like LuaOS = "unix" LuaLDir = "/usr/local/share/lua/5.1" + LuaDirSep = "/" LuaPathDefault = "./?.lua;" + LuaLDir + "/?.lua;" + LuaLDir + "/?/init.lua" } else { // windows LuaOS = "windows" LuaLDir = "!\\lua" + LuaDirSep = "\\" LuaPathDefault = ".\\?.lua;" + LuaLDir + "\\?.lua;" + LuaLDir + "\\?\\init.lua" } } diff --git a/vendor/github.com/yuin/gopher-lua/debuglib.go b/vendor/github.com/yuin/gopher-lua/debuglib.go index 41f883f1d..da8f5254b 100644 --- a/vendor/github.com/yuin/gopher-lua/debuglib.go +++ b/vendor/github.com/yuin/gopher-lua/debuglib.go @@ -155,8 +155,8 @@ func debugTraceback(L *LState) int { level := L.OptInt(2, 1) ls := L if L.GetTop() > 0 { - if s, ok := L.Get(1).assertString(); ok { - msg = s + if s, ok := L.Get(1).(LString); ok { + msg = string(s) } if l, ok := L.Get(1).(*LState); ok { ls = l diff --git a/vendor/github.com/yuin/gopher-lua/iolib.go b/vendor/github.com/yuin/gopher-lua/iolib.go index 4a86f8936..3f5f295ce 100644 --- a/vendor/github.com/yuin/gopher-lua/iolib.go +++ b/vendor/github.com/yuin/gopher-lua/iolib.go @@ -629,7 +629,7 @@ func ioOpenFile(L *LState) int { mode = os.O_RDONLY writable = false case "w", "wb": - mode = os.O_WRONLY | os.O_CREATE + mode = os.O_WRONLY | os.O_TRUNC | os.O_CREATE readable = false case "a", "ab": mode = os.O_WRONLY | os.O_APPEND | os.O_CREATE @@ -658,6 +658,9 @@ func ioPopen(L *LState) int { cmd := L.CheckString(1) if L.GetTop() == 1 { L.Push(LString("r")) + } else if L.GetTop() > 1 && (L.Get(2)).Type() == LTNil { + L.SetTop(1) + L.Push(LString("r")) } var file *LUserData var err error diff --git a/vendor/github.com/yuin/gopher-lua/loadlib.go b/vendor/github.com/yuin/gopher-lua/loadlib.go index 772bb04ad..40ce122b8 100644 --- a/vendor/github.com/yuin/gopher-lua/loadlib.go +++ b/vendor/github.com/yuin/gopher-lua/loadlib.go @@ -65,6 +65,9 @@ func OpenPackage(L *LState) int { L.SetField(packagemod, "path", LString(loGetPath(LuaPath, LuaPathDefault))) L.SetField(packagemod, "cpath", emptyLString) + L.SetField(packagemod, "config", LString(LuaDirSep+"\n"+LuaPathSep+ + "\n"+LuaPathMark+"\n"+LuaExecDir+"\n"+LuaIgMark+"\n")) + L.Push(packagemod) return 1 } diff --git a/vendor/github.com/yuin/gopher-lua/oslib.go b/vendor/github.com/yuin/gopher-lua/oslib.go index c70a99bf1..5448cc1f8 100644 --- a/vendor/github.com/yuin/gopher-lua/oslib.go +++ b/vendor/github.com/yuin/gopher-lua/oslib.go @@ -23,8 +23,11 @@ func getIntField(L *LState, tb *LTable, key string, v int) int { slv := string(lv) slv = strings.TrimLeft(slv, " ") if strings.HasPrefix(slv, "0") && !strings.HasPrefix(slv, "0x") && !strings.HasPrefix(slv, "0X") { - //Standard lua interpreter only support decimal and hexadecimal + // Standard lua interpreter only support decimal and hexadecimal slv = strings.TrimLeft(slv, "0") + if slv == "" { + return 0 + } } if num, err := parseNumber(slv); err == nil { return int(num) @@ -103,16 +106,20 @@ func osExit(L *LState) int { func osDate(L *LState) int { t := time.Now() + isUTC := false cfmt := "%c" if L.GetTop() >= 1 { cfmt = L.CheckString(1) if strings.HasPrefix(cfmt, "!") { - t = time.Now().UTC() cfmt = strings.TrimLeft(cfmt, "!") + isUTC = true } if L.GetTop() >= 2 { t = time.Unix(L.CheckInt64(2), 0) } + if isUTC { + t = t.UTC() + } if strings.HasPrefix(cfmt, "*t") { ret := L.NewTable() ret.RawSetString("year", LNumber(t.Year())) @@ -189,20 +196,28 @@ func osTime(L *LState) int { if L.GetTop() == 0 { L.Push(LNumber(time.Now().Unix())) } else { - tbl := L.CheckTable(1) - sec := getIntField(L, tbl, "sec", 0) - min := getIntField(L, tbl, "min", 0) - hour := getIntField(L, tbl, "hour", 12) - day := getIntField(L, tbl, "day", -1) - month := getIntField(L, tbl, "month", -1) - year := getIntField(L, tbl, "year", -1) - isdst := getBoolField(L, tbl, "isdst", false) - t := time.Date(year, time.Month(month), day, hour, min, sec, 0, time.Local) - // TODO dst - if false { - print(isdst) + lv := L.CheckAny(1) + if lv == LNil { + L.Push(LNumber(time.Now().Unix())) + } else { + tbl, ok := lv.(*LTable) + if !ok { + L.TypeError(1, LTTable) + } + sec := getIntField(L, tbl, "sec", 0) + min := getIntField(L, tbl, "min", 0) + hour := getIntField(L, tbl, "hour", 12) + day := getIntField(L, tbl, "day", -1) + month := getIntField(L, tbl, "month", -1) + year := getIntField(L, tbl, "year", -1) + isdst := getBoolField(L, tbl, "isdst", false) + t := time.Date(year, time.Month(month), day, hour, min, sec, 0, time.Local) + // TODO dst + if false { + print(isdst) + } + L.Push(LNumber(t.Unix())) } - L.Push(LNumber(t.Unix())) } return 1 } diff --git a/vendor/github.com/yuin/gopher-lua/parse/Makefile b/vendor/github.com/yuin/gopher-lua/parse/Makefile index 6dd048c16..9838b1393 100644 --- a/vendor/github.com/yuin/gopher-lua/parse/Makefile +++ b/vendor/github.com/yuin/gopher-lua/parse/Makefile @@ -2,3 +2,6 @@ all : parser.go parser.go : parser.go.y goyacc -o $@ parser.go.y; [ -f y.output ] && ( rm -f y.output ) + +clean: + rm -f parser.go diff --git a/vendor/github.com/yuin/gopher-lua/parse/lexer.go b/vendor/github.com/yuin/gopher-lua/parse/lexer.go index d711e78bc..c20a0bdd6 100644 --- a/vendor/github.com/yuin/gopher-lua/parse/lexer.go +++ b/vendor/github.com/yuin/gopher-lua/parse/lexer.go @@ -4,11 +4,12 @@ import ( "bufio" "bytes" "fmt" - "github.com/yuin/gopher-lua/ast" "io" "reflect" "strconv" "strings" + + "github.com/yuin/gopher-lua/ast" ) const EOF = -1 @@ -255,7 +256,7 @@ func (sc *Scanner) scanMultilineString(ch int, buf *bytes.Buffer) error { var count1, count2 int count1, ch = sc.countSep(ch) if ch != '[' { - return sc.Error(string(ch), "invalid multiline string") + return sc.Error(string(rune(ch)), "invalid multiline string") } ch = sc.Next() if ch == '\n' || ch == '\r' { @@ -286,7 +287,7 @@ var reservedWords = map[string]int{ "end": TEnd, "false": TFalse, "for": TFor, "function": TFunction, "if": TIf, "in": TIn, "local": TLocal, "nil": TNil, "not": TNot, "or": TOr, "return": TReturn, "repeat": TRepeat, "then": TThen, "true": TTrue, - "until": TUntil, "while": TWhile} + "until": TUntil, "while": TWhile, "goto": TGoto} func (sc *Scanner) Scan(lexer *Lexer) (ast.Token, error) { redo: @@ -338,7 +339,7 @@ redo: goto redo } else { tok.Type = ch - tok.Str = string(ch) + tok.Str = string(rune(ch)) } case '"', '\'': tok.Type = TString @@ -351,7 +352,7 @@ redo: tok.Str = buf.String() } else { tok.Type = ch - tok.Str = string(ch) + tok.Str = string(rune(ch)) } case '=': if sc.Peek() == '=' { @@ -360,7 +361,7 @@ redo: sc.Next() } else { tok.Type = ch - tok.Str = string(ch) + tok.Str = string(rune(ch)) } case '~': if sc.Peek() == '=' { @@ -377,7 +378,7 @@ redo: sc.Next() } else { tok.Type = ch - tok.Str = string(ch) + tok.Str = string(rune(ch)) } case '>': if sc.Peek() == '=' { @@ -386,7 +387,7 @@ redo: sc.Next() } else { tok.Type = ch - tok.Str = string(ch) + tok.Str = string(rune(ch)) } case '.': ch2 := sc.Peek() @@ -408,9 +409,18 @@ redo: tok.Type = '.' } tok.Str = buf.String() - case '+', '*', '/', '%', '^', '#', '(', ')', '{', '}', ']', ';', ':', ',': + case ':': + if sc.Peek() == ':' { + tok.Type = T2Colon + tok.Str = "::" + sc.Next() + } else { + tok.Type = ch + tok.Str = string(rune(ch)) + } + case '+', '*', '/', '%', '^', '#', '(', ')', '{', '}', ']', ';', ',': tok.Type = ch - tok.Str = string(ch) + tok.Str = string(rune(ch)) default: writeChar(buf, ch) err = sc.Error(buf.String(), "Invalid token") diff --git a/vendor/github.com/yuin/gopher-lua/parse/parser.go b/vendor/github.com/yuin/gopher-lua/parse/parser.go index f8f59b361..aee06c8c8 100644 --- a/vendor/github.com/yuin/gopher-lua/parse/parser.go +++ b/vendor/github.com/yuin/gopher-lua/parse/parser.go @@ -1,9 +1,12 @@ +// Code generated by goyacc -o parser.go parser.go.y. DO NOT EDIT. + //line parser.go.y:2 package parse import __yyfmt__ "fmt" //line parser.go.y:2 + import ( "github.com/yuin/gopher-lua/ast" ) @@ -51,18 +54,23 @@ const TThen = 57363 const TTrue = 57364 const TUntil = 57365 const TWhile = 57366 -const TEqeq = 57367 -const TNeq = 57368 -const TLte = 57369 -const TGte = 57370 -const T2Comma = 57371 -const T3Comma = 57372 -const TIdent = 57373 -const TNumber = 57374 -const TString = 57375 -const UNARY = 57376 - -var yyToknames = []string{ +const TGoto = 57367 +const TEqeq = 57368 +const TNeq = 57369 +const TLte = 57370 +const TGte = 57371 +const T2Comma = 57372 +const T3Comma = 57373 +const T2Colon = 57374 +const TIdent = 57375 +const TNumber = 57376 +const TString = 57377 +const UNARY = 57378 + +var yyToknames = [...]string{ + "$end", + "error", + "$unk", "TAnd", "TBreak", "TDo", @@ -84,34 +92,48 @@ var yyToknames = []string{ "TTrue", "TUntil", "TWhile", + "TGoto", "TEqeq", "TNeq", "TLte", "TGte", "T2Comma", "T3Comma", + "T2Colon", "TIdent", "TNumber", "TString", - " {", - " (", - " >", - " <", - " +", - " -", - " *", - " /", - " %", + "'{'", + "'('", + "'>'", + "'<'", + "'+'", + "'-'", + "'*'", + "'/'", + "'%'", "UNARY", - " ^", + "'^'", + "';'", + "'='", + "','", + "':'", + "'.'", + "'['", + "']'", + "'#'", + "')'", + "'}'", } -var yyStatenames = []string{} + +var yyStatenames = [...]string{} const yyEofCode = 1 const yyErrCode = 2 -const yyMaxDepth = 200 +const yyInitialStackSize = 16 + +//line parser.go.y:525 -//line parser.go.y:514 func TokenName(c int) string { if c >= TAnd && c-TAnd < len(yyToknames) { if yyToknames[c-TAnd] != "" { @@ -122,232 +144,262 @@ func TokenName(c int) string { } //line yacctab:1 -var yyExca = []int{ +var yyExca = [...]int8{ -1, 1, 1, -1, -2, 0, - -1, 17, - 46, 31, - 47, 31, - -2, 68, - -1, 93, - 46, 32, - 47, 32, - -2, 68, + -1, 19, + 48, 33, + 49, 33, + -2, 70, + -1, 97, + 48, 34, + 49, 34, + -2, 70, } -const yyNprod = 95 const yyPrivate = 57344 -var yyTokenNames []string -var yyStates []string - -const yyLast = 579 - -var yyAct = []int{ - - 24, 88, 50, 23, 45, 84, 56, 65, 137, 153, - 136, 113, 52, 142, 54, 53, 33, 134, 65, 132, - 62, 63, 32, 61, 108, 109, 48, 111, 106, 41, - 42, 105, 49, 155, 166, 81, 82, 83, 138, 104, - 22, 91, 131, 80, 95, 92, 162, 74, 48, 85, - 150, 99, 165, 148, 49, 149, 75, 76, 77, 78, - 79, 67, 80, 107, 106, 148, 114, 115, 116, 117, - 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, - 128, 129, 72, 73, 71, 70, 74, 65, 39, 40, - 47, 139, 133, 68, 69, 75, 76, 77, 78, 79, - 60, 80, 141, 144, 143, 146, 145, 31, 67, 147, - 9, 48, 110, 97, 48, 152, 151, 49, 38, 62, - 49, 17, 66, 77, 78, 79, 96, 80, 59, 72, - 73, 71, 70, 74, 154, 102, 91, 156, 55, 157, - 68, 69, 75, 76, 77, 78, 79, 21, 80, 187, - 94, 20, 26, 184, 37, 179, 163, 112, 25, 35, - 178, 93, 170, 172, 27, 171, 164, 173, 19, 159, - 175, 174, 29, 89, 28, 39, 40, 20, 182, 181, - 100, 34, 135, 183, 67, 39, 40, 47, 186, 64, - 51, 1, 90, 87, 36, 130, 86, 30, 66, 18, - 46, 44, 43, 8, 58, 72, 73, 71, 70, 74, - 57, 67, 168, 169, 167, 3, 68, 69, 75, 76, - 77, 78, 79, 160, 80, 66, 4, 2, 0, 0, - 0, 158, 72, 73, 71, 70, 74, 0, 0, 0, - 0, 0, 0, 68, 69, 75, 76, 77, 78, 79, - 26, 80, 37, 0, 0, 0, 25, 35, 140, 0, - 0, 0, 27, 0, 0, 0, 0, 0, 0, 0, - 29, 21, 28, 39, 40, 20, 26, 0, 37, 34, - 0, 0, 25, 35, 0, 0, 0, 0, 27, 0, - 0, 0, 36, 98, 0, 0, 29, 89, 28, 39, - 40, 20, 26, 0, 37, 34, 0, 0, 25, 35, - 0, 0, 0, 0, 27, 67, 90, 176, 36, 0, - 0, 0, 29, 21, 28, 39, 40, 20, 0, 66, - 0, 34, 0, 0, 0, 0, 72, 73, 71, 70, - 74, 0, 67, 0, 36, 0, 0, 68, 69, 75, - 76, 77, 78, 79, 0, 80, 66, 0, 177, 0, - 0, 0, 0, 72, 73, 71, 70, 74, 0, 67, - 0, 185, 0, 0, 68, 69, 75, 76, 77, 78, - 79, 0, 80, 66, 0, 161, 0, 0, 0, 0, - 72, 73, 71, 70, 74, 0, 67, 0, 0, 0, - 0, 68, 69, 75, 76, 77, 78, 79, 0, 80, - 66, 0, 0, 180, 0, 0, 0, 72, 73, 71, - 70, 74, 0, 67, 0, 0, 0, 0, 68, 69, - 75, 76, 77, 78, 79, 0, 80, 66, 0, 0, - 103, 0, 0, 0, 72, 73, 71, 70, 74, 0, - 67, 0, 101, 0, 0, 68, 69, 75, 76, 77, - 78, 79, 0, 80, 66, 0, 0, 0, 0, 0, - 0, 72, 73, 71, 70, 74, 0, 67, 0, 0, - 0, 0, 68, 69, 75, 76, 77, 78, 79, 0, - 80, 66, 0, 0, 0, 0, 0, 0, 72, 73, - 71, 70, 74, 0, 0, 0, 0, 0, 0, 68, - 69, 75, 76, 77, 78, 79, 0, 80, 72, 73, - 71, 70, 74, 0, 0, 0, 0, 0, 0, 68, - 69, 75, 76, 77, 78, 79, 0, 80, 7, 10, - 0, 0, 0, 0, 14, 15, 13, 0, 16, 0, - 0, 0, 6, 12, 0, 0, 0, 11, 0, 0, - 0, 0, 0, 0, 21, 0, 0, 0, 20, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 5, +const yyLast = 616 + +var yyAct = [...]uint8{ + 26, 92, 52, 25, 47, 88, 158, 58, 142, 118, + 141, 167, 54, 69, 56, 55, 35, 139, 41, 42, + 49, 160, 34, 67, 63, 137, 50, 64, 112, 113, + 69, 109, 51, 48, 46, 45, 147, 85, 86, 87, + 115, 110, 24, 95, 43, 44, 99, 96, 78, 136, + 50, 171, 143, 103, 69, 108, 51, 84, 79, 80, + 81, 82, 83, 89, 84, 111, 110, 41, 42, 49, + 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, + 129, 130, 131, 132, 133, 134, 81, 82, 83, 170, + 84, 153, 155, 33, 154, 144, 9, 138, 40, 23, + 153, 19, 62, 22, 114, 101, 146, 149, 148, 151, + 150, 71, 100, 152, 66, 50, 65, 61, 50, 157, + 156, 51, 57, 64, 51, 70, 116, 106, 192, 21, + 173, 174, 172, 76, 77, 75, 74, 78, 98, 159, + 189, 95, 161, 97, 162, 72, 73, 79, 80, 81, + 82, 83, 68, 84, 184, 183, 177, 169, 164, 104, + 140, 168, 117, 53, 1, 91, 135, 175, 32, 20, + 176, 8, 178, 60, 59, 180, 179, 3, 165, 28, + 4, 39, 2, 187, 186, 27, 37, 0, 188, 0, + 0, 29, 0, 191, 71, 0, 0, 0, 0, 0, + 31, 0, 93, 30, 41, 42, 22, 0, 70, 0, + 36, 0, 0, 0, 0, 0, 76, 77, 75, 74, + 78, 94, 0, 38, 71, 90, 0, 0, 72, 73, + 79, 80, 81, 82, 83, 0, 84, 0, 70, 0, + 0, 0, 0, 163, 0, 0, 76, 77, 75, 74, + 78, 0, 0, 0, 0, 0, 0, 0, 72, 73, + 79, 80, 81, 82, 83, 28, 84, 39, 0, 0, + 0, 27, 37, 145, 0, 0, 0, 29, 0, 0, + 0, 0, 0, 0, 0, 0, 31, 0, 23, 30, + 41, 42, 22, 28, 0, 39, 36, 0, 0, 27, + 37, 0, 0, 0, 0, 29, 0, 0, 0, 38, + 102, 0, 0, 0, 31, 0, 93, 30, 41, 42, + 22, 28, 0, 39, 36, 0, 0, 27, 37, 0, + 0, 0, 0, 29, 0, 94, 71, 38, 181, 0, + 0, 0, 31, 0, 23, 30, 41, 42, 22, 0, + 70, 0, 36, 0, 0, 0, 0, 0, 76, 77, + 75, 74, 78, 71, 0, 38, 0, 0, 0, 0, + 72, 73, 79, 80, 81, 82, 83, 70, 84, 0, + 0, 182, 0, 0, 0, 76, 77, 75, 74, 78, + 71, 0, 190, 0, 0, 0, 0, 72, 73, 79, + 80, 81, 82, 83, 70, 84, 0, 0, 166, 0, + 0, 0, 76, 77, 75, 74, 78, 71, 0, 0, + 0, 0, 0, 0, 72, 73, 79, 80, 81, 82, + 83, 70, 84, 0, 185, 0, 0, 0, 0, 76, + 77, 75, 74, 78, 71, 0, 0, 0, 0, 0, + 0, 72, 73, 79, 80, 81, 82, 83, 70, 84, + 0, 107, 0, 0, 0, 0, 76, 77, 75, 74, + 78, 71, 0, 105, 0, 0, 0, 0, 72, 73, + 79, 80, 81, 82, 83, 70, 84, 0, 0, 0, + 0, 0, 0, 76, 77, 75, 74, 78, 71, 0, + 0, 0, 0, 0, 0, 72, 73, 79, 80, 81, + 82, 83, 70, 84, 0, 0, 0, 0, 0, 0, + 76, 77, 75, 74, 78, 0, 0, 0, 0, 0, + 0, 0, 72, 73, 79, 80, 81, 82, 83, 0, + 84, 7, 10, 0, 0, 0, 0, 14, 15, 13, + 0, 16, 71, 0, 0, 6, 12, 0, 0, 0, + 11, 18, 0, 0, 0, 0, 0, 0, 17, 23, + 0, 0, 0, 22, 76, 77, 75, 74, 78, 0, + 0, 0, 0, 5, 0, 0, 72, 73, 79, 80, + 81, 82, 83, 0, 84, 76, 77, 75, 74, 78, + 0, 0, 0, 0, 0, 0, 0, 72, 73, 79, + 80, 81, 82, 83, 0, 84, } -var yyPact = []int{ - - -1000, -1000, 533, -5, -1000, -1000, 292, -1000, -17, 152, - -1000, 292, -1000, 292, 107, 97, 88, -1000, -1000, -1000, - 292, -1000, -1000, -29, 473, -1000, -1000, -1000, -1000, -1000, - -1000, 152, -1000, -1000, 292, 292, 292, 14, -1000, -1000, - 142, 292, 116, 292, 95, -1000, 82, 240, -1000, -1000, - 171, -1000, 446, 112, 419, -7, 17, 14, -24, -1000, - 81, -19, -1000, 104, -42, 292, 292, 292, 292, 292, - 292, 292, 292, 292, 292, 292, 292, 292, 292, 292, - 292, -1, -1, -1, -1000, -11, -1000, -37, -1000, -8, - 292, 473, -29, -1000, 152, 207, -1000, 55, -1000, -40, - -1000, -1000, 292, -1000, 292, 292, 34, -1000, 24, 19, - 14, 292, -1000, -1000, 473, 57, 493, 18, 18, 18, - 18, 18, 18, 18, 83, 83, -1, -1, -1, -1, - -44, -1000, -1000, -14, -1000, 266, -1000, -1000, 292, 180, - -1000, -1000, -1000, 160, 473, -1000, 338, 40, -1000, -1000, - -1000, -1000, -29, -1000, 157, 22, -1000, 473, -12, -1000, - 205, 292, -1000, 154, -1000, -1000, 292, -1000, -1000, 292, - 311, 151, -1000, 473, 146, 392, -1000, 292, -1000, -1000, - -1000, 144, 365, -1000, -1000, -1000, 140, -1000, + +var yyPact = [...]int16{ + -1000, -1000, 536, -5, -1000, -1000, 311, -1000, -4, -17, + -1000, 311, -1000, 311, 89, 84, 90, 83, 81, -1000, + -1000, -1000, 311, -1000, -1000, -36, 494, -1000, -1000, -1000, + -1000, -1000, -1000, -17, -1000, -1000, 311, 311, 311, 26, + -1000, -1000, 169, 311, 66, 311, 79, -1000, 72, 255, + -1000, -1000, 150, -1000, 467, 104, 440, 7, 17, 26, + -22, -1000, 71, -8, -1000, 94, -1000, 107, -46, 311, + 311, 311, 311, 311, 311, 311, 311, 311, 311, 311, + 311, 311, 311, 311, 311, 11, 11, 11, -1000, -6, + -1000, -39, -1000, 4, 311, 494, -36, -1000, -17, 220, + -1000, 32, -1000, -19, -1000, -1000, 311, -1000, 311, 311, + 67, -1000, 61, 59, 26, 311, -1000, -1000, -1000, 494, + 548, 569, 18, 18, 18, 18, 18, 18, 18, 44, + 44, 11, 11, 11, 11, -49, -1000, -1000, -28, -1000, + 283, -1000, -1000, 311, 190, -1000, -1000, -1000, 149, 494, + -1000, 359, 5, -1000, -1000, -1000, -1000, -36, -1000, 148, + 58, -1000, 494, 3, -1000, 123, 311, -1000, 147, -1000, + -1000, 311, -1000, -1000, 311, 332, 146, -1000, 494, 145, + 413, -1000, 311, -1000, -1000, -1000, 131, 386, -1000, -1000, + -1000, 119, -1000, } -var yyPgo = []int{ - 0, 190, 227, 2, 226, 223, 215, 210, 204, 203, - 118, 6, 3, 0, 22, 107, 168, 199, 4, 197, - 5, 195, 16, 193, 1, 182, +var yyPgo = [...]uint8{ + 0, 163, 182, 2, 180, 178, 177, 174, 173, 171, + 98, 7, 3, 0, 22, 93, 129, 169, 4, 168, + 5, 166, 16, 165, 1, 160, } -var yyR1 = []int{ +var yyR1 = [...]int8{ 0, 1, 1, 1, 2, 2, 2, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, - 4, 4, 5, 5, 6, 6, 6, 7, 7, 8, - 8, 9, 9, 10, 10, 10, 11, 11, 12, 12, - 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 4, 4, 4, 4, 5, 5, 6, 6, 6, 7, + 7, 8, 8, 9, 9, 10, 10, 10, 11, 11, + 12, 12, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, - 13, 13, 13, 13, 13, 13, 13, 14, 15, 15, - 15, 15, 17, 16, 16, 18, 18, 18, 18, 19, - 20, 20, 21, 21, 21, 22, 22, 23, 23, 23, - 24, 24, 24, 25, 25, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 14, + 15, 15, 15, 15, 17, 16, 16, 18, 18, 18, + 18, 19, 20, 20, 21, 21, 21, 22, 22, 23, + 23, 23, 24, 24, 24, 25, 25, } -var yyR2 = []int{ +var yyR2 = [...]int8{ 0, 1, 2, 3, 0, 2, 2, 1, 3, 1, 3, 5, 4, 6, 8, 9, 11, 7, 3, 4, - 4, 2, 0, 5, 1, 2, 1, 1, 3, 1, - 3, 1, 3, 1, 4, 3, 1, 3, 1, 3, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 2, 2, 2, 1, 1, 1, - 1, 3, 3, 2, 4, 2, 3, 1, 1, 2, - 5, 4, 1, 1, 3, 2, 3, 1, 3, 2, - 3, 5, 1, 1, 1, + 4, 2, 3, 2, 0, 5, 1, 2, 1, 1, + 3, 1, 3, 1, 3, 1, 4, 3, 1, 3, + 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 2, 2, 2, 1, + 1, 1, 1, 3, 3, 2, 4, 2, 3, 1, + 1, 2, 5, 4, 1, 1, 3, 2, 3, 1, + 3, 2, 3, 5, 1, 1, 1, } -var yyChk = []int{ - - -1000, -1, -2, -6, -4, 45, 19, 5, -9, -15, - 6, 24, 20, 13, 11, 12, 15, -10, -17, -16, - 35, 31, 45, -12, -13, 16, 10, 22, 32, 30, - -19, -15, -14, -22, 39, 17, 52, 12, -10, 33, - 34, 46, 47, 50, 49, -18, 48, 35, -22, -14, - -3, -1, -13, -3, -13, 31, -11, -7, -8, 31, - 12, -11, 31, -13, -16, 47, 18, 4, 36, 37, - 28, 27, 25, 26, 29, 38, 39, 40, 41, 42, - 44, -13, -13, -13, -20, 35, 54, -23, -24, 31, - 50, -13, -12, -10, -15, -13, 31, 31, 53, -12, - 9, 6, 23, 21, 46, 14, 47, -20, 48, 49, - 31, 46, 53, 53, -13, -13, -13, -13, -13, -13, + +var yyChk = [...]int16{ + -1000, -1, -2, -6, -4, 47, 19, 5, -9, -15, + 6, 24, 20, 13, 11, 12, 15, 32, 25, -10, + -17, -16, 37, 33, 47, -12, -13, 16, 10, 22, + 34, 31, -19, -15, -14, -22, 41, 17, 54, 12, + -10, 35, 36, 48, 49, 52, 51, -18, 50, 37, + -22, -14, -3, -1, -13, -3, -13, 33, -11, -7, + -8, 33, 12, -11, 33, 33, 33, -13, -16, 49, + 18, 4, 38, 39, 29, 28, 26, 27, 30, 40, + 41, 42, 43, 44, 46, -13, -13, -13, -20, 37, + 56, -23, -24, 33, 52, -13, -12, -10, -15, -13, + 33, 33, 55, -12, 9, 6, 23, 21, 48, 14, + 49, -20, 50, 51, 33, 48, 32, 55, 55, -13, -13, -13, -13, -13, -13, -13, -13, -13, -13, -13, - -21, 53, 30, -11, 54, -25, 47, 45, 46, -13, - 51, -18, 53, -3, -13, -3, -13, -12, 31, 31, - 31, -20, -12, 53, -3, 47, -24, -13, 51, 9, - -5, 47, 6, -3, 9, 30, 46, 9, 7, 8, - -13, -3, 9, -13, -3, -13, 6, 47, 9, 9, - 21, -3, -13, -3, 9, 6, -3, 9, + -13, -13, -13, -13, -13, -21, 55, 31, -11, 56, + -25, 49, 47, 48, -13, 53, -18, 55, -3, -13, + -3, -13, -12, 33, 33, 33, -20, -12, 55, -3, + 49, -24, -13, 53, 9, -5, 49, 6, -3, 9, + 31, 48, 9, 7, 8, -13, -3, 9, -13, -3, + -13, 6, 49, 9, 9, 21, -3, -13, -3, 9, + 6, -3, 9, } -var yyDef = []int{ - - 4, -2, 1, 2, 5, 6, 24, 26, 0, 9, - 4, 0, 4, 0, 0, 0, 0, -2, 69, 70, - 0, 33, 3, 25, 38, 40, 41, 42, 43, 44, - 45, 46, 47, 48, 0, 0, 0, 0, 68, 67, - 0, 0, 0, 0, 0, 73, 0, 0, 77, 78, - 0, 7, 0, 0, 0, 36, 0, 0, 27, 29, - 0, 21, 36, 0, 70, 0, 0, 0, 0, 0, + +var yyDef = [...]int8{ + 4, -2, 1, 2, 5, 6, 26, 28, 0, 9, + 4, 0, 4, 0, 0, 0, 0, 0, 0, -2, + 71, 72, 0, 35, 3, 27, 40, 42, 43, 44, + 45, 46, 47, 48, 49, 50, 0, 0, 0, 0, + 70, 69, 0, 0, 0, 0, 0, 75, 0, 0, + 79, 80, 0, 7, 0, 0, 0, 38, 0, 0, + 29, 31, 0, 21, 38, 0, 23, 0, 72, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 64, 65, 66, 79, 0, 85, 0, 87, 33, - 0, 92, 8, -2, 0, 0, 35, 0, 75, 0, - 10, 4, 0, 4, 0, 0, 0, 18, 0, 0, - 0, 0, 71, 72, 39, 49, 50, 51, 52, 53, - 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, - 0, 4, 82, 83, 86, 89, 93, 94, 0, 0, - 34, 74, 76, 0, 12, 22, 0, 0, 37, 28, - 30, 19, 20, 4, 0, 0, 88, 90, 0, 11, - 0, 0, 4, 0, 81, 84, 0, 13, 4, 0, - 0, 0, 80, 91, 0, 0, 4, 0, 17, 14, - 4, 0, 0, 23, 15, 4, 0, 16, + 0, 0, 0, 0, 0, 66, 67, 68, 81, 0, + 87, 0, 89, 35, 0, 94, 8, -2, 0, 0, + 37, 0, 77, 0, 10, 4, 0, 4, 0, 0, + 0, 18, 0, 0, 0, 0, 22, 73, 74, 41, + 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, + 61, 62, 63, 64, 65, 0, 4, 84, 85, 88, + 91, 95, 96, 0, 0, 36, 76, 78, 0, 12, + 24, 0, 0, 39, 30, 32, 19, 20, 4, 0, + 0, 90, 92, 0, 11, 0, 0, 4, 0, 83, + 86, 0, 13, 4, 0, 0, 0, 82, 93, 0, + 0, 4, 0, 17, 14, 4, 0, 0, 25, 15, + 4, 0, 16, } -var yyTok1 = []int{ +var yyTok1 = [...]int8{ 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 52, 3, 42, 3, 3, - 35, 53, 40, 38, 47, 39, 49, 41, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 48, 45, - 37, 46, 36, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 54, 3, 44, 3, 3, + 37, 55, 42, 40, 49, 41, 51, 43, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 50, 47, + 39, 48, 38, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 50, 3, 51, 44, 3, 3, 3, 3, 3, + 3, 52, 3, 53, 46, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 34, 3, 54, + 3, 3, 3, 36, 3, 56, } -var yyTok2 = []int{ +var yyTok2 = [...]int8{ 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, - 32, 33, 43, + 32, 33, 34, 35, 45, } -var yyTok3 = []int{ + +var yyTok3 = [...]int8{ 0, } +var yyErrorMessages = [...]struct { + state int + token int + msg string +}{} + //line yaccpar:1 /* parser for yacc output */ -var yyDebug = 0 +var ( + yyDebug = 0 + yyErrorVerbose = false +) type yyLexer interface { Lex(lval *yySymType) int Error(s string) } +type yyParser interface { + Parse(yyLexer) int + Lookahead() int +} + +type yyParserImpl struct { + lval yySymType + stack [yyInitialStackSize]yySymType + char int +} + +func (p *yyParserImpl) Lookahead() int { + return p.char +} + +func yyNewParser() yyParser { + return &yyParserImpl{} +} + const yyFlag = -1000 func yyTokname(c int) string { - // 4 is TOKSTART above - if c >= 4 && c-4 < len(yyToknames) { - if yyToknames[c-4] != "" { - return yyToknames[c-4] + if c >= 1 && c-1 < len(yyToknames) { + if yyToknames[c-1] != "" { + return yyToknames[c-1] } } return __yyfmt__.Sprintf("tok-%v", c) @@ -362,51 +414,127 @@ func yyStatname(s int) string { return __yyfmt__.Sprintf("state-%v", s) } -func yylex1(lex yyLexer, lval *yySymType) int { - c := 0 - char := lex.Lex(lval) +func yyErrorMessage(state, lookAhead int) string { + const TOKSTART = 4 + + if !yyErrorVerbose { + return "syntax error" + } + + for _, e := range yyErrorMessages { + if e.state == state && e.token == lookAhead { + return "syntax error: " + e.msg + } + } + + res := "syntax error: unexpected " + yyTokname(lookAhead) + + // To match Bison, suggest at most four expected tokens. + expected := make([]int, 0, 4) + + // Look for shiftable tokens. + base := int(yyPact[state]) + for tok := TOKSTART; tok-1 < len(yyToknames); tok++ { + if n := base + tok; n >= 0 && n < yyLast && int(yyChk[int(yyAct[n])]) == tok { + if len(expected) == cap(expected) { + return res + } + expected = append(expected, tok) + } + } + + if yyDef[state] == -2 { + i := 0 + for yyExca[i] != -1 || int(yyExca[i+1]) != state { + i += 2 + } + + // Look for tokens that we accept or reduce. + for i += 2; yyExca[i] >= 0; i += 2 { + tok := int(yyExca[i]) + if tok < TOKSTART || yyExca[i+1] == 0 { + continue + } + if len(expected) == cap(expected) { + return res + } + expected = append(expected, tok) + } + + // If the default action is to accept or reduce, give up. + if yyExca[i+1] != 0 { + return res + } + } + + for i, tok := range expected { + if i == 0 { + res += ", expecting " + } else { + res += " or " + } + res += yyTokname(tok) + } + return res +} + +func yylex1(lex yyLexer, lval *yySymType) (char, token int) { + token = 0 + char = lex.Lex(lval) if char <= 0 { - c = yyTok1[0] + token = int(yyTok1[0]) goto out } if char < len(yyTok1) { - c = yyTok1[char] + token = int(yyTok1[char]) goto out } if char >= yyPrivate { if char < yyPrivate+len(yyTok2) { - c = yyTok2[char-yyPrivate] + token = int(yyTok2[char-yyPrivate]) goto out } } for i := 0; i < len(yyTok3); i += 2 { - c = yyTok3[i+0] - if c == char { - c = yyTok3[i+1] + token = int(yyTok3[i+0]) + if token == char { + token = int(yyTok3[i+1]) goto out } } out: - if c == 0 { - c = yyTok2[1] /* unknown char */ + if token == 0 { + token = int(yyTok2[1]) /* unknown char */ } if yyDebug >= 3 { - __yyfmt__.Printf("lex %s(%d)\n", yyTokname(c), uint(char)) + __yyfmt__.Printf("lex %s(%d)\n", yyTokname(token), uint(char)) } - return c + return char, token } func yyParse(yylex yyLexer) int { + return yyNewParser().Parse(yylex) +} + +func (yyrcvr *yyParserImpl) Parse(yylex yyLexer) int { var yyn int - var yylval yySymType var yyVAL yySymType - yyS := make([]yySymType, yyMaxDepth) + var yyDollar []yySymType + _ = yyDollar // silence set and not used + yyS := yyrcvr.stack[:] Nerrs := 0 /* number of errors */ Errflag := 0 /* error recovery flag */ yystate := 0 - yychar := -1 + yyrcvr.char = -1 + yytoken := -1 // yyrcvr.char translated into internal numbering + defer func() { + // Make sure we report no lookahead when not parsing. + yystate = -1 + yyrcvr.char = -1 + yytoken = -1 + }() yyp := -1 goto yystack @@ -419,7 +547,7 @@ ret1: yystack: /* put a state and value onto the stack */ if yyDebug >= 4 { - __yyfmt__.Printf("char %v in %v\n", yyTokname(yychar), yyStatname(yystate)) + __yyfmt__.Printf("char %v in %v\n", yyTokname(yytoken), yyStatname(yystate)) } yyp++ @@ -432,21 +560,22 @@ yystack: yyS[yyp].yys = yystate yynewstate: - yyn = yyPact[yystate] + yyn = int(yyPact[yystate]) if yyn <= yyFlag { goto yydefault /* simple state */ } - if yychar < 0 { - yychar = yylex1(yylex, &yylval) + if yyrcvr.char < 0 { + yyrcvr.char, yytoken = yylex1(yylex, &yyrcvr.lval) } - yyn += yychar + yyn += yytoken if yyn < 0 || yyn >= yyLast { goto yydefault } - yyn = yyAct[yyn] - if yyChk[yyn] == yychar { /* valid shift */ - yychar = -1 - yyVAL = yylval + yyn = int(yyAct[yyn]) + if int(yyChk[yyn]) == yytoken { /* valid shift */ + yyrcvr.char = -1 + yytoken = -1 + yyVAL = yyrcvr.lval yystate = yyn if Errflag > 0 { Errflag-- @@ -456,27 +585,27 @@ yynewstate: yydefault: /* default state action */ - yyn = yyDef[yystate] + yyn = int(yyDef[yystate]) if yyn == -2 { - if yychar < 0 { - yychar = yylex1(yylex, &yylval) + if yyrcvr.char < 0 { + yyrcvr.char, yytoken = yylex1(yylex, &yyrcvr.lval) } /* look through exception table */ xi := 0 for { - if yyExca[xi+0] == -1 && yyExca[xi+1] == yystate { + if yyExca[xi+0] == -1 && int(yyExca[xi+1]) == yystate { break } xi += 2 } for xi += 2; ; xi += 2 { - yyn = yyExca[xi+0] - if yyn < 0 || yyn == yychar { + yyn = int(yyExca[xi+0]) + if yyn < 0 || yyn == yytoken { break } } - yyn = yyExca[xi+1] + yyn = int(yyExca[xi+1]) if yyn < 0 { goto ret0 } @@ -485,11 +614,11 @@ yydefault: /* error ... attempt to resume parsing */ switch Errflag { case 0: /* brand new error */ - yylex.Error("syntax error") + yylex.Error(yyErrorMessage(yystate, yytoken)) Nerrs++ if yyDebug >= 1 { __yyfmt__.Printf("%s", yyStatname(yystate)) - __yyfmt__.Printf(" saw %s\n", yyTokname(yychar)) + __yyfmt__.Printf(" saw %s\n", yyTokname(yytoken)) } fallthrough @@ -498,10 +627,10 @@ yydefault: /* find a state where "error" is a legal shift action */ for yyp >= 0 { - yyn = yyPact[yyS[yyp].yys] + yyErrCode + yyn = int(yyPact[yyS[yyp].yys]) + yyErrCode if yyn >= 0 && yyn < yyLast { - yystate = yyAct[yyn] /* simulate a shift of "error" */ - if yyChk[yystate] == yyErrCode { + yystate = int(yyAct[yyn]) /* simulate a shift of "error" */ + if int(yyChk[yystate]) == yyErrCode { goto yystack } } @@ -517,12 +646,13 @@ yydefault: case 3: /* no shift yet; clobber input char */ if yyDebug >= 2 { - __yyfmt__.Printf("error recovery discards %s\n", yyTokname(yychar)) + __yyfmt__.Printf("error recovery discards %s\n", yyTokname(yytoken)) } - if yychar == yyEofCode { + if yytoken == yyEofCode { goto ret1 } - yychar = -1 + yyrcvr.char = -1 + yytoken = -1 goto yynewstate /* try again in the same state */ } } @@ -536,599 +666,717 @@ yydefault: yypt := yyp _ = yypt // guard against "declared and not used" - yyp -= yyR2[yyn] + yyp -= int(yyR2[yyn]) + // yyp is now the index of $0. Perform the default action. Iff the + // reduced production is ε, $1 is possibly out of range. + if yyp+1 >= len(yyS) { + nyys := make([]yySymType, len(yyS)*2) + copy(nyys, yyS) + yyS = nyys + } yyVAL = yyS[yyp+1] /* consult goto table to find next state */ - yyn = yyR1[yyn] - yyg := yyPgo[yyn] + yyn = int(yyR1[yyn]) + yyg := int(yyPgo[yyn]) yyj := yyg + yyS[yyp].yys + 1 if yyj >= yyLast { - yystate = yyAct[yyg] + yystate = int(yyAct[yyg]) } else { - yystate = yyAct[yyj] - if yyChk[yystate] != -yyn { - yystate = yyAct[yyg] + yystate = int(yyAct[yyj]) + if int(yyChk[yystate]) != -yyn { + yystate = int(yyAct[yyg]) } } // dummy call; replaced with literal code switch yynt { case 1: - //line parser.go.y:73 + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:73 { - yyVAL.stmts = yyS[yypt-0].stmts + yyVAL.stmts = yyDollar[1].stmts if l, ok := yylex.(*Lexer); ok { l.Stmts = yyVAL.stmts } } case 2: - //line parser.go.y:79 + yyDollar = yyS[yypt-2 : yypt+1] +//line parser.go.y:79 { - yyVAL.stmts = append(yyS[yypt-1].stmts, yyS[yypt-0].stmt) + yyVAL.stmts = append(yyDollar[1].stmts, yyDollar[2].stmt) if l, ok := yylex.(*Lexer); ok { l.Stmts = yyVAL.stmts } } case 3: - //line parser.go.y:85 + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:85 { - yyVAL.stmts = append(yyS[yypt-2].stmts, yyS[yypt-1].stmt) + yyVAL.stmts = append(yyDollar[1].stmts, yyDollar[2].stmt) if l, ok := yylex.(*Lexer); ok { l.Stmts = yyVAL.stmts } } case 4: - //line parser.go.y:93 + yyDollar = yyS[yypt-0 : yypt+1] +//line parser.go.y:93 { yyVAL.stmts = []ast.Stmt{} } case 5: - //line parser.go.y:96 + yyDollar = yyS[yypt-2 : yypt+1] +//line parser.go.y:96 { - yyVAL.stmts = append(yyS[yypt-1].stmts, yyS[yypt-0].stmt) + yyVAL.stmts = append(yyDollar[1].stmts, yyDollar[2].stmt) } case 6: - //line parser.go.y:99 + yyDollar = yyS[yypt-2 : yypt+1] +//line parser.go.y:99 { - yyVAL.stmts = yyS[yypt-1].stmts + yyVAL.stmts = yyDollar[1].stmts } case 7: - //line parser.go.y:104 + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:104 { - yyVAL.stmts = yyS[yypt-0].stmts + yyVAL.stmts = yyDollar[1].stmts } case 8: - //line parser.go.y:109 + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:109 { - yyVAL.stmt = &ast.AssignStmt{Lhs: yyS[yypt-2].exprlist, Rhs: yyS[yypt-0].exprlist} - yyVAL.stmt.SetLine(yyS[yypt-2].exprlist[0].Line()) + yyVAL.stmt = &ast.AssignStmt{Lhs: yyDollar[1].exprlist, Rhs: yyDollar[3].exprlist} + yyVAL.stmt.SetLine(yyDollar[1].exprlist[0].Line()) } case 9: - //line parser.go.y:114 + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:114 { - if _, ok := yyS[yypt-0].expr.(*ast.FuncCallExpr); !ok { + if _, ok := yyDollar[1].expr.(*ast.FuncCallExpr); !ok { yylex.(*Lexer).Error("parse error") } else { - yyVAL.stmt = &ast.FuncCallStmt{Expr: yyS[yypt-0].expr} - yyVAL.stmt.SetLine(yyS[yypt-0].expr.Line()) + yyVAL.stmt = &ast.FuncCallStmt{Expr: yyDollar[1].expr} + yyVAL.stmt.SetLine(yyDollar[1].expr.Line()) } } case 10: - //line parser.go.y:122 + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:122 { - yyVAL.stmt = &ast.DoBlockStmt{Stmts: yyS[yypt-1].stmts} - yyVAL.stmt.SetLine(yyS[yypt-2].token.Pos.Line) - yyVAL.stmt.SetLastLine(yyS[yypt-0].token.Pos.Line) + yyVAL.stmt = &ast.DoBlockStmt{Stmts: yyDollar[2].stmts} + yyVAL.stmt.SetLine(yyDollar[1].token.Pos.Line) + yyVAL.stmt.SetLastLine(yyDollar[3].token.Pos.Line) } case 11: - //line parser.go.y:127 + yyDollar = yyS[yypt-5 : yypt+1] +//line parser.go.y:127 { - yyVAL.stmt = &ast.WhileStmt{Condition: yyS[yypt-3].expr, Stmts: yyS[yypt-1].stmts} - yyVAL.stmt.SetLine(yyS[yypt-4].token.Pos.Line) - yyVAL.stmt.SetLastLine(yyS[yypt-0].token.Pos.Line) + yyVAL.stmt = &ast.WhileStmt{Condition: yyDollar[2].expr, Stmts: yyDollar[4].stmts} + yyVAL.stmt.SetLine(yyDollar[1].token.Pos.Line) + yyVAL.stmt.SetLastLine(yyDollar[5].token.Pos.Line) } case 12: - //line parser.go.y:132 + yyDollar = yyS[yypt-4 : yypt+1] +//line parser.go.y:132 { - yyVAL.stmt = &ast.RepeatStmt{Condition: yyS[yypt-0].expr, Stmts: yyS[yypt-2].stmts} - yyVAL.stmt.SetLine(yyS[yypt-3].token.Pos.Line) - yyVAL.stmt.SetLastLine(yyS[yypt-0].expr.Line()) + yyVAL.stmt = &ast.RepeatStmt{Condition: yyDollar[4].expr, Stmts: yyDollar[2].stmts} + yyVAL.stmt.SetLine(yyDollar[1].token.Pos.Line) + yyVAL.stmt.SetLastLine(yyDollar[4].expr.Line()) } case 13: - //line parser.go.y:137 + yyDollar = yyS[yypt-6 : yypt+1] +//line parser.go.y:137 { - yyVAL.stmt = &ast.IfStmt{Condition: yyS[yypt-4].expr, Then: yyS[yypt-2].stmts} + yyVAL.stmt = &ast.IfStmt{Condition: yyDollar[2].expr, Then: yyDollar[4].stmts} cur := yyVAL.stmt - for _, elseif := range yyS[yypt-1].stmts { + for _, elseif := range yyDollar[5].stmts { cur.(*ast.IfStmt).Else = []ast.Stmt{elseif} cur = elseif } - yyVAL.stmt.SetLine(yyS[yypt-5].token.Pos.Line) - yyVAL.stmt.SetLastLine(yyS[yypt-0].token.Pos.Line) + yyVAL.stmt.SetLine(yyDollar[1].token.Pos.Line) + yyVAL.stmt.SetLastLine(yyDollar[6].token.Pos.Line) } case 14: - //line parser.go.y:147 + yyDollar = yyS[yypt-8 : yypt+1] +//line parser.go.y:147 { - yyVAL.stmt = &ast.IfStmt{Condition: yyS[yypt-6].expr, Then: yyS[yypt-4].stmts} + yyVAL.stmt = &ast.IfStmt{Condition: yyDollar[2].expr, Then: yyDollar[4].stmts} cur := yyVAL.stmt - for _, elseif := range yyS[yypt-3].stmts { + for _, elseif := range yyDollar[5].stmts { cur.(*ast.IfStmt).Else = []ast.Stmt{elseif} cur = elseif } - cur.(*ast.IfStmt).Else = yyS[yypt-1].stmts - yyVAL.stmt.SetLine(yyS[yypt-7].token.Pos.Line) - yyVAL.stmt.SetLastLine(yyS[yypt-0].token.Pos.Line) + cur.(*ast.IfStmt).Else = yyDollar[7].stmts + yyVAL.stmt.SetLine(yyDollar[1].token.Pos.Line) + yyVAL.stmt.SetLastLine(yyDollar[8].token.Pos.Line) } case 15: - //line parser.go.y:158 + yyDollar = yyS[yypt-9 : yypt+1] +//line parser.go.y:158 { - yyVAL.stmt = &ast.NumberForStmt{Name: yyS[yypt-7].token.Str, Init: yyS[yypt-5].expr, Limit: yyS[yypt-3].expr, Stmts: yyS[yypt-1].stmts} - yyVAL.stmt.SetLine(yyS[yypt-8].token.Pos.Line) - yyVAL.stmt.SetLastLine(yyS[yypt-0].token.Pos.Line) + yyVAL.stmt = &ast.NumberForStmt{Name: yyDollar[2].token.Str, Init: yyDollar[4].expr, Limit: yyDollar[6].expr, Stmts: yyDollar[8].stmts} + yyVAL.stmt.SetLine(yyDollar[1].token.Pos.Line) + yyVAL.stmt.SetLastLine(yyDollar[9].token.Pos.Line) } case 16: - //line parser.go.y:163 + yyDollar = yyS[yypt-11 : yypt+1] +//line parser.go.y:163 { - yyVAL.stmt = &ast.NumberForStmt{Name: yyS[yypt-9].token.Str, Init: yyS[yypt-7].expr, Limit: yyS[yypt-5].expr, Step: yyS[yypt-3].expr, Stmts: yyS[yypt-1].stmts} - yyVAL.stmt.SetLine(yyS[yypt-10].token.Pos.Line) - yyVAL.stmt.SetLastLine(yyS[yypt-0].token.Pos.Line) + yyVAL.stmt = &ast.NumberForStmt{Name: yyDollar[2].token.Str, Init: yyDollar[4].expr, Limit: yyDollar[6].expr, Step: yyDollar[8].expr, Stmts: yyDollar[10].stmts} + yyVAL.stmt.SetLine(yyDollar[1].token.Pos.Line) + yyVAL.stmt.SetLastLine(yyDollar[11].token.Pos.Line) } case 17: - //line parser.go.y:168 + yyDollar = yyS[yypt-7 : yypt+1] +//line parser.go.y:168 { - yyVAL.stmt = &ast.GenericForStmt{Names: yyS[yypt-5].namelist, Exprs: yyS[yypt-3].exprlist, Stmts: yyS[yypt-1].stmts} - yyVAL.stmt.SetLine(yyS[yypt-6].token.Pos.Line) - yyVAL.stmt.SetLastLine(yyS[yypt-0].token.Pos.Line) + yyVAL.stmt = &ast.GenericForStmt{Names: yyDollar[2].namelist, Exprs: yyDollar[4].exprlist, Stmts: yyDollar[6].stmts} + yyVAL.stmt.SetLine(yyDollar[1].token.Pos.Line) + yyVAL.stmt.SetLastLine(yyDollar[7].token.Pos.Line) } case 18: - //line parser.go.y:173 + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:173 { - yyVAL.stmt = &ast.FuncDefStmt{Name: yyS[yypt-1].funcname, Func: yyS[yypt-0].funcexpr} - yyVAL.stmt.SetLine(yyS[yypt-2].token.Pos.Line) - yyVAL.stmt.SetLastLine(yyS[yypt-0].funcexpr.LastLine()) + yyVAL.stmt = &ast.FuncDefStmt{Name: yyDollar[2].funcname, Func: yyDollar[3].funcexpr} + yyVAL.stmt.SetLine(yyDollar[1].token.Pos.Line) + yyVAL.stmt.SetLastLine(yyDollar[3].funcexpr.LastLine()) } case 19: - //line parser.go.y:178 + yyDollar = yyS[yypt-4 : yypt+1] +//line parser.go.y:178 { - yyVAL.stmt = &ast.LocalAssignStmt{Names: []string{yyS[yypt-1].token.Str}, Exprs: []ast.Expr{yyS[yypt-0].funcexpr}} - yyVAL.stmt.SetLine(yyS[yypt-3].token.Pos.Line) - yyVAL.stmt.SetLastLine(yyS[yypt-0].funcexpr.LastLine()) + yyVAL.stmt = &ast.LocalAssignStmt{Names: []string{yyDollar[3].token.Str}, Exprs: []ast.Expr{yyDollar[4].funcexpr}} + yyVAL.stmt.SetLine(yyDollar[1].token.Pos.Line) + yyVAL.stmt.SetLastLine(yyDollar[4].funcexpr.LastLine()) } case 20: - //line parser.go.y:183 + yyDollar = yyS[yypt-4 : yypt+1] +//line parser.go.y:183 { - yyVAL.stmt = &ast.LocalAssignStmt{Names: yyS[yypt-2].namelist, Exprs: yyS[yypt-0].exprlist} - yyVAL.stmt.SetLine(yyS[yypt-3].token.Pos.Line) + yyVAL.stmt = &ast.LocalAssignStmt{Names: yyDollar[2].namelist, Exprs: yyDollar[4].exprlist} + yyVAL.stmt.SetLine(yyDollar[1].token.Pos.Line) } case 21: - //line parser.go.y:187 + yyDollar = yyS[yypt-2 : yypt+1] +//line parser.go.y:187 { - yyVAL.stmt = &ast.LocalAssignStmt{Names: yyS[yypt-0].namelist, Exprs: []ast.Expr{}} - yyVAL.stmt.SetLine(yyS[yypt-1].token.Pos.Line) + yyVAL.stmt = &ast.LocalAssignStmt{Names: yyDollar[2].namelist, Exprs: []ast.Expr{}} + yyVAL.stmt.SetLine(yyDollar[1].token.Pos.Line) } case 22: - //line parser.go.y:193 + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:191 { - yyVAL.stmts = []ast.Stmt{} + yyVAL.stmt = &ast.LabelStmt{Name: yyDollar[2].token.Str} + yyVAL.stmt.SetLine(yyDollar[1].token.Pos.Line) } case 23: - //line parser.go.y:196 + yyDollar = yyS[yypt-2 : yypt+1] +//line parser.go.y:195 { - yyVAL.stmts = append(yyS[yypt-4].stmts, &ast.IfStmt{Condition: yyS[yypt-2].expr, Then: yyS[yypt-0].stmts}) - yyVAL.stmts[len(yyVAL.stmts)-1].SetLine(yyS[yypt-3].token.Pos.Line) + yyVAL.stmt = &ast.GotoStmt{Label: yyDollar[2].token.Str} + yyVAL.stmt.SetLine(yyDollar[1].token.Pos.Line) } case 24: - //line parser.go.y:202 + yyDollar = yyS[yypt-0 : yypt+1] +//line parser.go.y:201 { - yyVAL.stmt = &ast.ReturnStmt{Exprs: nil} - yyVAL.stmt.SetLine(yyS[yypt-0].token.Pos.Line) + yyVAL.stmts = []ast.Stmt{} } case 25: - //line parser.go.y:206 + yyDollar = yyS[yypt-5 : yypt+1] +//line parser.go.y:204 { - yyVAL.stmt = &ast.ReturnStmt{Exprs: yyS[yypt-0].exprlist} - yyVAL.stmt.SetLine(yyS[yypt-1].token.Pos.Line) + yyVAL.stmts = append(yyDollar[1].stmts, &ast.IfStmt{Condition: yyDollar[3].expr, Then: yyDollar[5].stmts}) + yyVAL.stmts[len(yyVAL.stmts)-1].SetLine(yyDollar[2].token.Pos.Line) } case 26: - //line parser.go.y:210 + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:210 { - yyVAL.stmt = &ast.BreakStmt{} - yyVAL.stmt.SetLine(yyS[yypt-0].token.Pos.Line) + yyVAL.stmt = &ast.ReturnStmt{Exprs: nil} + yyVAL.stmt.SetLine(yyDollar[1].token.Pos.Line) } case 27: - //line parser.go.y:216 + yyDollar = yyS[yypt-2 : yypt+1] +//line parser.go.y:214 { - yyVAL.funcname = yyS[yypt-0].funcname + yyVAL.stmt = &ast.ReturnStmt{Exprs: yyDollar[2].exprlist} + yyVAL.stmt.SetLine(yyDollar[1].token.Pos.Line) } case 28: - //line parser.go.y:219 + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:218 { - yyVAL.funcname = &ast.FuncName{Func: nil, Receiver: yyS[yypt-2].funcname.Func, Method: yyS[yypt-0].token.Str} + yyVAL.stmt = &ast.BreakStmt{} + yyVAL.stmt.SetLine(yyDollar[1].token.Pos.Line) } case 29: - //line parser.go.y:224 + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:224 { - yyVAL.funcname = &ast.FuncName{Func: &ast.IdentExpr{Value: yyS[yypt-0].token.Str}} - yyVAL.funcname.Func.SetLine(yyS[yypt-0].token.Pos.Line) + yyVAL.funcname = yyDollar[1].funcname } case 30: - //line parser.go.y:228 + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:227 { - key := &ast.StringExpr{Value: yyS[yypt-0].token.Str} - key.SetLine(yyS[yypt-0].token.Pos.Line) - fn := &ast.AttrGetExpr{Object: yyS[yypt-2].funcname.Func, Key: key} - fn.SetLine(yyS[yypt-0].token.Pos.Line) - yyVAL.funcname = &ast.FuncName{Func: fn} + yyVAL.funcname = &ast.FuncName{Func: nil, Receiver: yyDollar[1].funcname.Func, Method: yyDollar[3].token.Str} } case 31: - //line parser.go.y:237 + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:232 { - yyVAL.exprlist = []ast.Expr{yyS[yypt-0].expr} + yyVAL.funcname = &ast.FuncName{Func: &ast.IdentExpr{Value: yyDollar[1].token.Str}} + yyVAL.funcname.Func.SetLine(yyDollar[1].token.Pos.Line) } case 32: - //line parser.go.y:240 + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:236 { - yyVAL.exprlist = append(yyS[yypt-2].exprlist, yyS[yypt-0].expr) + key := &ast.StringExpr{Value: yyDollar[3].token.Str} + key.SetLine(yyDollar[3].token.Pos.Line) + fn := &ast.AttrGetExpr{Object: yyDollar[1].funcname.Func, Key: key} + fn.SetLine(yyDollar[3].token.Pos.Line) + yyVAL.funcname = &ast.FuncName{Func: fn} } case 33: - //line parser.go.y:245 + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:245 { - yyVAL.expr = &ast.IdentExpr{Value: yyS[yypt-0].token.Str} - yyVAL.expr.SetLine(yyS[yypt-0].token.Pos.Line) + yyVAL.exprlist = []ast.Expr{yyDollar[1].expr} } case 34: - //line parser.go.y:249 + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:248 { - yyVAL.expr = &ast.AttrGetExpr{Object: yyS[yypt-3].expr, Key: yyS[yypt-1].expr} - yyVAL.expr.SetLine(yyS[yypt-3].expr.Line()) + yyVAL.exprlist = append(yyDollar[1].exprlist, yyDollar[3].expr) } case 35: - //line parser.go.y:253 + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:253 { - key := &ast.StringExpr{Value: yyS[yypt-0].token.Str} - key.SetLine(yyS[yypt-0].token.Pos.Line) - yyVAL.expr = &ast.AttrGetExpr{Object: yyS[yypt-2].expr, Key: key} - yyVAL.expr.SetLine(yyS[yypt-2].expr.Line()) + yyVAL.expr = &ast.IdentExpr{Value: yyDollar[1].token.Str} + yyVAL.expr.SetLine(yyDollar[1].token.Pos.Line) } case 36: - //line parser.go.y:261 + yyDollar = yyS[yypt-4 : yypt+1] +//line parser.go.y:257 { - yyVAL.namelist = []string{yyS[yypt-0].token.Str} + yyVAL.expr = &ast.AttrGetExpr{Object: yyDollar[1].expr, Key: yyDollar[3].expr} + yyVAL.expr.SetLine(yyDollar[1].expr.Line()) } case 37: - //line parser.go.y:264 + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:261 { - yyVAL.namelist = append(yyS[yypt-2].namelist, yyS[yypt-0].token.Str) + key := &ast.StringExpr{Value: yyDollar[3].token.Str} + key.SetLine(yyDollar[3].token.Pos.Line) + yyVAL.expr = &ast.AttrGetExpr{Object: yyDollar[1].expr, Key: key} + yyVAL.expr.SetLine(yyDollar[1].expr.Line()) } case 38: - //line parser.go.y:269 + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:269 { - yyVAL.exprlist = []ast.Expr{yyS[yypt-0].expr} + yyVAL.namelist = []string{yyDollar[1].token.Str} } case 39: - //line parser.go.y:272 + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:272 { - yyVAL.exprlist = append(yyS[yypt-2].exprlist, yyS[yypt-0].expr) + yyVAL.namelist = append(yyDollar[1].namelist, yyDollar[3].token.Str) } case 40: - //line parser.go.y:277 + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:277 { - yyVAL.expr = &ast.NilExpr{} - yyVAL.expr.SetLine(yyS[yypt-0].token.Pos.Line) + yyVAL.exprlist = []ast.Expr{yyDollar[1].expr} } case 41: - //line parser.go.y:281 + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:280 { - yyVAL.expr = &ast.FalseExpr{} - yyVAL.expr.SetLine(yyS[yypt-0].token.Pos.Line) + yyVAL.exprlist = append(yyDollar[1].exprlist, yyDollar[3].expr) } case 42: - //line parser.go.y:285 + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:285 { - yyVAL.expr = &ast.TrueExpr{} - yyVAL.expr.SetLine(yyS[yypt-0].token.Pos.Line) + yyVAL.expr = &ast.NilExpr{} + yyVAL.expr.SetLine(yyDollar[1].token.Pos.Line) } case 43: - //line parser.go.y:289 + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:289 { - yyVAL.expr = &ast.NumberExpr{Value: yyS[yypt-0].token.Str} - yyVAL.expr.SetLine(yyS[yypt-0].token.Pos.Line) + yyVAL.expr = &ast.FalseExpr{} + yyVAL.expr.SetLine(yyDollar[1].token.Pos.Line) } case 44: - //line parser.go.y:293 + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:293 { - yyVAL.expr = &ast.Comma3Expr{} - yyVAL.expr.SetLine(yyS[yypt-0].token.Pos.Line) + yyVAL.expr = &ast.TrueExpr{} + yyVAL.expr.SetLine(yyDollar[1].token.Pos.Line) } case 45: - //line parser.go.y:297 + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:297 { - yyVAL.expr = yyS[yypt-0].expr + yyVAL.expr = &ast.NumberExpr{Value: yyDollar[1].token.Str} + yyVAL.expr.SetLine(yyDollar[1].token.Pos.Line) } case 46: - //line parser.go.y:300 + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:301 { - yyVAL.expr = yyS[yypt-0].expr + yyVAL.expr = &ast.Comma3Expr{} + yyVAL.expr.SetLine(yyDollar[1].token.Pos.Line) } case 47: - //line parser.go.y:303 + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:305 { - yyVAL.expr = yyS[yypt-0].expr + yyVAL.expr = yyDollar[1].expr } case 48: - //line parser.go.y:306 + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:308 { - yyVAL.expr = yyS[yypt-0].expr + yyVAL.expr = yyDollar[1].expr } case 49: - //line parser.go.y:309 + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:311 { - yyVAL.expr = &ast.LogicalOpExpr{Lhs: yyS[yypt-2].expr, Operator: "or", Rhs: yyS[yypt-0].expr} - yyVAL.expr.SetLine(yyS[yypt-2].expr.Line()) + yyVAL.expr = yyDollar[1].expr } case 50: - //line parser.go.y:313 + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:314 { - yyVAL.expr = &ast.LogicalOpExpr{Lhs: yyS[yypt-2].expr, Operator: "and", Rhs: yyS[yypt-0].expr} - yyVAL.expr.SetLine(yyS[yypt-2].expr.Line()) + yyVAL.expr = yyDollar[1].expr } case 51: - //line parser.go.y:317 + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:317 { - yyVAL.expr = &ast.RelationalOpExpr{Lhs: yyS[yypt-2].expr, Operator: ">", Rhs: yyS[yypt-0].expr} - yyVAL.expr.SetLine(yyS[yypt-2].expr.Line()) + yyVAL.expr = &ast.LogicalOpExpr{Lhs: yyDollar[1].expr, Operator: "or", Rhs: yyDollar[3].expr} + yyVAL.expr.SetLine(yyDollar[1].expr.Line()) } case 52: - //line parser.go.y:321 + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:321 { - yyVAL.expr = &ast.RelationalOpExpr{Lhs: yyS[yypt-2].expr, Operator: "<", Rhs: yyS[yypt-0].expr} - yyVAL.expr.SetLine(yyS[yypt-2].expr.Line()) + yyVAL.expr = &ast.LogicalOpExpr{Lhs: yyDollar[1].expr, Operator: "and", Rhs: yyDollar[3].expr} + yyVAL.expr.SetLine(yyDollar[1].expr.Line()) } case 53: - //line parser.go.y:325 + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:325 { - yyVAL.expr = &ast.RelationalOpExpr{Lhs: yyS[yypt-2].expr, Operator: ">=", Rhs: yyS[yypt-0].expr} - yyVAL.expr.SetLine(yyS[yypt-2].expr.Line()) + yyVAL.expr = &ast.RelationalOpExpr{Lhs: yyDollar[1].expr, Operator: ">", Rhs: yyDollar[3].expr} + yyVAL.expr.SetLine(yyDollar[1].expr.Line()) } case 54: - //line parser.go.y:329 + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:329 { - yyVAL.expr = &ast.RelationalOpExpr{Lhs: yyS[yypt-2].expr, Operator: "<=", Rhs: yyS[yypt-0].expr} - yyVAL.expr.SetLine(yyS[yypt-2].expr.Line()) + yyVAL.expr = &ast.RelationalOpExpr{Lhs: yyDollar[1].expr, Operator: "<", Rhs: yyDollar[3].expr} + yyVAL.expr.SetLine(yyDollar[1].expr.Line()) } case 55: - //line parser.go.y:333 + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:333 { - yyVAL.expr = &ast.RelationalOpExpr{Lhs: yyS[yypt-2].expr, Operator: "==", Rhs: yyS[yypt-0].expr} - yyVAL.expr.SetLine(yyS[yypt-2].expr.Line()) + yyVAL.expr = &ast.RelationalOpExpr{Lhs: yyDollar[1].expr, Operator: ">=", Rhs: yyDollar[3].expr} + yyVAL.expr.SetLine(yyDollar[1].expr.Line()) } case 56: - //line parser.go.y:337 + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:337 { - yyVAL.expr = &ast.RelationalOpExpr{Lhs: yyS[yypt-2].expr, Operator: "~=", Rhs: yyS[yypt-0].expr} - yyVAL.expr.SetLine(yyS[yypt-2].expr.Line()) + yyVAL.expr = &ast.RelationalOpExpr{Lhs: yyDollar[1].expr, Operator: "<=", Rhs: yyDollar[3].expr} + yyVAL.expr.SetLine(yyDollar[1].expr.Line()) } case 57: - //line parser.go.y:341 + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:341 { - yyVAL.expr = &ast.StringConcatOpExpr{Lhs: yyS[yypt-2].expr, Rhs: yyS[yypt-0].expr} - yyVAL.expr.SetLine(yyS[yypt-2].expr.Line()) + yyVAL.expr = &ast.RelationalOpExpr{Lhs: yyDollar[1].expr, Operator: "==", Rhs: yyDollar[3].expr} + yyVAL.expr.SetLine(yyDollar[1].expr.Line()) } case 58: - //line parser.go.y:345 + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:345 { - yyVAL.expr = &ast.ArithmeticOpExpr{Lhs: yyS[yypt-2].expr, Operator: "+", Rhs: yyS[yypt-0].expr} - yyVAL.expr.SetLine(yyS[yypt-2].expr.Line()) + yyVAL.expr = &ast.RelationalOpExpr{Lhs: yyDollar[1].expr, Operator: "~=", Rhs: yyDollar[3].expr} + yyVAL.expr.SetLine(yyDollar[1].expr.Line()) } case 59: - //line parser.go.y:349 + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:349 { - yyVAL.expr = &ast.ArithmeticOpExpr{Lhs: yyS[yypt-2].expr, Operator: "-", Rhs: yyS[yypt-0].expr} - yyVAL.expr.SetLine(yyS[yypt-2].expr.Line()) + yyVAL.expr = &ast.StringConcatOpExpr{Lhs: yyDollar[1].expr, Rhs: yyDollar[3].expr} + yyVAL.expr.SetLine(yyDollar[1].expr.Line()) } case 60: - //line parser.go.y:353 + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:353 { - yyVAL.expr = &ast.ArithmeticOpExpr{Lhs: yyS[yypt-2].expr, Operator: "*", Rhs: yyS[yypt-0].expr} - yyVAL.expr.SetLine(yyS[yypt-2].expr.Line()) + yyVAL.expr = &ast.ArithmeticOpExpr{Lhs: yyDollar[1].expr, Operator: "+", Rhs: yyDollar[3].expr} + yyVAL.expr.SetLine(yyDollar[1].expr.Line()) } case 61: - //line parser.go.y:357 + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:357 { - yyVAL.expr = &ast.ArithmeticOpExpr{Lhs: yyS[yypt-2].expr, Operator: "/", Rhs: yyS[yypt-0].expr} - yyVAL.expr.SetLine(yyS[yypt-2].expr.Line()) + yyVAL.expr = &ast.ArithmeticOpExpr{Lhs: yyDollar[1].expr, Operator: "-", Rhs: yyDollar[3].expr} + yyVAL.expr.SetLine(yyDollar[1].expr.Line()) } case 62: - //line parser.go.y:361 + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:361 { - yyVAL.expr = &ast.ArithmeticOpExpr{Lhs: yyS[yypt-2].expr, Operator: "%", Rhs: yyS[yypt-0].expr} - yyVAL.expr.SetLine(yyS[yypt-2].expr.Line()) + yyVAL.expr = &ast.ArithmeticOpExpr{Lhs: yyDollar[1].expr, Operator: "*", Rhs: yyDollar[3].expr} + yyVAL.expr.SetLine(yyDollar[1].expr.Line()) } case 63: - //line parser.go.y:365 + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:365 { - yyVAL.expr = &ast.ArithmeticOpExpr{Lhs: yyS[yypt-2].expr, Operator: "^", Rhs: yyS[yypt-0].expr} - yyVAL.expr.SetLine(yyS[yypt-2].expr.Line()) + yyVAL.expr = &ast.ArithmeticOpExpr{Lhs: yyDollar[1].expr, Operator: "/", Rhs: yyDollar[3].expr} + yyVAL.expr.SetLine(yyDollar[1].expr.Line()) } case 64: - //line parser.go.y:369 + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:369 { - yyVAL.expr = &ast.UnaryMinusOpExpr{Expr: yyS[yypt-0].expr} - yyVAL.expr.SetLine(yyS[yypt-0].expr.Line()) + yyVAL.expr = &ast.ArithmeticOpExpr{Lhs: yyDollar[1].expr, Operator: "%", Rhs: yyDollar[3].expr} + yyVAL.expr.SetLine(yyDollar[1].expr.Line()) } case 65: - //line parser.go.y:373 + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:373 { - yyVAL.expr = &ast.UnaryNotOpExpr{Expr: yyS[yypt-0].expr} - yyVAL.expr.SetLine(yyS[yypt-0].expr.Line()) + yyVAL.expr = &ast.ArithmeticOpExpr{Lhs: yyDollar[1].expr, Operator: "^", Rhs: yyDollar[3].expr} + yyVAL.expr.SetLine(yyDollar[1].expr.Line()) } case 66: - //line parser.go.y:377 + yyDollar = yyS[yypt-2 : yypt+1] +//line parser.go.y:377 { - yyVAL.expr = &ast.UnaryLenOpExpr{Expr: yyS[yypt-0].expr} - yyVAL.expr.SetLine(yyS[yypt-0].expr.Line()) + yyVAL.expr = &ast.UnaryMinusOpExpr{Expr: yyDollar[2].expr} + yyVAL.expr.SetLine(yyDollar[2].expr.Line()) } case 67: - //line parser.go.y:383 + yyDollar = yyS[yypt-2 : yypt+1] +//line parser.go.y:381 { - yyVAL.expr = &ast.StringExpr{Value: yyS[yypt-0].token.Str} - yyVAL.expr.SetLine(yyS[yypt-0].token.Pos.Line) + yyVAL.expr = &ast.UnaryNotOpExpr{Expr: yyDollar[2].expr} + yyVAL.expr.SetLine(yyDollar[2].expr.Line()) } case 68: - //line parser.go.y:389 + yyDollar = yyS[yypt-2 : yypt+1] +//line parser.go.y:385 { - yyVAL.expr = yyS[yypt-0].expr + yyVAL.expr = &ast.UnaryLenOpExpr{Expr: yyDollar[2].expr} + yyVAL.expr.SetLine(yyDollar[2].expr.Line()) } case 69: - //line parser.go.y:392 + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:391 { - yyVAL.expr = yyS[yypt-0].expr + yyVAL.expr = &ast.StringExpr{Value: yyDollar[1].token.Str} + yyVAL.expr.SetLine(yyDollar[1].token.Pos.Line) } case 70: - //line parser.go.y:395 + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:397 { - yyVAL.expr = yyS[yypt-0].expr + yyVAL.expr = yyDollar[1].expr } case 71: - //line parser.go.y:398 + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:400 { - yyVAL.expr = yyS[yypt-1].expr - yyVAL.expr.SetLine(yyS[yypt-2].token.Pos.Line) + yyVAL.expr = yyDollar[1].expr } case 72: - //line parser.go.y:404 + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:403 { - yyS[yypt-1].expr.(*ast.FuncCallExpr).AdjustRet = true - yyVAL.expr = yyS[yypt-1].expr + yyVAL.expr = yyDollar[1].expr } case 73: - //line parser.go.y:410 + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:406 { - yyVAL.expr = &ast.FuncCallExpr{Func: yyS[yypt-1].expr, Args: yyS[yypt-0].exprlist} - yyVAL.expr.SetLine(yyS[yypt-1].expr.Line()) + if ex, ok := yyDollar[2].expr.(*ast.Comma3Expr); ok { + ex.AdjustRet = true + } + yyVAL.expr = yyDollar[2].expr + yyVAL.expr.SetLine(yyDollar[1].token.Pos.Line) } case 74: - //line parser.go.y:414 + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:415 { - yyVAL.expr = &ast.FuncCallExpr{Method: yyS[yypt-1].token.Str, Receiver: yyS[yypt-3].expr, Args: yyS[yypt-0].exprlist} - yyVAL.expr.SetLine(yyS[yypt-3].expr.Line()) + yyDollar[2].expr.(*ast.FuncCallExpr).AdjustRet = true + yyVAL.expr = yyDollar[2].expr } case 75: - //line parser.go.y:420 + yyDollar = yyS[yypt-2 : yypt+1] +//line parser.go.y:421 { - if yylex.(*Lexer).PNewLine { - yylex.(*Lexer).TokenError(yyS[yypt-1].token, "ambiguous syntax (function call x new statement)") - } - yyVAL.exprlist = []ast.Expr{} + yyVAL.expr = &ast.FuncCallExpr{Func: yyDollar[1].expr, Args: yyDollar[2].exprlist} + yyVAL.expr.SetLine(yyDollar[1].expr.Line()) } case 76: - //line parser.go.y:426 + yyDollar = yyS[yypt-4 : yypt+1] +//line parser.go.y:425 { - if yylex.(*Lexer).PNewLine { - yylex.(*Lexer).TokenError(yyS[yypt-2].token, "ambiguous syntax (function call x new statement)") - } - yyVAL.exprlist = yyS[yypt-1].exprlist + yyVAL.expr = &ast.FuncCallExpr{Method: yyDollar[3].token.Str, Receiver: yyDollar[1].expr, Args: yyDollar[4].exprlist} + yyVAL.expr.SetLine(yyDollar[1].expr.Line()) } case 77: - //line parser.go.y:432 + yyDollar = yyS[yypt-2 : yypt+1] +//line parser.go.y:431 { - yyVAL.exprlist = []ast.Expr{yyS[yypt-0].expr} + if yylex.(*Lexer).PNewLine { + yylex.(*Lexer).TokenError(yyDollar[1].token, "ambiguous syntax (function call x new statement)") + } + yyVAL.exprlist = []ast.Expr{} } case 78: - //line parser.go.y:435 + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:437 { - yyVAL.exprlist = []ast.Expr{yyS[yypt-0].expr} + if yylex.(*Lexer).PNewLine { + yylex.(*Lexer).TokenError(yyDollar[1].token, "ambiguous syntax (function call x new statement)") + } + yyVAL.exprlist = yyDollar[2].exprlist } case 79: - //line parser.go.y:440 + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:443 { - yyVAL.expr = &ast.FunctionExpr{ParList: yyS[yypt-0].funcexpr.ParList, Stmts: yyS[yypt-0].funcexpr.Stmts} - yyVAL.expr.SetLine(yyS[yypt-1].token.Pos.Line) - yyVAL.expr.SetLastLine(yyS[yypt-0].funcexpr.LastLine()) + yyVAL.exprlist = []ast.Expr{yyDollar[1].expr} } case 80: - //line parser.go.y:447 + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:446 { - yyVAL.funcexpr = &ast.FunctionExpr{ParList: yyS[yypt-3].parlist, Stmts: yyS[yypt-1].stmts} - yyVAL.funcexpr.SetLine(yyS[yypt-4].token.Pos.Line) - yyVAL.funcexpr.SetLastLine(yyS[yypt-0].token.Pos.Line) + yyVAL.exprlist = []ast.Expr{yyDollar[1].expr} } case 81: - //line parser.go.y:452 + yyDollar = yyS[yypt-2 : yypt+1] +//line parser.go.y:451 { - yyVAL.funcexpr = &ast.FunctionExpr{ParList: &ast.ParList{HasVargs: false, Names: []string{}}, Stmts: yyS[yypt-1].stmts} - yyVAL.funcexpr.SetLine(yyS[yypt-3].token.Pos.Line) - yyVAL.funcexpr.SetLastLine(yyS[yypt-0].token.Pos.Line) + yyVAL.expr = &ast.FunctionExpr{ParList: yyDollar[2].funcexpr.ParList, Stmts: yyDollar[2].funcexpr.Stmts} + yyVAL.expr.SetLine(yyDollar[1].token.Pos.Line) + yyVAL.expr.SetLastLine(yyDollar[2].funcexpr.LastLine()) } case 82: - //line parser.go.y:459 + yyDollar = yyS[yypt-5 : yypt+1] +//line parser.go.y:458 { - yyVAL.parlist = &ast.ParList{HasVargs: true, Names: []string{}} + yyVAL.funcexpr = &ast.FunctionExpr{ParList: yyDollar[2].parlist, Stmts: yyDollar[4].stmts} + yyVAL.funcexpr.SetLine(yyDollar[1].token.Pos.Line) + yyVAL.funcexpr.SetLastLine(yyDollar[5].token.Pos.Line) } case 83: - //line parser.go.y:462 + yyDollar = yyS[yypt-4 : yypt+1] +//line parser.go.y:463 { - yyVAL.parlist = &ast.ParList{HasVargs: false, Names: []string{}} - yyVAL.parlist.Names = append(yyVAL.parlist.Names, yyS[yypt-0].namelist...) + yyVAL.funcexpr = &ast.FunctionExpr{ParList: &ast.ParList{HasVargs: false, Names: []string{}}, Stmts: yyDollar[3].stmts} + yyVAL.funcexpr.SetLine(yyDollar[1].token.Pos.Line) + yyVAL.funcexpr.SetLastLine(yyDollar[4].token.Pos.Line) } case 84: - //line parser.go.y:466 + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:470 { yyVAL.parlist = &ast.ParList{HasVargs: true, Names: []string{}} - yyVAL.parlist.Names = append(yyVAL.parlist.Names, yyS[yypt-2].namelist...) } case 85: - //line parser.go.y:473 + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:473 { - yyVAL.expr = &ast.TableExpr{Fields: []*ast.Field{}} - yyVAL.expr.SetLine(yyS[yypt-1].token.Pos.Line) + yyVAL.parlist = &ast.ParList{HasVargs: false, Names: []string{}} + yyVAL.parlist.Names = append(yyVAL.parlist.Names, yyDollar[1].namelist...) } case 86: - //line parser.go.y:477 + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:477 { - yyVAL.expr = &ast.TableExpr{Fields: yyS[yypt-1].fieldlist} - yyVAL.expr.SetLine(yyS[yypt-2].token.Pos.Line) + yyVAL.parlist = &ast.ParList{HasVargs: true, Names: []string{}} + yyVAL.parlist.Names = append(yyVAL.parlist.Names, yyDollar[1].namelist...) } case 87: - //line parser.go.y:484 + yyDollar = yyS[yypt-2 : yypt+1] +//line parser.go.y:484 { - yyVAL.fieldlist = []*ast.Field{yyS[yypt-0].field} + yyVAL.expr = &ast.TableExpr{Fields: []*ast.Field{}} + yyVAL.expr.SetLine(yyDollar[1].token.Pos.Line) } case 88: - //line parser.go.y:487 + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:488 { - yyVAL.fieldlist = append(yyS[yypt-2].fieldlist, yyS[yypt-0].field) + yyVAL.expr = &ast.TableExpr{Fields: yyDollar[2].fieldlist} + yyVAL.expr.SetLine(yyDollar[1].token.Pos.Line) } case 89: - //line parser.go.y:490 + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:495 { - yyVAL.fieldlist = yyS[yypt-1].fieldlist + yyVAL.fieldlist = []*ast.Field{yyDollar[1].field} } case 90: - //line parser.go.y:495 + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:498 { - yyVAL.field = &ast.Field{Key: &ast.StringExpr{Value: yyS[yypt-2].token.Str}, Value: yyS[yypt-0].expr} - yyVAL.field.Key.SetLine(yyS[yypt-2].token.Pos.Line) + yyVAL.fieldlist = append(yyDollar[1].fieldlist, yyDollar[3].field) } case 91: - //line parser.go.y:499 + yyDollar = yyS[yypt-2 : yypt+1] +//line parser.go.y:501 { - yyVAL.field = &ast.Field{Key: yyS[yypt-3].expr, Value: yyS[yypt-0].expr} + yyVAL.fieldlist = yyDollar[1].fieldlist } case 92: - //line parser.go.y:502 + yyDollar = yyS[yypt-3 : yypt+1] +//line parser.go.y:506 { - yyVAL.field = &ast.Field{Value: yyS[yypt-0].expr} + yyVAL.field = &ast.Field{Key: &ast.StringExpr{Value: yyDollar[1].token.Str}, Value: yyDollar[3].expr} + yyVAL.field.Key.SetLine(yyDollar[1].token.Pos.Line) } case 93: - //line parser.go.y:507 + yyDollar = yyS[yypt-5 : yypt+1] +//line parser.go.y:510 { - yyVAL.fieldsep = "," + yyVAL.field = &ast.Field{Key: yyDollar[2].expr, Value: yyDollar[5].expr} } case 94: - //line parser.go.y:510 + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:513 + { + yyVAL.field = &ast.Field{Value: yyDollar[1].expr} + } + case 95: + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:518 + { + yyVAL.fieldsep = "," + } + case 96: + yyDollar = yyS[yypt-1 : yypt+1] +//line parser.go.y:521 { yyVAL.fieldsep = ";" } diff --git a/vendor/github.com/yuin/gopher-lua/parse/parser.go.y b/vendor/github.com/yuin/gopher-lua/parse/parser.go.y index 956133db2..52bcd55fa 100644 --- a/vendor/github.com/yuin/gopher-lua/parse/parser.go.y +++ b/vendor/github.com/yuin/gopher-lua/parse/parser.go.y @@ -52,10 +52,10 @@ import ( } /* Reserved words */ -%token TAnd TBreak TDo TElse TElseIf TEnd TFalse TFor TFunction TIf TIn TLocal TNil TNot TOr TReturn TRepeat TThen TTrue TUntil TWhile +%token TAnd TBreak TDo TElse TElseIf TEnd TFalse TFor TFunction TIf TIn TLocal TNil TNot TOr TReturn TRepeat TThen TTrue TUntil TWhile TGoto /* Literals */ -%token TEqeq TNeq TLte TGte T2Comma T3Comma TIdent TNumber TString '{' '(' +%token TEqeq TNeq TLte TGte T2Comma T3Comma T2Colon TIdent TNumber TString '{' '(' /* Operators */ %left TOr @@ -187,6 +187,14 @@ stat: TLocal namelist { $$ = &ast.LocalAssignStmt{Names: $2, Exprs:[]ast.Expr{}} $$.SetLine($1.Pos.Line) + } | + T2Colon TIdent T2Colon { + $$ = &ast.LabelStmt{Name: $2.Str} + $$.SetLine($1.Pos.Line) + } | + TGoto TIdent { + $$ = &ast.GotoStmt{Label: $2.Str} + $$.SetLine($1.Pos.Line) } elseifs: @@ -396,6 +404,9 @@ prefixexp: $$ = $1 } | '(' expr ')' { + if ex, ok := $2.(*ast.Comma3Expr); ok { + ex.AdjustRet = true + } $$ = $2 $$.SetLine($1.Pos.Line) } diff --git a/vendor/github.com/yuin/gopher-lua/pm/pm.go b/vendor/github.com/yuin/gopher-lua/pm/pm.go index e15bc2100..e5c651f94 100644 --- a/vendor/github.com/yuin/gopher-lua/pm/pm.go +++ b/vendor/github.com/yuin/gopher-lua/pm/pm.go @@ -210,7 +210,7 @@ func (pn *singleClass) Matches(ch int) bool { case 'l', 'L': ret = 'a' <= ch && ch <= 'z' case 'p', 'P': - ret = (0x21 <= ch && ch <= 0x2f) || (0x30 <= ch && ch <= 0x40) || (0x5b <= ch && ch <= 0x60) || (0x7b <= ch && ch <= 0x7e) + ret = (0x21 <= ch && ch <= 0x2f) || (0x3a <= ch && ch <= 0x40) || (0x5b <= ch && ch <= 0x60) || (0x7b <= ch && ch <= 0x7e) case 's', 'S': switch ch { case ' ', '\f', '\n', '\r', '\t', '\v': diff --git a/vendor/github.com/yuin/gopher-lua/state.go b/vendor/github.com/yuin/gopher-lua/state.go index a1ee672e9..292f93b48 100644 --- a/vendor/github.com/yuin/gopher-lua/state.go +++ b/vendor/github.com/yuin/gopher-lua/state.go @@ -402,24 +402,25 @@ func (rg *registry) forceResize(newSize int) { copy(newSlice, rg.array[:rg.top]) // should we copy the area beyond top? there shouldn't be any valid values there so it shouldn't be necessary. rg.array = newSlice } -func (rg *registry) SetTop(top int) { + +func (rg *registry) SetTop(topi int) { // +inline-start // this section is inlined by go-inline // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' { - requiredSize := top + requiredSize := topi if requiredSize > cap(rg.array) { rg.resize(requiredSize) } } - oldtop := rg.top - rg.top = top - for i := oldtop; i < rg.top; i++ { + oldtopi := rg.top + rg.top = topi + for i := oldtopi; i < rg.top; i++ { rg.array[i] = LNil } // values beyond top don't need to be valid LValues, so setting them to nil is fine // setting them to nil rather than LNil lets us invoke the golang memclr opto - if rg.top < oldtop { - nilRange := rg.array[rg.top:oldtop] + if rg.top < oldtopi { + nilRange := rg.array[rg.top:oldtopi] for i := range nilRange { nilRange[i] = nil } @@ -427,7 +428,7 @@ func (rg *registry) SetTop(top int) { //for i := rg.top; i < oldtop; i++ { // rg.array[i] = LNil //} -} +} // +inline-end func (rg *registry) Top() int { return rg.top @@ -530,19 +531,73 @@ func (rg *registry) FillNil(regm, n int) { // +inline-start func (rg *registry) Insert(value LValue, reg int) { top := rg.Top() if reg >= top { - rg.Set(reg, value) + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + regi := reg + vali := value + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } return } top-- for ; top >= reg; top-- { // FIXME consider using copy() here if Insert() is called enough - rg.Set(top+1, rg.Get(top)) + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + regi := top + 1 + vali := rg.Get(top) + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } + } + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + regi := reg + vali := value + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } } - rg.Set(reg, value) } -func (rg *registry) Set(reg int, val LValue) { - newSize := reg + 1 +func (rg *registry) Set(regi int, vali LValue) { // +inline-start + newSize := regi + 1 // this section is inlined by go-inline // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' { @@ -551,14 +606,14 @@ func (rg *registry) Set(reg int, val LValue) { rg.resize(requiredSize) } } - rg.array[reg] = val - if reg >= rg.top { - rg.top = reg + 1 + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 } -} +} // +inline-end -func (rg *registry) SetNumber(reg int, val LNumber) { - newSize := reg + 1 +func (rg *registry) SetNumber(regi int, vali LNumber) { // +inline-start + newSize := regi + 1 // this section is inlined by go-inline // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' { @@ -567,11 +622,11 @@ func (rg *registry) SetNumber(reg int, val LNumber) { rg.resize(requiredSize) } } - rg.array[reg] = rg.alloc.LNumber2I(val) - if reg >= rg.top { - rg.top = reg + 1 + rg.array[regi] = rg.alloc.LNumber2I(vali) + if regi >= rg.top { + rg.top = regi + 1 } -} +} // +inline-end func (rg *registry) IsFull() bool { return rg.top >= cap(rg.array) @@ -815,6 +870,9 @@ func (ls *LState) isStarted() bool { func (ls *LState) kill() { ls.Dead = true + if ls.ctxCancelFn != nil { + ls.ctxCancelFn() + } } func (ls *LState) indexToReg(idx int) int { @@ -1561,6 +1619,7 @@ func (ls *LState) NewThread() (*LState, context.CancelFunc) { if ls.ctx != nil { thread.mainLoop = mainLoopWithContext thread.ctx, f = context.WithCancel(ls.ctx) + thread.ctxCancelFn = f } return thread, f } @@ -2010,6 +2069,9 @@ func (ls *LState) PCall(nargs, nret int, errfunc *LFunction) (err error) { err = rcv.(*ApiError) err.(*ApiError).StackTrace = ls.stackTrace(0) } + ls.stack.SetSp(sp) + ls.currentFrame = ls.stack.Last() + ls.reg.SetTop(base) } }() ls.Call(1, 1) diff --git a/vendor/github.com/yuin/gopher-lua/table.go b/vendor/github.com/yuin/gopher-lua/table.go index e220bd9c3..ddf14dd88 100644 --- a/vendor/github.com/yuin/gopher-lua/table.go +++ b/vendor/github.com/yuin/gopher-lua/table.go @@ -46,7 +46,7 @@ func newLTable(acap int, hcap int) *LTable { return tb } -// Len returns length of this LTable. +// Len returns length of this LTable without using __len. func (tb *LTable) Len() int { if tb.array == nil { return 0 diff --git a/vendor/github.com/yuin/gopher-lua/value.go b/vendor/github.com/yuin/gopher-lua/value.go index 0d4af8081..4156e9d5b 100644 --- a/vendor/github.com/yuin/gopher-lua/value.go +++ b/vendor/github.com/yuin/gopher-lua/value.go @@ -29,12 +29,6 @@ func (vt LValueType) String() string { type LValue interface { String() string Type() LValueType - // to reduce `runtime.assertI2T2` costs, this method should be used instead of the type assertion in heavy paths(typically inside the VM). - assertFloat64() (float64, bool) - // to reduce `runtime.assertI2T2` costs, this method should be used instead of the type assertion in heavy paths(typically inside the VM). - assertString() (string, bool) - // to reduce `runtime.assertI2T2` costs, this method should be used instead of the type assertion in heavy paths(typically inside the VM). - assertFunction() (*LFunction, bool) } // LVIsFalse returns true if a given LValue is a nil or false otherwise false. @@ -80,11 +74,8 @@ func LVAsNumber(v LValue) LNumber { type LNilType struct{} -func (nl *LNilType) String() string { return "nil" } -func (nl *LNilType) Type() LValueType { return LTNil } -func (nl *LNilType) assertFloat64() (float64, bool) { return 0, false } -func (nl *LNilType) assertString() (string, bool) { return "", false } -func (nl *LNilType) assertFunction() (*LFunction, bool) { return nil, false } +func (nl *LNilType) String() string { return "nil" } +func (nl *LNilType) Type() LValueType { return LTNil } var LNil = LValue(&LNilType{}) @@ -96,21 +87,15 @@ func (bl LBool) String() string { } return "false" } -func (bl LBool) Type() LValueType { return LTBool } -func (bl LBool) assertFloat64() (float64, bool) { return 0, false } -func (bl LBool) assertString() (string, bool) { return "", false } -func (bl LBool) assertFunction() (*LFunction, bool) { return nil, false } +func (bl LBool) Type() LValueType { return LTBool } var LTrue = LBool(true) var LFalse = LBool(false) type LString string -func (st LString) String() string { return string(st) } -func (st LString) Type() LValueType { return LTString } -func (st LString) assertFloat64() (float64, bool) { return 0, false } -func (st LString) assertString() (string, bool) { return string(st), true } -func (st LString) assertFunction() (*LFunction, bool) { return nil, false } +func (st LString) String() string { return string(st) } +func (st LString) Type() LValueType { return LTString } // fmt.Formatter interface func (st LString) Format(f fmt.State, c rune) { @@ -133,10 +118,7 @@ func (nm LNumber) String() string { return fmt.Sprint(float64(nm)) } -func (nm LNumber) Type() LValueType { return LTNumber } -func (nm LNumber) assertFloat64() (float64, bool) { return float64(nm), true } -func (nm LNumber) assertString() (string, bool) { return "", false } -func (nm LNumber) assertFunction() (*LFunction, bool) { return nil, false } +func (nm LNumber) Type() LValueType { return LTNumber } // fmt.Formatter interface func (nm LNumber) Format(f fmt.State, c rune) { @@ -168,11 +150,8 @@ type LTable struct { k2i map[LValue]int } -func (tb *LTable) String() string { return fmt.Sprintf("table: %p", tb) } -func (tb *LTable) Type() LValueType { return LTTable } -func (tb *LTable) assertFloat64() (float64, bool) { return 0, false } -func (tb *LTable) assertString() (string, bool) { return "", false } -func (tb *LTable) assertFunction() (*LFunction, bool) { return nil, false } +func (tb *LTable) String() string { return fmt.Sprintf("table: %p", tb) } +func (tb *LTable) Type() LValueType { return LTTable } type LFunction struct { IsG bool @@ -183,11 +162,8 @@ type LFunction struct { } type LGFunction func(*LState) int -func (fn *LFunction) String() string { return fmt.Sprintf("function: %p", fn) } -func (fn *LFunction) Type() LValueType { return LTFunction } -func (fn *LFunction) assertFloat64() (float64, bool) { return 0, false } -func (fn *LFunction) assertString() (string, bool) { return "", false } -func (fn *LFunction) assertFunction() (*LFunction, bool) { return fn, true } +func (fn *LFunction) String() string { return fmt.Sprintf("function: %p", fn) } +func (fn *LFunction) Type() LValueType { return LTFunction } type Global struct { MainThread *LState @@ -218,13 +194,11 @@ type LState struct { hasErrorFunc bool mainLoop func(*LState, *callFrame) ctx context.Context + ctxCancelFn context.CancelFunc } -func (ls *LState) String() string { return fmt.Sprintf("thread: %p", ls) } -func (ls *LState) Type() LValueType { return LTThread } -func (ls *LState) assertFloat64() (float64, bool) { return 0, false } -func (ls *LState) assertString() (string, bool) { return "", false } -func (ls *LState) assertFunction() (*LFunction, bool) { return nil, false } +func (ls *LState) String() string { return fmt.Sprintf("thread: %p", ls) } +func (ls *LState) Type() LValueType { return LTThread } type LUserData struct { Value interface{} @@ -232,16 +206,10 @@ type LUserData struct { Metatable LValue } -func (ud *LUserData) String() string { return fmt.Sprintf("userdata: %p", ud) } -func (ud *LUserData) Type() LValueType { return LTUserData } -func (ud *LUserData) assertFloat64() (float64, bool) { return 0, false } -func (ud *LUserData) assertString() (string, bool) { return "", false } -func (ud *LUserData) assertFunction() (*LFunction, bool) { return nil, false } +func (ud *LUserData) String() string { return fmt.Sprintf("userdata: %p", ud) } +func (ud *LUserData) Type() LValueType { return LTUserData } type LChannel chan LValue -func (ch LChannel) String() string { return fmt.Sprintf("channel: %p", ch) } -func (ch LChannel) Type() LValueType { return LTChannel } -func (ch LChannel) assertFloat64() (float64, bool) { return 0, false } -func (ch LChannel) assertString() (string, bool) { return "", false } -func (ch LChannel) assertFunction() (*LFunction, bool) { return nil, false } +func (ch LChannel) String() string { return fmt.Sprintf("channel: %p", ch) } +func (ch LChannel) Type() LValueType { return LTChannel } diff --git a/vendor/github.com/yuin/gopher-lua/vm.go b/vendor/github.com/yuin/gopher-lua/vm.go index f3733f130..97335a750 100644 --- a/vendor/github.com/yuin/gopher-lua/vm.go +++ b/vendor/github.com/yuin/gopher-lua/vm.go @@ -307,7 +307,27 @@ func init() { A := int(inst>>18) & 0xff //GETA RA := lbase + A B := int(inst & 0x1ff) //GETB - reg.Set(RA, reg.Get(lbase+B)) + v := reg.Get(lbase + B) + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + rg := reg + regi := RA + vali := v + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } return 0 }, func(L *LState, inst uint32, baseframe *callFrame) int { //OP_MOVEN @@ -317,7 +337,27 @@ func init() { A := int(inst>>18) & 0xff //GETA B := int(inst & 0x1ff) //GETB C := int(inst>>9) & 0x1ff //GETC - reg.Set(lbase+A, reg.Get(lbase+B)) + v := reg.Get(lbase + B) + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + rg := reg + regi := lbase + A + vali := v + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } code := cf.Fn.Proto.Code pc := cf.Pc for i := 0; i < C; i++ { @@ -325,7 +365,27 @@ func init() { pc++ A = int(inst>>18) & 0xff //GETA B = int(inst & 0x1ff) //GETB - reg.Set(lbase+A, reg.Get(lbase+B)) + v := reg.Get(lbase + B) + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + rg := reg + regi := lbase + A + vali := v + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } } cf.Pc = pc return 0 @@ -337,7 +397,27 @@ func init() { A := int(inst>>18) & 0xff //GETA RA := lbase + A Bx := int(inst & 0x3ffff) //GETBX - reg.Set(RA, cf.Fn.Proto.Constants[Bx]) + v := cf.Fn.Proto.Constants[Bx] + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + rg := reg + regi := RA + vali := v + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } return 0 }, func(L *LState, inst uint32, baseframe *callFrame) int { //OP_LOADBOOL @@ -349,9 +429,47 @@ func init() { B := int(inst & 0x1ff) //GETB C := int(inst>>9) & 0x1ff //GETC if B != 0 { - reg.Set(RA, LTrue) + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + rg := reg + regi := RA + vali := LTrue + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } } else { - reg.Set(RA, LFalse) + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + rg := reg + regi := RA + vali := LFalse + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } } if C != 0 { cf.Pc++ @@ -366,7 +484,26 @@ func init() { RA := lbase + A B := int(inst & 0x1ff) //GETB for i := RA; i <= lbase+B; i++ { - reg.Set(i, LNil) + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + rg := reg + regi := i + vali := LNil + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } } return 0 }, @@ -377,7 +514,27 @@ func init() { A := int(inst>>18) & 0xff //GETA RA := lbase + A B := int(inst & 0x1ff) //GETB - reg.Set(RA, cf.Fn.Upvalues[B].Value()) + v := cf.Fn.Upvalues[B].Value() + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + rg := reg + regi := RA + vali := v + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } return 0 }, func(L *LState, inst uint32, baseframe *callFrame) int { //OP_GETGLOBAL @@ -388,7 +545,27 @@ func init() { RA := lbase + A Bx := int(inst & 0x3ffff) //GETBX //reg.Set(RA, L.getField(cf.Fn.Env, cf.Fn.Proto.Constants[Bx])) - reg.Set(RA, L.getFieldString(cf.Fn.Env, cf.Fn.Proto.stringConstants[Bx])) + v := L.getFieldString(cf.Fn.Env, cf.Fn.Proto.stringConstants[Bx]) + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + rg := reg + regi := RA + vali := v + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } return 0 }, func(L *LState, inst uint32, baseframe *callFrame) int { //OP_GETTABLE @@ -399,7 +576,27 @@ func init() { RA := lbase + A B := int(inst & 0x1ff) //GETB C := int(inst>>9) & 0x1ff //GETC - reg.Set(RA, L.getField(reg.Get(lbase+B), L.rkValue(C))) + v := L.getField(reg.Get(lbase+B), L.rkValue(C)) + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + rg := reg + regi := RA + vali := v + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } return 0 }, func(L *LState, inst uint32, baseframe *callFrame) int { //OP_GETTABLEKS @@ -410,7 +607,27 @@ func init() { RA := lbase + A B := int(inst & 0x1ff) //GETB C := int(inst>>9) & 0x1ff //GETC - reg.Set(RA, L.getFieldString(reg.Get(lbase+B), L.rkString(C))) + v := L.getFieldString(reg.Get(lbase+B), L.rkString(C)) + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + rg := reg + regi := RA + vali := v + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } return 0 }, func(L *LState, inst uint32, baseframe *callFrame) int { //OP_SETGLOBAL @@ -464,7 +681,27 @@ func init() { RA := lbase + A B := int(inst & 0x1ff) //GETB C := int(inst>>9) & 0x1ff //GETC - reg.Set(RA, newLTable(B, C)) + v := newLTable(B, C) + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + rg := reg + regi := RA + vali := v + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } return 0 }, func(L *LState, inst uint32, baseframe *callFrame) int { //OP_SELF @@ -476,8 +713,47 @@ func init() { B := int(inst & 0x1ff) //GETB C := int(inst>>9) & 0x1ff //GETC selfobj := reg.Get(lbase + B) - reg.Set(RA, L.getFieldString(selfobj, L.rkString(C))) - reg.Set(RA+1, selfobj) + v := L.getFieldString(selfobj, L.rkString(C)) + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + rg := reg + regi := RA + vali := v + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + rg := reg + regi := RA + 1 + vali := selfobj + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } return 0 }, opArith, // OP_ADD @@ -495,17 +771,74 @@ func init() { B := int(inst & 0x1ff) //GETB unaryv := L.rkValue(B) if nm, ok := unaryv.(LNumber); ok { - reg.SetNumber(RA, -nm) + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + rg := reg + regi := RA + vali := -nm + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } } else { op := L.metaOp1(unaryv, "__unm") if op.Type() == LTFunction { reg.Push(op) reg.Push(unaryv) L.Call(1, 1) - reg.Set(RA, reg.Pop()) + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + rg := reg + regi := RA + vali := reg.Pop() + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } } else if str, ok1 := unaryv.(LString); ok1 { if num, err := parseNumber(string(str)); err == nil { - reg.Set(RA, -num) + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + rg := reg + regi := RA + vali := -num + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } } else { L.RaiseError("__unm undefined") } @@ -523,9 +856,47 @@ func init() { RA := lbase + A B := int(inst & 0x1ff) //GETB if LVIsFalse(reg.Get(lbase + B)) { - reg.Set(RA, LTrue) + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + rg := reg + regi := RA + vali := LTrue + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } } else { - reg.Set(RA, LFalse) + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + rg := reg + regi := RA + vali := LFalse + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } } return 0 }, @@ -538,7 +909,26 @@ func init() { B := int(inst & 0x1ff) //GETB switch lv := L.rkValue(B).(type) { case LString: - reg.SetNumber(RA, LNumber(len(lv))) + // this section is inlined by go-inline + // source function is 'func (rg *registry) SetNumber(regi int, vali LNumber) ' in '_state.go' + { + rg := reg + regi := RA + vali := LNumber(len(lv)) + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = rg.alloc.LNumber2I(vali) + if regi >= rg.top { + rg.top = regi + 1 + } + } default: op := L.metaOp1(lv, "__len") if op.Type() == LTFunction { @@ -547,12 +937,70 @@ func init() { L.Call(1, 1) ret := reg.Pop() if ret.Type() == LTNumber { - reg.SetNumber(RA, ret.(LNumber)) + v, _ := ret.(LNumber) + // this section is inlined by go-inline + // source function is 'func (rg *registry) SetNumber(regi int, vali LNumber) ' in '_state.go' + { + rg := reg + regi := RA + vali := v + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = rg.alloc.LNumber2I(vali) + if regi >= rg.top { + rg.top = regi + 1 + } + } } else { - reg.SetNumber(RA, LNumber(0)) + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + rg := reg + regi := RA + vali := ret + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } } } else if lv.Type() == LTTable { - reg.SetNumber(RA, LNumber(lv.(*LTable).Len())) + // this section is inlined by go-inline + // source function is 'func (rg *registry) SetNumber(regi int, vali LNumber) ' in '_state.go' + { + rg := reg + regi := RA + vali := LNumber(lv.(*LTable).Len()) + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = rg.alloc.LNumber2I(vali) + if regi >= rg.top { + rg.top = regi + 1 + } + } } else { L.RaiseError("__len undefined") } @@ -569,7 +1017,27 @@ func init() { C := int(inst>>9) & 0x1ff //GETC RC := lbase + C RB := lbase + B - reg.Set(RA, stringConcat(L, RC-RB+1, RC)) + v := stringConcat(L, RC-RB+1, RC) + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + rg := reg + regi := RA + vali := v + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } return 0 }, func(L *LState, inst uint32, baseframe *callFrame) int { //OP_JMP @@ -617,8 +1085,8 @@ func init() { rhs := L.rkValue(C) ret := false - if v1, ok1 := lhs.assertFloat64(); ok1 { - if v2, ok2 := rhs.assertFloat64(); ok2 { + if v1, ok1 := lhs.(LNumber); ok1 { + if v2, ok2 := rhs.(LNumber); ok2 { ret = v1 <= v2 } else { L.RaiseError("attempt to compare %v with %v", lhs.Type().String(), rhs.Type().String()) @@ -672,7 +1140,26 @@ func init() { B := int(inst & 0x1ff) //GETB C := int(inst>>9) & 0x1ff //GETC if value := reg.Get(lbase + B); LVAsBool(value) != (C == 0) { - reg.Set(RA, value) + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + rg := reg + regi := RA + vali := value + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } } else { cf.Pc++ } @@ -694,7 +1181,7 @@ func init() { nret := C - 1 var callable *LFunction var meta bool - if fn, ok := lv.assertFunction(); ok { + if fn, ok := lv.(*LFunction); ok { callable = fn meta = false } else { @@ -837,7 +1324,7 @@ func init() { lv := reg.Get(RA) var callable *LFunction var meta bool - if fn, ok := lv.assertFunction(); ok { + if fn, ok := lv.(*LFunction); ok { callable = fn meta = false } else { @@ -1308,17 +1795,85 @@ func init() { lbase := cf.LocalBase A := int(inst>>18) & 0xff //GETA RA := lbase + A - if init, ok1 := reg.Get(RA).assertFloat64(); ok1 { - if limit, ok2 := reg.Get(RA + 1).assertFloat64(); ok2 { - if step, ok3 := reg.Get(RA + 2).assertFloat64(); ok3 { + if init, ok1 := reg.Get(RA).(LNumber); ok1 { + if limit, ok2 := reg.Get(RA + 1).(LNumber); ok2 { + if step, ok3 := reg.Get(RA + 2).(LNumber); ok3 { init += step - reg.SetNumber(RA, LNumber(init)) + v := LNumber(init) + // this section is inlined by go-inline + // source function is 'func (rg *registry) SetNumber(regi int, vali LNumber) ' in '_state.go' + { + rg := reg + regi := RA + vali := v + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = rg.alloc.LNumber2I(vali) + if regi >= rg.top { + rg.top = regi + 1 + } + } if (step > 0 && init <= limit) || (step <= 0 && init >= limit) { Sbx := int(inst&0x3ffff) - opMaxArgSbx //GETSBX cf.Pc += Sbx - reg.SetNumber(RA+3, LNumber(init)) + // this section is inlined by go-inline + // source function is 'func (rg *registry) SetNumber(regi int, vali LNumber) ' in '_state.go' + { + rg := reg + regi := RA + 3 + vali := v + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = rg.alloc.LNumber2I(vali) + if regi >= rg.top { + rg.top = regi + 1 + } + } } else { - reg.SetTop(RA + 1) + // this section is inlined by go-inline + // source function is 'func (rg *registry) SetTop(topi int) ' in '_state.go' + { + rg := reg + topi := RA + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := topi + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + oldtopi := rg.top + rg.top = topi + for i := oldtopi; i < rg.top; i++ { + rg.array[i] = LNil + } + // values beyond top don't need to be valid LValues, so setting them to nil is fine + // setting them to nil rather than LNil lets us invoke the golang memclr opto + if rg.top < oldtopi { + nilRange := rg.array[rg.top:oldtopi] + for i := range nilRange { + nilRange[i] = nil + } + } + //for i := rg.top; i < oldtop; i++ { + // rg.array[i] = LNil + //} + } } } else { L.RaiseError("for statement step must be a number") @@ -1338,9 +1893,28 @@ func init() { A := int(inst>>18) & 0xff //GETA RA := lbase + A Sbx := int(inst&0x3ffff) - opMaxArgSbx //GETSBX - if init, ok1 := reg.Get(RA).assertFloat64(); ok1 { - if step, ok2 := reg.Get(RA + 2).assertFloat64(); ok2 { - reg.SetNumber(RA, LNumber(init-step)) + if init, ok1 := reg.Get(RA).(LNumber); ok1 { + if step, ok2 := reg.Get(RA + 2).(LNumber); ok2 { + // this section is inlined by go-inline + // source function is 'func (rg *registry) SetNumber(regi int, vali LNumber) ' in '_state.go' + { + rg := reg + regi := RA + vali := LNumber(init - step) + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = rg.alloc.LNumber2I(vali) + if regi >= rg.top { + rg.top = regi + 1 + } + } } else { L.RaiseError("for statement step must be a number") } @@ -1358,13 +1932,118 @@ func init() { RA := lbase + A C := int(inst>>9) & 0x1ff //GETC nret := C - reg.SetTop(RA + 3 + 2) - reg.Set(RA+3+2, reg.Get(RA+2)) - reg.Set(RA+3+1, reg.Get(RA+1)) - reg.Set(RA+3, reg.Get(RA)) + // this section is inlined by go-inline + // source function is 'func (rg *registry) SetTop(topi int) ' in '_state.go' + { + rg := reg + topi := RA + 3 + 2 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := topi + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + oldtopi := rg.top + rg.top = topi + for i := oldtopi; i < rg.top; i++ { + rg.array[i] = LNil + } + // values beyond top don't need to be valid LValues, so setting them to nil is fine + // setting them to nil rather than LNil lets us invoke the golang memclr opto + if rg.top < oldtopi { + nilRange := rg.array[rg.top:oldtopi] + for i := range nilRange { + nilRange[i] = nil + } + } + //for i := rg.top; i < oldtop; i++ { + // rg.array[i] = LNil + //} + } + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + rg := reg + regi := RA + 3 + 2 + vali := reg.Get(RA + 2) + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + rg := reg + regi := RA + 3 + 1 + vali := reg.Get(RA + 1) + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + rg := reg + regi := RA + 3 + vali := reg.Get(RA) + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } L.callR(2, nret, RA+3) if value := reg.Get(RA + 3); value != LNil { - reg.Set(RA+2, value) + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + rg := reg + regi := RA + 2 + vali := value + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } pc := cf.Fn.Proto.Code[cf.Pc] cf.Pc += int(pc&0x3ffff) - opMaxArgSbx } @@ -1430,7 +2109,26 @@ func init() { Bx := int(inst & 0x3ffff) //GETBX proto := cf.Fn.Proto.FunctionPrototypes[Bx] closure := newLFunctionL(proto, cf.Fn.Env, int(proto.NumUpvalues)) - reg.Set(RA, closure) + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + rg := reg + regi := RA + vali := closure + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } for i := 0; i < int(proto.NumUpvalues); i++ { inst = cf.Fn.Proto.Code[cf.Pc] cf.Pc++ @@ -1519,12 +2217,52 @@ func opArith(L *LState, inst uint32, baseframe *callFrame) int { //OP_ADD, OP_SU C := int(inst>>9) & 0x1ff //GETC lhs := L.rkValue(B) rhs := L.rkValue(C) - v1, ok1 := lhs.assertFloat64() - v2, ok2 := rhs.assertFloat64() + v1, ok1 := lhs.(LNumber) + v2, ok2 := rhs.(LNumber) if ok1 && ok2 { - reg.SetNumber(RA, numberArith(L, opcode, LNumber(v1), LNumber(v2))) + v := numberArith(L, opcode, LNumber(v1), LNumber(v2)) + // this section is inlined by go-inline + // source function is 'func (rg *registry) SetNumber(regi int, vali LNumber) ' in '_state.go' + { + rg := reg + regi := RA + vali := v + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = rg.alloc.LNumber2I(vali) + if regi >= rg.top { + rg.top = regi + 1 + } + } } else { - reg.Set(RA, objectArith(L, opcode, lhs, rhs)) + v := objectArith(L, opcode, lhs, rhs) + // this section is inlined by go-inline + // source function is 'func (rg *registry) Set(regi int, vali LValue) ' in '_state.go' + { + rg := reg + regi := RA + vali := v + newSize := regi + 1 + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + rg.array[regi] = vali + if regi >= rg.top { + rg.top = regi + 1 + } + } } return 0 } @@ -1533,7 +2271,7 @@ func luaModulo(lhs, rhs LNumber) LNumber { flhs := float64(lhs) frhs := float64(rhs) v := math.Mod(flhs, frhs) - if flhs < 0 || frhs < 0 && !(flhs < 0 && frhs < 0) { + if frhs > 0 && v < 0 || frhs < 0 && v > 0 { v += frhs } return LNumber(v) @@ -1577,7 +2315,7 @@ func objectArith(L *LState, opcode int, lhs, rhs LValue) LValue { event = "__pow" } op := L.metaOp2(lhs, rhs, event) - if op.Type() == LTFunction { + if _, ok := op.(*LFunction); ok { L.reg.Push(op) L.reg.Push(lhs) L.reg.Push(rhs) @@ -1594,8 +2332,8 @@ func objectArith(L *LState, opcode int, lhs, rhs LValue) LValue { rhs = rnum } } - if v1, ok1 := lhs.assertFloat64(); ok1 { - if v2, ok2 := rhs.assertFloat64(); ok2 { + if v1, ok1 := lhs.(LNumber); ok1 { + if v2, ok2 := rhs.(LNumber); ok2 { return numberArith(L, opcode, LNumber(v1), LNumber(v2)) } } @@ -1644,8 +2382,8 @@ func stringConcat(L *LState, total, last int) LValue { func lessThan(L *LState, lhs, rhs LValue) bool { // optimization for numbers - if v1, ok1 := lhs.assertFloat64(); ok1 { - if v2, ok2 := rhs.assertFloat64(); ok2 { + if v1, ok1 := lhs.(LNumber); ok1 { + if v2, ok2 := rhs.(LNumber); ok2 { return v1 < v2 } L.RaiseError("attempt to compare %v with %v", lhs.Type().String(), rhs.Type().String()) @@ -1665,17 +2403,18 @@ func lessThan(L *LState, lhs, rhs LValue) bool { } func equals(L *LState, lhs, rhs LValue, raw bool) bool { - if lhs.Type() != rhs.Type() { + lt := lhs.Type() + if lt != rhs.Type() { return false } ret := false - switch lhs.Type() { + switch lt { case LTNil: ret = true case LTNumber: - v1, _ := lhs.assertFloat64() - v2, _ := rhs.assertFloat64() + v1, _ := lhs.(LNumber) + v2, _ := rhs.(LNumber) ret = v1 == v2 case LTBool: ret = bool(lhs.(LBool)) == bool(rhs.(LBool)) diff --git a/vendor/modules.txt b/vendor/modules.txt index 29d192209..4b1903cb0 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -4,16 +4,17 @@ github.com/JaderDias/movingmedian # github.com/aclements/go-moremath v0.0.0-20210112150236-f10218a38794 ## explicit; go 1.12 github.com/aclements/go-moremath/mathx -# github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a -## explicit -github.com/alicebob/gopher-json -# github.com/alicebob/miniredis/v2 v2.22.0 -## explicit; go 1.14 +# github.com/alicebob/miniredis/v2 v2.35.0 +## explicit; go 1.17 github.com/alicebob/miniredis/v2 +github.com/alicebob/miniredis/v2/fpconv github.com/alicebob/miniredis/v2/geohash +github.com/alicebob/miniredis/v2/gopher-json github.com/alicebob/miniredis/v2/hyperloglog github.com/alicebob/miniredis/v2/metro +github.com/alicebob/miniredis/v2/proto github.com/alicebob/miniredis/v2/server +github.com/alicebob/miniredis/v2/size # github.com/ansel1/merry v1.8.0 ## explicit; go 1.18 github.com/ansel1/merry @@ -209,8 +210,8 @@ github.com/valyala/fastjson/fastfloat # github.com/wangjohn/quickselect v0.0.0-20161129230411-ed8402a42d5f ## explicit github.com/wangjohn/quickselect -# github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 -## explicit; go 1.14 +# github.com/yuin/gopher-lua v1.1.1 +## explicit; go 1.17 github.com/yuin/gopher-lua github.com/yuin/gopher-lua/ast github.com/yuin/gopher-lua/parse