From 089396cfd4aed5d17493298d2a3492c4c8e5a713 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gonz=C3=A1lez=20Lopes?= Date: Thu, 19 Nov 2020 12:15:58 +0100 Subject: [PATCH 01/16] Add Redis caching MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add Redis client - Add Redis support - Add Redis file Signed-off-by: Daniel González Lopes --- tempodb/backend/redis/cache.go | 144 +++++++++++++++++++++++++++++++++ tempodb/config.go | 2 + tempodb/tempodb.go | 9 +++ 3 files changed, 155 insertions(+) create mode 100644 tempodb/backend/redis/cache.go diff --git a/tempodb/backend/redis/cache.go b/tempodb/backend/redis/cache.go new file mode 100644 index 00000000000..17969b2c618 --- /dev/null +++ b/tempodb/backend/redis/cache.go @@ -0,0 +1,144 @@ +package redis + +import ( + "context" + "strconv" + "time" + + "github.com/cortexproject/cortex/pkg/chunk/cache" + "github.com/go-kit/kit/log" + "github.com/grafana/tempo/tempodb/backend" + "github.com/grafana/tempo/tempodb/encoding" + + "github.com/google/uuid" +) + +const ( + typeBloom = "bloom" + typeIndex = "index" +) + +type Config struct { + ClientConfig cache.RedisConfig `yaml:",inline"` + + TTL time.Duration `yaml:"ttl"` +} + +type readerWriter struct { + nextReader backend.Reader + nextWriter backend.Writer + client *cache.RedisCache + logger log.Logger +} + +func New(nextReader backend.Reader, nextWriter backend.Writer, cfg *Config, logger log.Logger) (backend.Reader, backend.Writer, error) { + if cfg.ClientConfig.Timeout == 0 { + cfg.ClientConfig.Timeout = 100 * time.Millisecond + } + + client := cache.NewRedisClient(&cfg.ClientConfig) + + rw := &readerWriter{ + client: cache.NewRedisCache("tempo", client, logger), + nextReader: nextReader, + nextWriter: nextWriter, + logger: logger, + } + + return rw, rw, nil +} + +// Reader +func (r *readerWriter) Tenants(ctx context.Context) ([]string, error) { + return r.nextReader.Tenants(ctx) +} + +func (r *readerWriter) Blocks(ctx context.Context, tenantID string) ([]uuid.UUID, error) { + return r.nextReader.Blocks(ctx, tenantID) +} + +func (r *readerWriter) BlockMeta(ctx context.Context, blockID uuid.UUID, tenantID string) (*encoding.BlockMeta, error) { + return r.nextReader.BlockMeta(ctx, blockID, tenantID) +} + +func (r *readerWriter) Bloom(ctx context.Context, blockID uuid.UUID, tenantID string, shardNum int) ([]byte, error) { + key := bloomKey(blockID, tenantID, typeBloom, shardNum) + val := r.get(ctx, key) + if val != nil { + return val, nil + } + + val, err := r.nextReader.Bloom(ctx, blockID, tenantID, shardNum) + if err == nil { + r.set(ctx, key, val) + } + + return val, err +} + +func (r *readerWriter) Index(ctx context.Context, blockID uuid.UUID, tenantID string) ([]byte, error) { + key := key(blockID, tenantID, typeIndex) + val := r.get(ctx, key) + if val != nil { + return val, nil + } + + val, err := r.nextReader.Index(ctx, blockID, tenantID) + if err == nil { + r.set(ctx, key, val) + } + + return val, err +} + +func (r *readerWriter) Object(ctx context.Context, blockID uuid.UUID, tenantID string, start uint64, buffer []byte) error { + return r.nextReader.Object(ctx, blockID, tenantID, start, buffer) +} + +func (r *readerWriter) Shutdown() { + r.nextReader.Shutdown() + r.client.Stop() +} + +// Writer +func (r *readerWriter) Write(ctx context.Context, meta *encoding.BlockMeta, bBloom [][]byte, bIndex []byte, objectFilePath string) error { + for i, b := range bBloom { + r.set(ctx, bloomKey(meta.BlockID, meta.TenantID, typeBloom, i), b) + } + r.set(ctx, key(meta.BlockID, meta.TenantID, typeIndex), bIndex) + + return r.nextWriter.Write(ctx, meta, bBloom, bIndex, objectFilePath) +} + +func (r *readerWriter) WriteBlockMeta(ctx context.Context, tracker backend.AppendTracker, meta *encoding.BlockMeta, bBloom [][]byte, bIndex []byte) error { + for i, b := range bBloom { + r.set(ctx, bloomKey(meta.BlockID, meta.TenantID, typeBloom, i), b) + } + r.set(ctx, key(meta.BlockID, meta.TenantID, typeIndex), bIndex) + + return r.nextWriter.WriteBlockMeta(ctx, tracker, meta, bBloom, bIndex) +} + +func (r *readerWriter) AppendObject(ctx context.Context, tracker backend.AppendTracker, meta *encoding.BlockMeta, bObject []byte) (backend.AppendTracker, error) { + return r.nextWriter.AppendObject(ctx, tracker, meta, bObject) +} + +func (r *readerWriter) get(ctx context.Context, key string) []byte { + found, vals, _ := r.client.Fetch(ctx, []string{key}) + if len(found) > 0 { + return vals[0] + } + return nil +} + +func (r *readerWriter) set(ctx context.Context, key string, val []byte) { + r.client.Store(ctx, []string{key}, [][]byte{val}) +} + +func key(blockID uuid.UUID, tenantID string, t string) string { + return blockID.String() + ":" + tenantID + ":" + t +} + +func bloomKey(blockID uuid.UUID, tenantID string, t string, shardNum int) string { + return blockID.String() + ":" + tenantID + ":" + t + strconv.Itoa(shardNum) +} diff --git a/tempodb/config.go b/tempodb/config.go index d561de45016..1928c617e98 100644 --- a/tempodb/config.go +++ b/tempodb/config.go @@ -7,6 +7,7 @@ import ( "github.com/grafana/tempo/tempodb/backend/gcs" "github.com/grafana/tempo/tempodb/backend/local" "github.com/grafana/tempo/tempodb/backend/memcached" + "github.com/grafana/tempo/tempodb/backend/redis" "github.com/grafana/tempo/tempodb/backend/s3" "github.com/grafana/tempo/tempodb/pool" "github.com/grafana/tempo/tempodb/wal" @@ -22,6 +23,7 @@ type Config struct { Diskcache *diskcache.Config `yaml:"disk_cache"` Memcached *memcached.Config `yaml:"memcached"` + Redis *redis.Config `yaml:"redis"` BlocklistPoll time.Duration `yaml:"blocklist_poll"` } diff --git a/tempodb/tempodb.go b/tempodb/tempodb.go index fbd1800c4b4..63a5699fa17 100644 --- a/tempodb/tempodb.go +++ b/tempodb/tempodb.go @@ -26,6 +26,7 @@ import ( "github.com/grafana/tempo/tempodb/backend/gcs" "github.com/grafana/tempo/tempodb/backend/local" "github.com/grafana/tempo/tempodb/backend/memcached" + "github.com/grafana/tempo/tempodb/backend/redis" "github.com/grafana/tempo/tempodb/backend/s3" "github.com/grafana/tempo/tempodb/encoding" "github.com/grafana/tempo/tempodb/encoding/bloom" @@ -156,6 +157,14 @@ func New(cfg *Config, logger log.Logger) (Reader, Writer, Compactor, error) { } } + if cfg.Redis != nil { + r, w, err = redis.New(r, w, cfg.Redis, logger) + + if err != nil { + return nil, nil, nil, err + } + } + rw := &readerWriter{ c: c, compactedBlockLists: make(map[string][]*encoding.CompactedBlockMeta), From 59d921aac3cf64272ab97f0fc0d124d54b22b5dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gonz=C3=A1lez=20Lopes?= Date: Thu, 19 Nov 2020 12:20:06 +0100 Subject: [PATCH 02/16] Add Redis to docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniel González Lopes --- docs/tempo/website/configuration/_index.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/tempo/website/configuration/_index.md b/docs/tempo/website/configuration/_index.md index e213c4c6972..76feb3920e1 100644 --- a/docs/tempo/website/configuration/_index.md +++ b/docs/tempo/website/configuration/_index.md @@ -91,6 +91,9 @@ storage: host: memcached service: memcached-client timeout: 500ms + redis: # optional redis configuration + endpoint: redis + timeout: 500ms pool: # the worker pool is used primarily when finding traces by id, but is also used by other max_workers: 50 # total number of workers pulling jobs from the queue queue_depth: 2000 # length of job queue From 69b1f59b88a3fb7365f546e24f8b25e4e945bf2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gonz=C3=A1lez=20Lopes?= Date: Thu, 19 Nov 2020 13:33:10 +0100 Subject: [PATCH 03/16] Add to changelog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniel González Lopes --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 68e3f7fd026..c951bea8d3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ * [ENHANCEMENT] Add tempodb_compaction_bytes_written metric. [#360](https://github.com/grafana/tempo/pull/360) * [ENHANCEMENT] Add tempodb_compaction_blocks_total metric. [#360](https://github.com/grafana/tempo/pull/360) * [ENHANCEMENT] Add support for S3 V2 signatures. [#352](https://github.com/grafana/tempo/pull/352) +* [ENHANCEMENT] Add support for Redis caching. [#354](https://github.com/grafana/tempo/pull/354) * [BUGFIX] Frequent errors logged by compactor regarding meta not found [#327](https://github.com/grafana/tempo/pull/327) * [BUGFIX] Fix distributors panicking on rollout [#343](https://github.com/grafana/tempo/pull/343) From 3cdc181d5c09a35b4cef956f44a940ce2cd4b3f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gonz=C3=A1lez=20Lopes?= Date: Thu, 19 Nov 2020 13:38:47 +0100 Subject: [PATCH 04/16] Add ttl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniel González Lopes --- tempodb/backend/redis/cache.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tempodb/backend/redis/cache.go b/tempodb/backend/redis/cache.go index 17969b2c618..c99b77f9428 100644 --- a/tempodb/backend/redis/cache.go +++ b/tempodb/backend/redis/cache.go @@ -35,6 +35,9 @@ func New(nextReader backend.Reader, nextWriter backend.Writer, cfg *Config, logg if cfg.ClientConfig.Timeout == 0 { cfg.ClientConfig.Timeout = 100 * time.Millisecond } + if cfg.ClientConfig.Expiration == 0 { + cfg.ClientConfig.Expiration = cfg.TTL + } client := cache.NewRedisClient(&cfg.ClientConfig) From 3858ed25d6d78b41be30c61356234ba539c27def Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gonz=C3=A1lez=20Lopes?= Date: Mon, 23 Nov 2020 17:47:19 +0100 Subject: [PATCH 05/16] Add Redis tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniel González Lopes --- go.mod | 1 + tempodb/backend/redis/cache_test.go | 169 ++ .../github.com/alicebob/gopher-json/LICENSE | 24 + .../github.com/alicebob/gopher-json/README.md | 7 + vendor/github.com/alicebob/gopher-json/doc.go | 33 + .../github.com/alicebob/gopher-json/json.go | 189 ++ .../github.com/alicebob/miniredis/.gitignore | 1 + .../github.com/alicebob/miniredis/.travis.yml | 13 + vendor/github.com/alicebob/miniredis/LICENSE | 21 + vendor/github.com/alicebob/miniredis/Makefile | 12 + .../github.com/alicebob/miniredis/README.md | 336 +++ vendor/github.com/alicebob/miniredis/check.go | 68 + .../alicebob/miniredis/cmd_connection.go | 96 + .../alicebob/miniredis/cmd_generic.go | 479 ++++ .../github.com/alicebob/miniredis/cmd_hash.go | 571 +++++ .../github.com/alicebob/miniredis/cmd_list.go | 687 +++++ .../alicebob/miniredis/cmd_scripting.go | 220 ++ .../alicebob/miniredis/cmd_server.go | 104 + .../github.com/alicebob/miniredis/cmd_set.go | 639 +++++ .../alicebob/miniredis/cmd_sorted_set.go | 1399 +++++++++++ .../alicebob/miniredis/cmd_string.go | 1075 ++++++++ .../alicebob/miniredis/cmd_transactions.go | 152 ++ vendor/github.com/alicebob/miniredis/db.go | 551 ++++ .../github.com/alicebob/miniredis/direct.go | 549 ++++ vendor/github.com/alicebob/miniredis/keys.go | 65 + vendor/github.com/alicebob/miniredis/lua.go | 189 ++ .../alicebob/miniredis/miniredis.go | 373 +++ vendor/github.com/alicebob/miniredis/redis.go | 208 ++ .../alicebob/miniredis/server/Makefile | 9 + .../alicebob/miniredis/server/proto.go | 84 + .../alicebob/miniredis/server/server.go | 242 ++ .../alicebob/miniredis/sorted_set.go | 97 + vendor/github.com/gomodule/redigo/LICENSE | 175 ++ .../gomodule/redigo/internal/commandinfo.go | 54 + .../github.com/gomodule/redigo/redis/conn.go | 673 +++++ .../github.com/gomodule/redigo/redis/doc.go | 177 ++ .../github.com/gomodule/redigo/redis/go16.go | 27 + .../github.com/gomodule/redigo/redis/go17.go | 29 + .../github.com/gomodule/redigo/redis/go18.go | 9 + .../github.com/gomodule/redigo/redis/log.go | 134 + .../github.com/gomodule/redigo/redis/pool.go | 562 +++++ .../gomodule/redigo/redis/pool17.go | 35 + .../gomodule/redigo/redis/pubsub.go | 148 ++ .../github.com/gomodule/redigo/redis/redis.go | 117 + .../github.com/gomodule/redigo/redis/reply.go | 479 ++++ .../github.com/gomodule/redigo/redis/scan.go | 585 +++++ .../gomodule/redigo/redis/script.go | 91 + vendor/github.com/yuin/gopher-lua/.travis.yml | 18 + vendor/github.com/yuin/gopher-lua/LICENSE | 21 + vendor/github.com/yuin/gopher-lua/Makefile | 10 + vendor/github.com/yuin/gopher-lua/README.rst | 887 +++++++ vendor/github.com/yuin/gopher-lua/_state.go | 2081 +++++++++++++++ vendor/github.com/yuin/gopher-lua/_vm.go | 1033 ++++++++ vendor/github.com/yuin/gopher-lua/alloc.go | 79 + vendor/github.com/yuin/gopher-lua/ast/ast.go | 29 + vendor/github.com/yuin/gopher-lua/ast/expr.go | 137 + vendor/github.com/yuin/gopher-lua/ast/misc.go | 17 + vendor/github.com/yuin/gopher-lua/ast/stmt.go | 95 + .../github.com/yuin/gopher-lua/ast/token.go | 22 + vendor/github.com/yuin/gopher-lua/auxlib.go | 460 ++++ vendor/github.com/yuin/gopher-lua/baselib.go | 592 +++++ .../github.com/yuin/gopher-lua/channellib.go | 184 ++ vendor/github.com/yuin/gopher-lua/compile.go | 1672 ++++++++++++ vendor/github.com/yuin/gopher-lua/config.go | 36 + .../yuin/gopher-lua/coroutinelib.go | 112 + vendor/github.com/yuin/gopher-lua/debuglib.go | 173 ++ vendor/github.com/yuin/gopher-lua/function.go | 193 ++ vendor/github.com/yuin/gopher-lua/go.mod | 10 + vendor/github.com/yuin/gopher-lua/go.sum | 8 + vendor/github.com/yuin/gopher-lua/iolib.go | 746 ++++++ vendor/github.com/yuin/gopher-lua/linit.go | 54 + vendor/github.com/yuin/gopher-lua/loadlib.go | 125 + vendor/github.com/yuin/gopher-lua/mathlib.go | 231 ++ vendor/github.com/yuin/gopher-lua/opcode.go | 371 +++ vendor/github.com/yuin/gopher-lua/oslib.go | 221 ++ vendor/github.com/yuin/gopher-lua/package.go | 7 + .../github.com/yuin/gopher-lua/parse/Makefile | 4 + .../github.com/yuin/gopher-lua/parse/lexer.go | 539 ++++ .../yuin/gopher-lua/parse/parser.go | 1137 +++++++++ .../yuin/gopher-lua/parse/parser.go.y | 524 ++++ vendor/github.com/yuin/gopher-lua/pm/pm.go | 638 +++++ vendor/github.com/yuin/gopher-lua/state.go | 2236 +++++++++++++++++ .../github.com/yuin/gopher-lua/stringlib.go | 448 ++++ vendor/github.com/yuin/gopher-lua/table.go | 387 +++ vendor/github.com/yuin/gopher-lua/tablelib.go | 100 + vendor/github.com/yuin/gopher-lua/utils.go | 265 ++ vendor/github.com/yuin/gopher-lua/value.go | 247 ++ vendor/github.com/yuin/gopher-lua/vm.go | 1718 +++++++++++++ vendor/modules.txt | 14 + 89 files changed, 29839 insertions(+) create mode 100644 tempodb/backend/redis/cache_test.go create mode 100644 vendor/github.com/alicebob/gopher-json/LICENSE create mode 100644 vendor/github.com/alicebob/gopher-json/README.md create mode 100644 vendor/github.com/alicebob/gopher-json/doc.go create mode 100644 vendor/github.com/alicebob/gopher-json/json.go create mode 100644 vendor/github.com/alicebob/miniredis/.gitignore create mode 100644 vendor/github.com/alicebob/miniredis/.travis.yml create mode 100644 vendor/github.com/alicebob/miniredis/LICENSE create mode 100644 vendor/github.com/alicebob/miniredis/Makefile create mode 100644 vendor/github.com/alicebob/miniredis/README.md create mode 100644 vendor/github.com/alicebob/miniredis/check.go create mode 100644 vendor/github.com/alicebob/miniredis/cmd_connection.go create mode 100644 vendor/github.com/alicebob/miniredis/cmd_generic.go create mode 100644 vendor/github.com/alicebob/miniredis/cmd_hash.go create mode 100644 vendor/github.com/alicebob/miniredis/cmd_list.go create mode 100644 vendor/github.com/alicebob/miniredis/cmd_scripting.go create mode 100644 vendor/github.com/alicebob/miniredis/cmd_server.go create mode 100644 vendor/github.com/alicebob/miniredis/cmd_set.go create mode 100644 vendor/github.com/alicebob/miniredis/cmd_sorted_set.go create mode 100644 vendor/github.com/alicebob/miniredis/cmd_string.go create mode 100644 vendor/github.com/alicebob/miniredis/cmd_transactions.go create mode 100644 vendor/github.com/alicebob/miniredis/db.go create mode 100644 vendor/github.com/alicebob/miniredis/direct.go create mode 100644 vendor/github.com/alicebob/miniredis/keys.go create mode 100644 vendor/github.com/alicebob/miniredis/lua.go create mode 100644 vendor/github.com/alicebob/miniredis/miniredis.go create mode 100644 vendor/github.com/alicebob/miniredis/redis.go create mode 100644 vendor/github.com/alicebob/miniredis/server/Makefile create mode 100644 vendor/github.com/alicebob/miniredis/server/proto.go create mode 100644 vendor/github.com/alicebob/miniredis/server/server.go create mode 100644 vendor/github.com/alicebob/miniredis/sorted_set.go create mode 100644 vendor/github.com/gomodule/redigo/LICENSE create mode 100644 vendor/github.com/gomodule/redigo/internal/commandinfo.go create mode 100644 vendor/github.com/gomodule/redigo/redis/conn.go create mode 100644 vendor/github.com/gomodule/redigo/redis/doc.go create mode 100644 vendor/github.com/gomodule/redigo/redis/go16.go create mode 100644 vendor/github.com/gomodule/redigo/redis/go17.go create mode 100644 vendor/github.com/gomodule/redigo/redis/go18.go create mode 100644 vendor/github.com/gomodule/redigo/redis/log.go create mode 100644 vendor/github.com/gomodule/redigo/redis/pool.go create mode 100644 vendor/github.com/gomodule/redigo/redis/pool17.go create mode 100644 vendor/github.com/gomodule/redigo/redis/pubsub.go create mode 100644 vendor/github.com/gomodule/redigo/redis/redis.go create mode 100644 vendor/github.com/gomodule/redigo/redis/reply.go create mode 100644 vendor/github.com/gomodule/redigo/redis/scan.go create mode 100644 vendor/github.com/gomodule/redigo/redis/script.go create mode 100644 vendor/github.com/yuin/gopher-lua/.travis.yml create mode 100644 vendor/github.com/yuin/gopher-lua/LICENSE create mode 100644 vendor/github.com/yuin/gopher-lua/Makefile create mode 100644 vendor/github.com/yuin/gopher-lua/README.rst create mode 100644 vendor/github.com/yuin/gopher-lua/_state.go create mode 100644 vendor/github.com/yuin/gopher-lua/_vm.go create mode 100644 vendor/github.com/yuin/gopher-lua/alloc.go create mode 100644 vendor/github.com/yuin/gopher-lua/ast/ast.go create mode 100644 vendor/github.com/yuin/gopher-lua/ast/expr.go create mode 100644 vendor/github.com/yuin/gopher-lua/ast/misc.go create mode 100644 vendor/github.com/yuin/gopher-lua/ast/stmt.go create mode 100644 vendor/github.com/yuin/gopher-lua/ast/token.go create mode 100644 vendor/github.com/yuin/gopher-lua/auxlib.go create mode 100644 vendor/github.com/yuin/gopher-lua/baselib.go create mode 100644 vendor/github.com/yuin/gopher-lua/channellib.go create mode 100644 vendor/github.com/yuin/gopher-lua/compile.go create mode 100644 vendor/github.com/yuin/gopher-lua/config.go create mode 100644 vendor/github.com/yuin/gopher-lua/coroutinelib.go create mode 100644 vendor/github.com/yuin/gopher-lua/debuglib.go create mode 100644 vendor/github.com/yuin/gopher-lua/function.go create mode 100644 vendor/github.com/yuin/gopher-lua/go.mod create mode 100644 vendor/github.com/yuin/gopher-lua/go.sum create mode 100644 vendor/github.com/yuin/gopher-lua/iolib.go create mode 100644 vendor/github.com/yuin/gopher-lua/linit.go create mode 100644 vendor/github.com/yuin/gopher-lua/loadlib.go create mode 100644 vendor/github.com/yuin/gopher-lua/mathlib.go create mode 100644 vendor/github.com/yuin/gopher-lua/opcode.go create mode 100644 vendor/github.com/yuin/gopher-lua/oslib.go create mode 100644 vendor/github.com/yuin/gopher-lua/package.go create mode 100644 vendor/github.com/yuin/gopher-lua/parse/Makefile create mode 100644 vendor/github.com/yuin/gopher-lua/parse/lexer.go create mode 100644 vendor/github.com/yuin/gopher-lua/parse/parser.go create mode 100644 vendor/github.com/yuin/gopher-lua/parse/parser.go.y create mode 100644 vendor/github.com/yuin/gopher-lua/pm/pm.go create mode 100644 vendor/github.com/yuin/gopher-lua/state.go create mode 100644 vendor/github.com/yuin/gopher-lua/stringlib.go create mode 100644 vendor/github.com/yuin/gopher-lua/table.go create mode 100644 vendor/github.com/yuin/gopher-lua/tablelib.go create mode 100644 vendor/github.com/yuin/gopher-lua/utils.go create mode 100644 vendor/github.com/yuin/gopher-lua/value.go create mode 100644 vendor/github.com/yuin/gopher-lua/vm.go diff --git a/go.mod b/go.mod index c0e92cd9c95..dd92fcf5e33 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.15 require ( cloud.google.com/go/storage v1.6.0 contrib.go.opencensus.io/exporter/prometheus v0.2.0 + github.com/alicebob/miniredis v2.5.0+incompatible github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b github.com/cortexproject/cortex v1.4.0 github.com/go-kit/kit v0.10.0 diff --git a/tempodb/backend/redis/cache_test.go b/tempodb/backend/redis/cache_test.go new file mode 100644 index 00000000000..9e486e6e698 --- /dev/null +++ b/tempodb/backend/redis/cache_test.go @@ -0,0 +1,169 @@ +package redis + +import ( + "context" + "testing" + + "github.com/alicebob/miniredis" + "github.com/cortexproject/cortex/pkg/chunk/cache" + "github.com/go-kit/kit/log" + "github.com/google/uuid" + "github.com/grafana/tempo/tempodb/backend" + "github.com/grafana/tempo/tempodb/encoding" + "github.com/stretchr/testify/assert" +) + +type mockReader struct { + tenants []string + blocks []uuid.UUID + meta *encoding.BlockMeta + bloom []byte + index []byte + object []byte +} + +func (m *mockReader) Tenants(ctx context.Context) ([]string, error) { + return m.tenants, nil +} +func (m *mockReader) Blocks(ctx context.Context, tenantID string) ([]uuid.UUID, error) { + return m.blocks, nil +} +func (m *mockReader) BlockMeta(ctx context.Context, blockID uuid.UUID, tenantID string) (*encoding.BlockMeta, error) { + return m.meta, nil +} +func (m *mockReader) Bloom(ctx context.Context, blockID uuid.UUID, tenantID string, shardNum int) ([]byte, error) { + return m.bloom, nil +} +func (m *mockReader) Index(ctx context.Context, blockID uuid.UUID, tenantID string) ([]byte, error) { + return m.index, nil +} +func (m *mockReader) Object(ctx context.Context, blockID uuid.UUID, tenantID string, offset uint64, buffer []byte) error { + copy(buffer, m.object) + return nil +} +func (m *mockReader) Shutdown() {} + +type mockWriter struct { +} + +func (m *mockWriter) Write(ctx context.Context, meta *encoding.BlockMeta, bBloom [][]byte, bIndex []byte, objectFilePath string) error { + return nil +} +func (m *mockWriter) WriteBlockMeta(ctx context.Context, tracker backend.AppendTracker, meta *encoding.BlockMeta, bBloom [][]byte, bIndex []byte) error { + return nil +} +func (m *mockWriter) AppendObject(ctx context.Context, tracker backend.AppendTracker, meta *encoding.BlockMeta, bObject []byte) (backend.AppendTracker, error) { + return nil, nil +} + +func TestCache(t *testing.T) { + tenantID := "test" + blockID := uuid.New() + shardNum := 0 + + tests := []struct { + name string + readerTenants []string + readerBlocks []uuid.UUID + readerMeta *encoding.BlockMeta + readerBloom []byte + readerIndex []byte + readerObject []byte + expectedTenants []string + expectedBlocks []uuid.UUID + expectedMeta *encoding.BlockMeta + expectedBloom []byte + expectedIndex []byte + expectedObject []byte + }{ + { + name: "tenants passthrough", + expectedTenants: []string{"1"}, + readerTenants: []string{"1"}, + }, + { + name: "blocks passthrough", + expectedBlocks: []uuid.UUID{blockID}, + readerBlocks: []uuid.UUID{blockID}, + }, + { + name: "index", + expectedIndex: []byte{0x01}, + readerIndex: []byte{0x01}, + }, + { + name: "bloom", + expectedBloom: []byte{0x02}, + readerBloom: []byte{0x02}, + }, + { + name: "object", + expectedObject: []byte{0x03}, + readerObject: []byte{0x03}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockR := &mockReader{ + tenants: tt.readerTenants, + blocks: tt.readerBlocks, + meta: tt.readerMeta, + bloom: tt.readerBloom, + index: tt.readerIndex, + object: tt.readerObject, + } + mockW := &mockWriter{} + mr, _ := miniredis.Run() + + mockC := cache.NewRedisClient(&cache.RedisConfig{ + Endpoint: mr.Addr(), + }) + logger := log.NewNopLogger() + rw := &readerWriter{ + client: cache.NewRedisCache("tempo", mockC, logger), + nextReader: mockR, + nextWriter: mockW, + logger: logger, + } + + ctx := context.Background() + tenants, _ := rw.Tenants(ctx) + assert.Equal(t, tt.expectedTenants, tenants) + blocks, _ := rw.Blocks(ctx, tenantID) + assert.Equal(t, tt.expectedBlocks, blocks) + meta, _ := rw.BlockMeta(ctx, blockID, tenantID) + assert.Equal(t, tt.expectedMeta, meta) + bloom, _ := rw.Bloom(ctx, blockID, tenantID, shardNum) + assert.Equal(t, tt.expectedBloom, bloom) + index, _ := rw.Index(ctx, blockID, tenantID) + assert.Equal(t, tt.expectedIndex, index) + + if tt.expectedObject != nil { + object := make([]byte, 1) + _ = rw.Object(ctx, blockID, tenantID, 0, object) + assert.Equal(t, tt.expectedObject, object) + } + + // clear reader and re-request. things should be cached! + mockR.bloom = nil + mockR.index = nil + mockR.tenants = nil + mockR.blocks = nil + mockR.meta = nil + + bloom, _ = rw.Bloom(ctx, blockID, tenantID, shardNum) + assert.Equal(t, tt.expectedBloom, bloom) + index, _ = rw.Index(ctx, blockID, tenantID) + assert.Equal(t, tt.expectedIndex, index) + + // others should be nil + tenants, _ = rw.Tenants(ctx) + assert.Nil(t, tenants) + blocks, _ = rw.Blocks(ctx, tenantID) + assert.Nil(t, blocks) + meta, _ = rw.BlockMeta(ctx, blockID, tenantID) + assert.Nil(t, tt.expectedMeta, meta) + }) + } +} diff --git a/vendor/github.com/alicebob/gopher-json/LICENSE b/vendor/github.com/alicebob/gopher-json/LICENSE new file mode 100644 index 00000000000..68a49daad8f --- /dev/null +++ b/vendor/github.com/alicebob/gopher-json/LICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/vendor/github.com/alicebob/gopher-json/README.md b/vendor/github.com/alicebob/gopher-json/README.md new file mode 100644 index 00000000000..84fe1bdf949 --- /dev/null +++ b/vendor/github.com/alicebob/gopher-json/README.md @@ -0,0 +1,7 @@ +# 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 new file mode 100644 index 00000000000..b73aeafd530 --- /dev/null +++ b/vendor/github.com/alicebob/gopher-json/doc.go @@ -0,0 +1,33 @@ +// 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/gopher-json/json.go b/vendor/github.com/alicebob/gopher-json/json.go new file mode 100644 index 00000000000..11561333de9 --- /dev/null +++ b/vendor/github.com/alicebob/gopher-json/json.go @@ -0,0 +1,189 @@ +package json + +import ( + "encoding/json" + "errors" + + "github.com/yuin/gopher-lua" +) + +// Preload adds json to the given Lua state's package.preload table. After it +// has been preloaded, it can be loaded using require: +// +// local json = require("json") +func Preload(L *lua.LState) { + L.PreloadModule("json", Loader) +} + +// Loader is the module loader function. +func Loader(L *lua.LState) int { + t := L.NewTable() + L.SetFuncs(t, api) + L.Push(t) + return 1 +} + +var api = map[string]lua.LGFunction{ + "decode": apiDecode, + "encode": apiEncode, +} + +func apiDecode(L *lua.LState) int { + if L.GetTop() != 1 { + L.Error(lua.LString("bad argument #1 to decode"), 1) + return 0 + } + str := L.CheckString(1) + + value, err := Decode(L, []byte(str)) + if err != nil { + L.Push(lua.LNil) + L.Push(lua.LString(err.Error())) + return 2 + } + L.Push(value) + return 1 +} + +func apiEncode(L *lua.LState) int { + if L.GetTop() != 1 { + L.Error(lua.LString("bad argument #1 to encode"), 1) + return 0 + } + value := L.CheckAny(1) + + data, err := Encode(value) + if err != nil { + L.Push(lua.LNil) + L.Push(lua.LString(err.Error())) + return 2 + } + L.Push(lua.LString(string(data))) + return 1 +} + +var ( + errNested = errors.New("cannot encode recursively nested tables to JSON") + errSparseArray = errors.New("cannot encode sparse array") + errInvalidKeys = errors.New("cannot encode mixed or invalid key types") +) + +type invalidTypeError lua.LValueType + +func (i invalidTypeError) Error() string { + return `cannot encode ` + lua.LValueType(i).String() + ` to JSON` +} + +// Encode returns the JSON encoding of value. +func Encode(value lua.LValue) ([]byte, error) { + return json.Marshal(jsonValue{ + LValue: value, + visited: make(map[*lua.LTable]bool), + }) +} + +type jsonValue struct { + lua.LValue + visited map[*lua.LTable]bool +} + +func (j jsonValue) MarshalJSON() (data []byte, err error) { + switch converted := j.LValue.(type) { + case lua.LBool: + data, err = json.Marshal(bool(converted)) + case lua.LNumber: + data, err = json.Marshal(float64(converted)) + case *lua.LNilType: + data = []byte(`null`) + case lua.LString: + data, err = json.Marshal(string(converted)) + case *lua.LTable: + if j.visited[converted] { + return nil, errNested + } + j.visited[converted] = true + + key, value := converted.Next(lua.LNil) + + switch key.Type() { + case lua.LTNil: // empty table + data = []byte(`[]`) + case lua.LTNumber: + arr := make([]jsonValue, 0, converted.Len()) + expectedKey := lua.LNumber(1) + for key != lua.LNil { + if key.Type() != lua.LTNumber { + err = errInvalidKeys + return + } + if expectedKey != key { + err = errSparseArray + return + } + arr = append(arr, jsonValue{value, j.visited}) + expectedKey++ + key, value = converted.Next(key) + } + data, err = json.Marshal(arr) + case lua.LTString: + obj := make(map[string]jsonValue) + for key != lua.LNil { + if key.Type() != lua.LTString { + err = errInvalidKeys + return + } + obj[key.String()] = jsonValue{value, j.visited} + key, value = converted.Next(key) + } + data, err = json.Marshal(obj) + default: + err = errInvalidKeys + } + default: + err = invalidTypeError(j.LValue.Type()) + } + return +} + +// Decode converts the JSON encoded data to Lua values. +func Decode(L *lua.LState, data []byte) (lua.LValue, error) { + var value interface{} + err := json.Unmarshal(data, &value) + if err != nil { + return nil, err + } + return DecodeValue(L, value), nil +} + +// DecodeValue converts the value to a Lua value. +// +// This function only converts values that the encoding/json package decodes to. +// All other values will return lua.LNil. +func DecodeValue(L *lua.LState, value interface{}) lua.LValue { + switch converted := value.(type) { + case bool: + return lua.LBool(converted) + case float64: + return lua.LNumber(converted) + case string: + return lua.LString(converted) + case json.Number: + return lua.LString(converted) + case []interface{}: + arr := L.CreateTable(len(converted), 0) + for _, item := range converted { + arr.Append(DecodeValue(L, item)) + } + return arr + case map[string]interface{}: + tbl := L.CreateTable(0, len(converted)) + for key, item := range converted { + tbl.RawSetH(lua.LString(key), DecodeValue(L, item)) + } + return tbl + case nil: + return lua.LNil + } + + return lua.LNil +} diff --git a/vendor/github.com/alicebob/miniredis/.gitignore b/vendor/github.com/alicebob/miniredis/.gitignore new file mode 100644 index 00000000000..a6fadca4d6e --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/.gitignore @@ -0,0 +1 @@ +/integration/redis_src/ diff --git a/vendor/github.com/alicebob/miniredis/.travis.yml b/vendor/github.com/alicebob/miniredis/.travis.yml new file mode 100644 index 00000000000..d9122d17d30 --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/.travis.yml @@ -0,0 +1,13 @@ +language: go + +before_script: + - (cd ./integration && ./get_redis.sh) + +install: go get -t + +script: make test testrace int + +sudo: false + +go: + - 1.11 diff --git a/vendor/github.com/alicebob/miniredis/LICENSE b/vendor/github.com/alicebob/miniredis/LICENSE new file mode 100644 index 00000000000..bb02657caa2 --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Harmen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/alicebob/miniredis/Makefile b/vendor/github.com/alicebob/miniredis/Makefile new file mode 100644 index 00000000000..2aa4cd2c573 --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/Makefile @@ -0,0 +1,12 @@ +.PHONY: all test testrace int + +all: test + +test: + go test ./... + +testrace: + go test -race ./... + +int: + ${MAKE} -C integration all diff --git a/vendor/github.com/alicebob/miniredis/README.md b/vendor/github.com/alicebob/miniredis/README.md new file mode 100644 index 00000000000..bfeed831401 --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/README.md @@ -0,0 +1,336 @@ +# Miniredis + +Pure Go Redis test server, used in Go unittests. + + +## + +Sometimes you want to test code which uses Redis, without making it a full-blown +integration test. +Miniredis implements (parts of) the Redis server, to be used in unittests. It +enables a simple, cheap, in-memory, Redis replacement, with a real TCP interface. Think of it as the Redis version of `net/http/httptest`. + +It saves you from using mock code, and since the redis server lives in the +test process you can query for values directly, without going through the server +stack. + +There are no dependencies on external binaries, so you can easily integrate it in automated build processes. + +## Changelog + +### 2.5.0 + +Added ZPopMin and ZPopMax + +### v2.4.6 + +support for TIME (thanks @leon-barrett and @lirao) +support for ZREVRANGEBYLEX +fix for SINTER (thanks @robstein) +updates for latest redis + +### 2.4.4 + +Fixed nil Lua return value (#43) + +### 2.4.3 + +Fixed using Lua with authenticated redis. + +### 2.4.2 + +Changed redigo import path. + +### 2.4 + +Minor cleanups. Miniredis now requires Go >= 1.9 (only for the tests. If you don't run the tests you can use an older Go version). + +### 2.3.1 + +Lua changes: added `cjson` library, and `redis.sha1hex()`. + +### 2.3 + +Added the `EVAL`, `EVALSHA`, and `SCRIPT` commands. Uses a pure Go Lua interpreter. Please open an issue if there are problems with any Lua code. + +### 2.2 + +Introduced `StartAddr()`. + +### 2.1 + +Internal cleanups. No changes in functionality. + +### 2.0 + +2.0.0 improves TTLs to be `time.Duration` values. `.Expire()` is removed and +replaced by `.TTL()`, which returns the TTL as a `time.Duration`. +This should be the change needed to upgrade: + +1.0: + + m.Expire() == 4 + +2.0: + + m.TTL() == 4 * time.Second + +Furthermore, `.SetTime()` is added to help with `EXPIREAT` commands, and `.FastForward()` is introduced to test keys expiration. + + +## Commands + +Implemented commands: + + - Connection (complete) + - AUTH -- see RequireAuth() + - ECHO + - PING + - SELECT + - QUIT + - Key + - DEL + - EXISTS + - EXPIRE + - EXPIREAT + - KEYS + - MOVE + - PERSIST + - PEXPIRE + - PEXPIREAT + - PTTL + - RENAME + - RENAMENX + - RANDOMKEY -- call math.rand.Seed(...) once before using. + - TTL + - TYPE + - SCAN + - Transactions (complete) + - DISCARD + - EXEC + - MULTI + - UNWATCH + - WATCH + - Server + - DBSIZE + - FLUSHALL + - FLUSHDB + - TIME -- returns time.Now() or value set by SetTime() + - String keys (complete) + - APPEND + - BITCOUNT + - BITOP + - BITPOS + - DECR + - DECRBY + - GET + - GETBIT + - GETRANGE + - GETSET + - INCR + - INCRBY + - INCRBYFLOAT + - MGET + - MSET + - MSETNX + - PSETEX + - SET + - SETBIT + - SETEX + - SETNX + - SETRANGE + - STRLEN + - Hash keys (complete) + - HDEL + - HEXISTS + - HGET + - HGETALL + - HINCRBY + - HINCRBYFLOAT + - HKEYS + - HLEN + - HMGET + - HMSET + - HSET + - HSETNX + - HVALS + - HSCAN + - List keys (complete) + - BLPOP + - BRPOP + - BRPOPLPUSH + - LINDEX + - LINSERT + - LLEN + - LPOP + - LPUSH + - LPUSHX + - LRANGE + - LREM + - LSET + - LTRIM + - RPOP + - RPOPLPUSH + - RPUSH + - RPUSHX + - Set keys (complete) + - SADD + - SCARD + - SDIFF + - SDIFFSTORE + - SINTER + - SINTERSTORE + - SISMEMBER + - SMEMBERS + - SMOVE + - SPOP -- call math.rand.Seed(...) once before using. + - SRANDMEMBER -- call math.rand.Seed(...) once before using. + - SREM + - SUNION + - SUNIONSTORE + - SSCAN + - Sorted Set keys (complete) + - ZADD + - ZCARD + - ZCOUNT + - ZINCRBY + - ZINTERSTORE + - ZLEXCOUNT + - ZPOPMIN + - ZPOPMAX + - ZRANGE + - ZRANGEBYLEX + - ZRANGEBYSCORE + - ZRANK + - ZREM + - ZREMRANGEBYLEX + - ZREMRANGEBYRANK + - ZREMRANGEBYSCORE + - ZREVRANGE + - ZREVRANGEBYLEX + - ZREVRANGEBYSCORE + - ZREVRANK + - ZSCORE + - ZUNIONSTORE + - ZSCAN + - Scripting + - EVAL + - EVALSHA + - SCRIPT LOAD + - SCRIPT EXISTS + - SCRIPT FLUSH + +## TTLs, key expiration, and time + +Since miniredis is intended to be used in unittests TTLs don't decrease +automatically. You can use `TTL()` to get the TTL (as a time.Duration) of a +key. It will return 0 when no TTL is set. + +`m.FastForward(d)` can be used to decrement all TTLs. All TTLs which become <= +0 will be removed. + +EXPIREAT and PEXPIREAT values will be +converted to a duration. For that you can either set m.SetTime(t) to use that +time as the base for the (P)EXPIREAT conversion, or don't call SetTime(), in +which case time.Now() will be used. + +SetTime() also sets the value returned by TIME, which defaults to time.Now(). +It is not updated by FastForward, only by SetTime. + +## Example + +``` Go +func TestSomething(t *testing.T) { + s, err := miniredis.Run() + if err != nil { + panic(err) + } + defer s.Close() + + // Optionally set some keys your code expects: + s.Set("foo", "bar") + s.HSet("some", "other", "key") + + // Run your code and see if it behaves. + // An example using the redigo library from "github.com/gomodule/redigo/redis": + c, err := redis.Dial("tcp", s.Addr()) + _, err = c.Do("SET", "foo", "bar") + + // Optionally check values in redis... + if got, err := s.Get("foo"); err != nil || got != "bar" { + t.Error("'foo' has the wrong value") + } + // ... or use a helper for that: + s.CheckGet(t, "foo", "bar") + + // TTL and expiration: + s.Set("foo", "bar") + s.SetTTL("foo", 10*time.Second) + s.FastForward(11 * time.Second) + if s.Exists("foo") { + t.Fatal("'foo' should not have existed anymore") + } +} +``` + +## Not supported + +Commands which will probably not be implemented: + + - CLUSTER (all) + - ~~CLUSTER *~~ + - ~~READONLY~~ + - ~~READWRITE~~ + - GEO (all) -- unless someone needs these + - ~~GEOADD~~ + - ~~GEODIST~~ + - ~~GEOHASH~~ + - ~~GEOPOS~~ + - ~~GEORADIUS~~ + - ~~GEORADIUSBYMEMBER~~ + - HyperLogLog (all) -- unless someone needs these + - ~~PFADD~~ + - ~~PFCOUNT~~ + - ~~PFMERGE~~ + - Key + - ~~DUMP~~ + - ~~MIGRATE~~ + - ~~OBJECT~~ + - ~~RESTORE~~ + - ~~WAIT~~ + - Pub/Sub (all) + - ~~PSUBSCRIBE~~ + - ~~PUBLISH~~ + - ~~PUBSUB~~ + - ~~PUNSUBSCRIBE~~ + - ~~SUBSCRIBE~~ + - ~~UNSUBSCRIBE~~ + - Scripting + - ~~SCRIPT DEBUG~~ + - ~~SCRIPT KILL~~ + - Server + - ~~BGSAVE~~ + - ~~BGWRITEAOF~~ + - ~~CLIENT *~~ + - ~~COMMAND *~~ + - ~~CONFIG *~~ + - ~~DEBUG *~~ + - ~~INFO~~ + - ~~LASTSAVE~~ + - ~~MONITOR~~ + - ~~ROLE~~ + - ~~SAVE~~ + - ~~SHUTDOWN~~ + - ~~SLAVEOF~~ + - ~~SLOWLOG~~ + - ~~SYNC~~ + + +## &c. + +Tests are run against Redis 5.0.3. The [./integration](./integration/) subdir +compares miniredis against a real redis instance. + + +[![Build Status](https://travis-ci.org/alicebob/miniredis.svg?branch=master)](https://travis-ci.org/alicebob/miniredis) +[![GoDoc](https://godoc.org/github.com/alicebob/miniredis?status.svg)](https://godoc.org/github.com/alicebob/miniredis) diff --git a/vendor/github.com/alicebob/miniredis/check.go b/vendor/github.com/alicebob/miniredis/check.go new file mode 100644 index 00000000000..8b42b2e0fd6 --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/check.go @@ -0,0 +1,68 @@ +package miniredis + +// 'Fail' methods. + +import ( + "fmt" + "path/filepath" + "reflect" + "runtime" + "sort" +) + +// T is implemented by Testing.T +type T interface { + Fail() +} + +// CheckGet does not call Errorf() iff there is a string key with the +// expected value. Normal use case is `m.CheckGet(t, "username", "theking")`. +func (m *Miniredis) CheckGet(t T, key, expected string) { + found, err := m.Get(key) + if err != nil { + lError(t, "GET error, key %#v: %v", key, err) + return + } + if found != expected { + lError(t, "GET error, key %#v: Expected %#v, got %#v", key, expected, found) + return + } +} + +// CheckList does not call Errorf() iff there is a list key with the +// expected values. +// Normal use case is `m.CheckGet(t, "favorite_colors", "red", "green", "infrared")`. +func (m *Miniredis) CheckList(t T, key string, expected ...string) { + found, err := m.List(key) + if err != nil { + lError(t, "List error, key %#v: %v", key, err) + return + } + if !reflect.DeepEqual(expected, found) { + lError(t, "List error, key %#v: Expected %#v, got %#v", key, expected, found) + return + } +} + +// CheckSet does not call Errorf() iff there is a set key with the +// expected values. +// Normal use case is `m.CheckSet(t, "visited", "Rome", "Stockholm", "Dublin")`. +func (m *Miniredis) CheckSet(t T, key string, expected ...string) { + found, err := m.Members(key) + if err != nil { + lError(t, "Set error, key %#v: %v", key, err) + return + } + sort.Strings(expected) + if !reflect.DeepEqual(expected, found) { + lError(t, "Set error, key %#v: Expected %#v, got %#v", key, expected, found) + return + } +} + +func lError(t T, format string, args ...interface{}) { + _, file, line, _ := runtime.Caller(2) + prefix := fmt.Sprintf("%s:%d: ", filepath.Base(file), line) + fmt.Printf(prefix+format+"\n", args...) + t.Fail() +} diff --git a/vendor/github.com/alicebob/miniredis/cmd_connection.go b/vendor/github.com/alicebob/miniredis/cmd_connection.go new file mode 100644 index 00000000000..ca648f4bfb9 --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/cmd_connection.go @@ -0,0 +1,96 @@ +// Commands from https://redis.io/commands#connection + +package miniredis + +import ( + "strconv" + + "github.com/alicebob/miniredis/server" +) + +func commandsConnection(m *Miniredis) { + m.srv.Register("AUTH", m.cmdAuth) + m.srv.Register("ECHO", m.cmdEcho) + m.srv.Register("PING", m.cmdPing) + m.srv.Register("SELECT", m.cmdSelect) + m.srv.Register("QUIT", m.cmdQuit) +} + +// PING +func (m *Miniredis) cmdPing(c *server.Peer, cmd string, args []string) { + if !m.handleAuth(c) { + return + } + c.WriteInline("PONG") +} + +// AUTH +func (m *Miniredis) cmdAuth(c *server.Peer, cmd string, args []string) { + if len(args) != 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + pw := args[0] + + m.Lock() + defer m.Unlock() + if m.password == "" { + c.WriteError("ERR Client sent AUTH, but no password is set") + return + } + if m.password != pw { + c.WriteError("ERR invalid password") + return + } + + setAuthenticated(c) + c.WriteOK() +} + +// ECHO +func (m *Miniredis) cmdEcho(c *server.Peer, cmd string, args []string) { + if len(args) != 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + msg := args[0] + c.WriteBulk(msg) +} + +// SELECT +func (m *Miniredis) cmdSelect(c *server.Peer, cmd string, args []string) { + if len(args) != 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + id, err := strconv.Atoi(args[0]) + if err != nil { + id = 0 + } + + m.Lock() + defer m.Unlock() + + ctx := getCtx(c) + ctx.selectedDB = id + + c.WriteOK() +} + +// QUIT +func (m *Miniredis) cmdQuit(c *server.Peer, cmd string, args []string) { + // QUIT isn't transactionfied and accepts any arguments. + c.WriteOK() + c.Close() +} diff --git a/vendor/github.com/alicebob/miniredis/cmd_generic.go b/vendor/github.com/alicebob/miniredis/cmd_generic.go new file mode 100644 index 00000000000..fa394790877 --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/cmd_generic.go @@ -0,0 +1,479 @@ +// Commands from https://redis.io/commands#generic + +package miniredis + +import ( + "math/rand" + "strconv" + "strings" + "time" + + "github.com/alicebob/miniredis/server" +) + +// commandsGeneric handles EXPIRE, TTL, PERSIST, &c. +func commandsGeneric(m *Miniredis) { + m.srv.Register("DEL", 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("KEYS", m.cmdKeys) + // MIGRATE + m.srv.Register("MOVE", m.cmdMove) + // OBJECT + m.srv.Register("PERSIST", m.cmdPersist) + m.srv.Register("PEXPIRE", makeCmdExpire(m, false, time.Millisecond)) + m.srv.Register("PEXPIREAT", makeCmdExpire(m, true, time.Millisecond)) + m.srv.Register("PTTL", m.cmdPTTL) + m.srv.Register("RANDOMKEY", m.cmdRandomkey) + m.srv.Register("RENAME", m.cmdRename) + m.srv.Register("RENAMENX", m.cmdRenamenx) + // RESTORE + // SORT + m.srv.Register("TTL", m.cmdTTL) + m.srv.Register("TYPE", m.cmdType) + m.srv.Register("SCAN", m.cmdScan) +} + +// generic expire command for EXPIRE, PEXPIRE, EXPIREAT, PEXPIREAT +// d is the time unit. If unix is set it'll be seen as a unixtimestamp and +// 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 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + value := args[1] + i, err := strconv.Atoi(value) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + // Key must be present. + if _, ok := db.keys[key]; !ok { + c.WriteInt(0) + return + } + if unix { + var ts time.Time + switch d { + case time.Millisecond: + ts = time.Unix(int64(i/1000), 1000000*int64(i%1000)) + case time.Second: + ts = time.Unix(int64(i), 0) + default: + panic("invalid time unit (d). Fixme!") + } + now := m.now + if now.IsZero() { + now = time.Now().UTC() + } + db.ttl[key] = ts.Sub(now) + } else { + db.ttl[key] = time.Duration(i) * d + } + db.keyVersion[key]++ + db.checkTTL(key) + c.WriteInt(1) + }) + } +} + +// TTL +func (m *Miniredis) cmdTTL(c *server.Peer, cmd string, args []string) { + if len(args) != 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + key := args[0] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if _, ok := db.keys[key]; !ok { + // No such key + c.WriteInt(-2) + return + } + + v, ok := db.ttl[key] + if !ok { + // no expire value + c.WriteInt(-1) + return + } + c.WriteInt(int(v.Seconds())) + }) +} + +// PTTL +func (m *Miniredis) cmdPTTL(c *server.Peer, cmd string, args []string) { + if len(args) != 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + key := args[0] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if _, ok := db.keys[key]; !ok { + // no such key + c.WriteInt(-2) + return + } + + v, ok := db.ttl[key] + if !ok { + // no expire value + c.WriteInt(-1) + return + } + c.WriteInt(int(v.Nanoseconds() / 1000000)) + }) +} + +// PERSIST +func (m *Miniredis) cmdPersist(c *server.Peer, cmd string, args []string) { + if len(args) != 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + key := args[0] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if _, ok := db.keys[key]; !ok { + // no such key + c.WriteInt(0) + return + } + + if _, ok := db.ttl[key]; !ok { + // no expire value + c.WriteInt(0) + return + } + delete(db.ttl, key) + db.keyVersion[key]++ + c.WriteInt(1) + }) +} + +// DEL +func (m *Miniredis) cmdDel(c *server.Peer, cmd string, args []string) { + if !m.handleAuth(c) { + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + count := 0 + for _, key := range args { + if db.exists(key) { + count++ + } + db.del(key, true) // delete expire + } + c.WriteInt(count) + }) +} + +// TYPE +func (m *Miniredis) cmdType(c *server.Peer, cmd string, args []string) { + if len(args) != 1 { + setDirty(c) + c.WriteError("usage error") + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + t, ok := db.keys[key] + if !ok { + c.WriteInline("none") + return + } + + c.WriteInline(t) + }) +} + +// EXISTS +func (m *Miniredis) cmdExists(c *server.Peer, cmd string, args []string) { + if len(args) < 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + found := 0 + for _, k := range args { + if db.exists(k) { + found++ + } + } + c.WriteInt(found) + }) +} + +// MOVE +func (m *Miniredis) cmdMove(c *server.Peer, cmd string, args []string) { + if len(args) != 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + targetDB, err := strconv.Atoi(args[1]) + if err != nil { + targetDB = 0 + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + if ctx.selectedDB == targetDB { + c.WriteError("ERR source and destination objects are the same") + return + } + db := m.db(ctx.selectedDB) + targetDB := m.db(targetDB) + + if !db.move(key, targetDB) { + c.WriteInt(0) + return + } + c.WriteInt(1) + }) +} + +// KEYS +func (m *Miniredis) cmdKeys(c *server.Peer, cmd string, args []string) { + if len(args) != 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + keys := matchKeys(db.allKeys(), key) + c.WriteLen(len(keys)) + for _, s := range keys { + c.WriteBulk(s) + } + }) +} + +// RANDOMKEY +func (m *Miniredis) cmdRandomkey(c *server.Peer, cmd string, args []string) { + if len(args) != 0 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if len(db.keys) == 0 { + c.WriteNull() + return + } + nr := rand.Intn(len(db.keys)) + for k := range db.keys { + if nr == 0 { + c.WriteBulk(k) + return + } + nr-- + } + }) +} + +// RENAME +func (m *Miniredis) cmdRename(c *server.Peer, cmd string, args []string) { + if len(args) != 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + from, to := args[0], args[1] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if !db.exists(from) { + c.WriteError(msgKeyNotFound) + return + } + + db.rename(from, to) + c.WriteOK() + }) +} + +// RENAMENX +func (m *Miniredis) cmdRenamenx(c *server.Peer, cmd string, args []string) { + if len(args) != 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + from, to := args[0], args[1] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if !db.exists(from) { + c.WriteError(msgKeyNotFound) + return + } + + if db.exists(to) { + c.WriteInt(0) + return + } + + db.rename(from, 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 + } + + cursor, err := strconv.Atoi(args[0]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidCursor) + return + } + args = args[1:] + + // 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 + if len(args) < 2 { + setDirty(c) + c.WriteError(msgSyntaxError) + return + } + if _, err := strconv.Atoi(args[1]); err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + args = args[2:] + continue + } + if strings.ToLower(args[0]) == "match" { + if len(args) < 2 { + setDirty(c) + c.WriteError(msgSyntaxError) + return + } + withMatch = true + match, args = args[1], args[2:] + continue + } + setDirty(c) + c.WriteError(msgSyntaxError) + 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 + } + + keys := db.allKeys() + if withMatch { + keys = matchKeys(keys, match) + } + + c.WriteLen(2) + c.WriteBulk("0") // no next cursor + c.WriteLen(len(keys)) + for _, k := range keys { + c.WriteBulk(k) + } + }) +} diff --git a/vendor/github.com/alicebob/miniredis/cmd_hash.go b/vendor/github.com/alicebob/miniredis/cmd_hash.go new file mode 100644 index 00000000000..1c65ebece7e --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/cmd_hash.go @@ -0,0 +1,571 @@ +// Commands from https://redis.io/commands#hash + +package miniredis + +import ( + "strconv" + "strings" + + "github.com/alicebob/miniredis/server" +) + +// commandsHash handles all hash value operations. +func commandsHash(m *Miniredis) { + m.srv.Register("HDEL", m.cmdHdel) + m.srv.Register("HEXISTS", m.cmdHexists) + m.srv.Register("HGET", m.cmdHget) + m.srv.Register("HGETALL", m.cmdHgetall) + m.srv.Register("HINCRBY", m.cmdHincrby) + m.srv.Register("HINCRBYFLOAT", m.cmdHincrbyfloat) + m.srv.Register("HKEYS", m.cmdHkeys) + m.srv.Register("HLEN", m.cmdHlen) + m.srv.Register("HMGET", m.cmdHmget) + m.srv.Register("HMSET", m.cmdHmset) + m.srv.Register("HSET", m.cmdHset) + m.srv.Register("HSETNX", m.cmdHsetnx) + m.srv.Register("HVALS", m.cmdHvals) + m.srv.Register("HSCAN", m.cmdHscan) +} + +// HSET +func (m *Miniredis) cmdHset(c *server.Peer, cmd string, args []string) { + if len(args) != 3 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key, field, value := args[0], args[1], 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" { + c.WriteError(msgWrongType) + return + } + + if db.hashSet(key, field, value) { + c.WriteInt(0) + } else { + c.WriteInt(1) + } + }) +} + +// HSETNX +func (m *Miniredis) cmdHsetnx(c *server.Peer, cmd string, args []string) { + if len(args) != 3 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key, field, value := args[0], args[1], 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" { + c.WriteError(msgWrongType) + return + } + + if _, ok := db.hashKeys[key]; !ok { + db.hashKeys[key] = map[string]string{} + db.keys[key] = "hash" + } + _, ok := db.hashKeys[key][field] + if ok { + c.WriteInt(0) + return + } + db.hashKeys[key][field] = value + db.keyVersion[key]++ + c.WriteInt(1) + }) +} + +// HMSET +func (m *Miniredis) cmdHmset(c *server.Peer, cmd string, args []string) { + if len(args) < 3 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key, args := args[0], args[1:] + if len(args)%2 != 0 { + setDirty(c) + // non-default error message + c.WriteError("ERR wrong number of arguments for HMSET") + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if t, ok := db.keys[key]; ok && t != "hash" { + c.WriteError(msgWrongType) + return + } + + for len(args) > 0 { + field, value := args[0], args[1] + args = args[2:] + db.hashSet(key, field, value) + } + c.WriteOK() + }) +} + +// HGET +func (m *Miniredis) cmdHget(c *server.Peer, cmd string, args []string) { + if len(args) != 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key, field := args[0], args[1] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + t, ok := db.keys[key] + if !ok { + c.WriteNull() + return + } + if t != "hash" { + c.WriteError(msgWrongType) + return + } + value, ok := db.hashKeys[key][field] + if !ok { + c.WriteNull() + return + } + c.WriteBulk(value) + }) +} + +// HDEL +func (m *Miniredis) cmdHdel(c *server.Peer, cmd string, args []string) { + if len(args) < 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key, fields := args[0], args[1:] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + t, ok := db.keys[key] + if !ok { + // No key is zero deleted + c.WriteInt(0) + return + } + if t != "hash" { + c.WriteError(msgWrongType) + return + } + + deleted := 0 + for _, f := range fields { + _, ok := db.hashKeys[key][f] + if !ok { + continue + } + delete(db.hashKeys[key], f) + deleted++ + } + c.WriteInt(deleted) + + // Nothing left. Remove the whole key. + if len(db.hashKeys[key]) == 0 { + db.del(key, true) + } + }) +} + +// HEXISTS +func (m *Miniredis) cmdHexists(c *server.Peer, cmd string, args []string) { + if len(args) != 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key, field := args[0], args[1] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + t, ok := db.keys[key] + if !ok { + c.WriteInt(0) + return + } + if t != "hash" { + c.WriteError(msgWrongType) + return + } + + if _, ok := db.hashKeys[key][field]; !ok { + c.WriteInt(0) + return + } + c.WriteInt(1) + }) +} + +// HGETALL +func (m *Miniredis) cmdHgetall(c *server.Peer, cmd string, args []string) { + if len(args) != 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + t, ok := db.keys[key] + if !ok { + c.WriteLen(0) + return + } + if t != "hash" { + c.WriteError(msgWrongType) + return + } + + c.WriteLen(len(db.hashKeys[key]) * 2) + for _, k := range db.hashFields(key) { + c.WriteBulk(k) + c.WriteBulk(db.hashGet(key, k)) + } + }) +} + +// HKEYS +func (m *Miniredis) cmdHkeys(c *server.Peer, cmd string, args []string) { + if len(args) != 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if !db.exists(key) { + c.WriteLen(0) + return + } + if db.t(key) != "hash" { + c.WriteError(msgWrongType) + return + } + + fields := db.hashFields(key) + c.WriteLen(len(fields)) + for _, f := range fields { + c.WriteBulk(f) + } + }) +} + +// HVALS +func (m *Miniredis) cmdHvals(c *server.Peer, cmd string, args []string) { + if len(args) != 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + t, ok := db.keys[key] + if !ok { + c.WriteLen(0) + return + } + if t != "hash" { + c.WriteError(msgWrongType) + return + } + + c.WriteLen(len(db.hashKeys[key])) + for _, v := range db.hashKeys[key] { + c.WriteBulk(v) + } + }) +} + +// HLEN +func (m *Miniredis) cmdHlen(c *server.Peer, cmd string, args []string) { + if len(args) != 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + t, ok := db.keys[key] + if !ok { + c.WriteInt(0) + return + } + if t != "hash" { + c.WriteError(msgWrongType) + return + } + + c.WriteInt(len(db.hashKeys[key])) + }) +} + +// HMGET +func (m *Miniredis) cmdHmget(c *server.Peer, cmd string, args []string) { + if len(args) < 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if t, ok := db.keys[key]; ok && t != "hash" { + c.WriteError(msgWrongType) + return + } + + f, ok := db.hashKeys[key] + if !ok { + f = map[string]string{} + } + + c.WriteLen(len(args) - 1) + for _, k := range args[1:] { + v, ok := f[k] + if !ok { + c.WriteNull() + continue + } + c.WriteBulk(v) + } + }) +} + +// HINCRBY +func (m *Miniredis) cmdHincrby(c *server.Peer, cmd string, args []string) { + if len(args) != 3 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key, field, deltas := args[0], args[1], args[2] + + delta, err := strconv.Atoi(deltas) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if t, ok := db.keys[key]; ok && t != "hash" { + c.WriteError(msgWrongType) + return + } + + v, err := db.hashIncr(key, field, delta) + if err != nil { + c.WriteError(err.Error()) + return + } + c.WriteInt(v) + }) +} + +// HINCRBYFLOAT +func (m *Miniredis) cmdHincrbyfloat(c *server.Peer, cmd string, args []string) { + if len(args) != 3 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key, field, deltas := args[0], args[1], args[2] + + delta, err := strconv.ParseFloat(deltas, 64) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidFloat) + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if t, ok := db.keys[key]; ok && t != "hash" { + c.WriteError(msgWrongType) + return + } + + v, err := db.hashIncrfloat(key, field, delta) + if err != nil { + c.WriteError(err.Error()) + return + } + c.WriteBulk(formatFloat(v)) + }) +} + +// HSCAN +func (m *Miniredis) cmdHscan(c *server.Peer, cmd string, args []string) { + if len(args) < 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + cursor, err := strconv.Atoi(args[1]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidCursor) + 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 + if len(args) < 2 { + setDirty(c) + c.WriteError(msgSyntaxError) + return + } + _, err := strconv.Atoi(args[1]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + args = args[2:] + continue + } + if strings.ToLower(args[0]) == "match" { + if len(args) < 2 { + setDirty(c) + c.WriteError(msgSyntaxError) + return + } + withMatch = true + match, args = args[1], args[2:] + continue + } + setDirty(c) + c.WriteError(msgSyntaxError) + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + // 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 + } + if db.exists(key) && db.t(key) != "hash" { + c.WriteError(ErrWrongType.Error()) + return + } + + members := db.hashFields(key) + if withMatch { + members = matchKeys(members, match) + } + + c.WriteLen(2) + c.WriteBulk("0") // no next cursor + // HSCAN gives key, values. + c.WriteLen(len(members) * 2) + for _, k := range members { + c.WriteBulk(k) + c.WriteBulk(db.hashGet(key, k)) + } + }) +} diff --git a/vendor/github.com/alicebob/miniredis/cmd_list.go b/vendor/github.com/alicebob/miniredis/cmd_list.go new file mode 100644 index 00000000000..ae543dc6c03 --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/cmd_list.go @@ -0,0 +1,687 @@ +// Commands from https://redis.io/commands#list + +package miniredis + +import ( + "strconv" + "strings" + "time" + + "github.com/alicebob/miniredis/server" +) + +type leftright int + +const ( + left leftright = iota + right +) + +// commandsList handles list commands (mostly L*) +func commandsList(m *Miniredis) { + m.srv.Register("BLPOP", m.cmdBlpop) + m.srv.Register("BRPOP", m.cmdBrpop) + m.srv.Register("BRPOPLPUSH", m.cmdBrpoplpush) + m.srv.Register("LINDEX", m.cmdLindex) + m.srv.Register("LINSERT", m.cmdLinsert) + m.srv.Register("LLEN", m.cmdLlen) + m.srv.Register("LPOP", m.cmdLpop) + m.srv.Register("LPUSH", m.cmdLpush) + m.srv.Register("LPUSHX", m.cmdLpushx) + m.srv.Register("LRANGE", m.cmdLrange) + m.srv.Register("LREM", m.cmdLrem) + m.srv.Register("LSET", m.cmdLset) + m.srv.Register("LTRIM", m.cmdLtrim) + m.srv.Register("RPOP", m.cmdRpop) + m.srv.Register("RPOPLPUSH", m.cmdRpoplpush) + m.srv.Register("RPUSH", m.cmdRpush) + m.srv.Register("RPUSHX", m.cmdRpushx) +} + +// BLPOP +func (m *Miniredis) cmdBlpop(c *server.Peer, cmd string, args []string) { + m.cmdBXpop(c, cmd, args, left) +} + +// BRPOP +func (m *Miniredis) cmdBrpop(c *server.Peer, cmd string, args []string) { + m.cmdBXpop(c, cmd, args, right) +} + +func (m *Miniredis) cmdBXpop(c *server.Peer, cmd string, args []string, lr leftright) { + if len(args) < 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + 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 + } + if timeout < 0 { + setDirty(c) + c.WriteError(msgNegTimeout) + return + } + + blocking( + m, + c, + time.Duration(timeout)*time.Second, + func(c *server.Peer, ctx *connCtx) bool { + db := m.db(ctx.selectedDB) + for _, key := range keys { + if !db.exists(key) { + continue + } + if db.t(key) != "list" { + c.WriteError(msgWrongType) + return true + } + + if len(db.listKeys[key]) == 0 { + continue + } + c.WriteLen(2) + c.WriteBulk(key) + var v string + switch lr { + case left: + v = db.listLpop(key) + case right: + v = db.listPop(key) + } + c.WriteBulk(v) + return true + } + return false + }, + func(c *server.Peer) { + // timeout + c.WriteNull() + }, + ) +} + +// LINDEX +func (m *Miniredis) cmdLindex(c *server.Peer, cmd string, args []string) { + if len(args) != 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key, offsets := args[0], args[1] + + offset, err := strconv.Atoi(offsets) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + t, ok := db.keys[key] + if !ok { + // No such key + c.WriteNull() + return + } + if t != "list" { + c.WriteError(msgWrongType) + return + } + + l := db.listKeys[key] + if offset < 0 { + offset = len(l) + offset + } + if offset < 0 || offset > len(l)-1 { + c.WriteNull() + return + } + c.WriteBulk(l[offset]) + }) +} + +// LINSERT +func (m *Miniredis) cmdLinsert(c *server.Peer, cmd string, args []string) { + if len(args) != 4 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + where := 0 + switch strings.ToLower(args[1]) { + case "before": + where = -1 + case "after": + where = +1 + default: + setDirty(c) + c.WriteError(msgSyntaxError) + return + } + pivot := args[2] + value := args[3] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + t, ok := db.keys[key] + if !ok { + // No such key + c.WriteInt(0) + return + } + if t != "list" { + c.WriteError(msgWrongType) + return + } + + l := db.listKeys[key] + for i, el := range l { + if el != pivot { + continue + } + + if where < 0 { + l = append(l[:i], append(listKey{value}, l[i:]...)...) + } else { + if i == len(l)-1 { + l = append(l, value) + } else { + l = append(l[:i+1], append(listKey{value}, l[i+1:]...)...) + } + } + db.listKeys[key] = l + db.keyVersion[key]++ + c.WriteInt(len(l)) + return + } + c.WriteInt(-1) + }) +} + +// LLEN +func (m *Miniredis) cmdLlen(c *server.Peer, cmd string, args []string) { + if len(args) != 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + t, ok := db.keys[key] + if !ok { + // No such key. That's zero length. + c.WriteInt(0) + return + } + if t != "list" { + c.WriteError(msgWrongType) + return + } + + c.WriteInt(len(db.listKeys[key])) + }) +} + +// LPOP +func (m *Miniredis) cmdLpop(c *server.Peer, cmd string, args []string) { + m.cmdXpop(c, cmd, args, left) +} + +// RPOP +func (m *Miniredis) cmdRpop(c *server.Peer, cmd string, args []string) { + m.cmdXpop(c, cmd, args, right) +} + +func (m *Miniredis) cmdXpop(c *server.Peer, cmd string, args []string, lr leftright) { + if len(args) != 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if !db.exists(key) { + // non-existing key is fine + c.WriteNull() + return + } + if db.t(key) != "list" { + c.WriteError(msgWrongType) + return + } + + var elem string + switch lr { + case left: + elem = db.listLpop(key) + case right: + elem = db.listPop(key) + } + c.WriteBulk(elem) + }) +} + +// LPUSH +func (m *Miniredis) cmdLpush(c *server.Peer, cmd string, args []string) { + m.cmdXpush(c, cmd, args, left) +} + +// RPUSH +func (m *Miniredis) cmdRpush(c *server.Peer, cmd string, args []string) { + m.cmdXpush(c, cmd, args, right) +} + +func (m *Miniredis) cmdXpush(c *server.Peer, cmd string, args []string, lr leftright) { + if len(args) < 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key, args := args[0], args[1:] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if db.exists(key) && db.t(key) != "list" { + c.WriteError(msgWrongType) + return + } + + var newLen int + for _, value := range args { + switch lr { + case left: + newLen = db.listLpush(key, value) + case right: + newLen = db.listPush(key, value) + } + } + c.WriteInt(newLen) + }) +} + +// LPUSHX +func (m *Miniredis) cmdLpushx(c *server.Peer, cmd string, args []string) { + m.cmdXpushx(c, cmd, args, left) +} + +// RPUSHX +func (m *Miniredis) cmdRpushx(c *server.Peer, cmd string, args []string) { + m.cmdXpushx(c, cmd, args, right) +} + +func (m *Miniredis) cmdXpushx(c *server.Peer, cmd string, args []string, lr leftright) { + if len(args) < 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key, args := args[0], args[1:] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if !db.exists(key) { + c.WriteInt(0) + return + } + if db.t(key) != "list" { + c.WriteError(msgWrongType) + return + } + + var newLen int + for _, value := range args { + switch lr { + case left: + newLen = db.listLpush(key, value) + case right: + newLen = db.listPush(key, value) + } + } + c.WriteInt(newLen) + }) +} + +// LRANGE +func (m *Miniredis) cmdLrange(c *server.Peer, cmd string, args []string) { + if len(args) != 3 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + start, err := strconv.Atoi(args[1]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + end, err := strconv.Atoi(args[2]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if t, ok := db.keys[key]; ok && t != "list" { + c.WriteError(msgWrongType) + return + } + + l := db.listKeys[key] + if len(l) == 0 { + c.WriteLen(0) + return + } + + rs, re := redisRange(len(l), start, end, false) + c.WriteLen(re - rs) + for _, el := range l[rs:re] { + c.WriteBulk(el) + } + }) +} + +// LREM +func (m *Miniredis) cmdLrem(c *server.Peer, cmd string, args []string) { + if len(args) != 3 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + count, err := strconv.Atoi(args[1]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + value := args[2] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if !db.exists(key) { + c.WriteInt(0) + return + } + if db.t(key) != "list" { + c.WriteError(msgWrongType) + return + } + + l := db.listKeys[key] + if count < 0 { + reverseSlice(l) + } + deleted := 0 + newL := []string{} + toDelete := len(l) + if count < 0 { + toDelete = -count + } + if count > 0 { + toDelete = count + } + for _, el := range l { + if el == value { + if toDelete > 0 { + deleted++ + toDelete-- + continue + } + } + newL = append(newL, el) + } + if count < 0 { + reverseSlice(newL) + } + if len(newL) == 0 { + db.del(key, true) + } else { + db.listKeys[key] = newL + db.keyVersion[key]++ + } + + c.WriteInt(deleted) + }) +} + +// LSET +func (m *Miniredis) cmdLset(c *server.Peer, cmd string, args []string) { + if len(args) != 3 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + index, err := strconv.Atoi(args[1]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + value := args[2] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if !db.exists(key) { + c.WriteError(msgKeyNotFound) + return + } + if db.t(key) != "list" { + c.WriteError(msgWrongType) + return + } + + l := db.listKeys[key] + if index < 0 { + index = len(l) + index + } + if index < 0 || index > len(l)-1 { + c.WriteError(msgOutOfRange) + return + } + l[index] = value + db.keyVersion[key]++ + + c.WriteOK() + }) +} + +// LTRIM +func (m *Miniredis) cmdLtrim(c *server.Peer, cmd string, args []string) { + if len(args) != 3 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + start, err := strconv.Atoi(args[1]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + end, err := strconv.Atoi(args[2]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + t, ok := db.keys[key] + if !ok { + c.WriteOK() + return + } + if t != "list" { + c.WriteError(msgWrongType) + return + } + + l := db.listKeys[key] + rs, re := redisRange(len(l), start, end, false) + l = l[rs:re] + if len(l) == 0 { + db.del(key, true) + } else { + db.listKeys[key] = l + db.keyVersion[key]++ + } + c.WriteOK() + }) +} + +// RPOPLPUSH +func (m *Miniredis) cmdRpoplpush(c *server.Peer, cmd string, args []string) { + if len(args) != 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + src, dst := args[0], args[1] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if !db.exists(src) { + c.WriteNull() + return + } + if db.t(src) != "list" || (db.exists(dst) && db.t(dst) != "list") { + c.WriteError(msgWrongType) + return + } + elem := db.listPop(src) + db.listLpush(dst, elem) + c.WriteBulk(elem) + }) +} + +// BRPOPLPUSH +func (m *Miniredis) cmdBrpoplpush(c *server.Peer, cmd string, args []string) { + if len(args) != 3 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + src := args[0] + dst := args[1] + timeout, err := strconv.Atoi(args[2]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidTimeout) + return + } + if timeout < 0 { + setDirty(c) + c.WriteError(msgNegTimeout) + return + } + + blocking( + m, + c, + time.Duration(timeout)*time.Second, + func(c *server.Peer, ctx *connCtx) bool { + db := m.db(ctx.selectedDB) + + if !db.exists(src) { + return false + } + if db.t(src) != "list" || (db.exists(dst) && db.t(dst) != "list") { + c.WriteError(msgWrongType) + return true + } + if len(db.listKeys[src]) == 0 { + return false + } + elem := db.listPop(src) + db.listLpush(dst, elem) + c.WriteBulk(elem) + return true + }, + func(c *server.Peer) { + // timeout + c.WriteNull() + }, + ) +} diff --git a/vendor/github.com/alicebob/miniredis/cmd_scripting.go b/vendor/github.com/alicebob/miniredis/cmd_scripting.go new file mode 100644 index 00000000000..296e61b9c69 --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/cmd_scripting.go @@ -0,0 +1,220 @@ +package miniredis + +import ( + "crypto/sha1" + "encoding/hex" + "fmt" + "io" + "strconv" + "strings" + + luajson "github.com/alicebob/gopher-json" + "github.com/yuin/gopher-lua" + "github.com/yuin/gopher-lua/parse" + + "github.com/alicebob/miniredis/server" +) + +func commandsScripting(m *Miniredis) { + m.srv.Register("EVAL", m.cmdEval) + m.srv.Register("EVALSHA", m.cmdEvalsha) + m.srv.Register("SCRIPT", m.cmdScript) +} + +// Execute lua. Needs to run m.Lock()ed, from within withTx(). +func (m *Miniredis) runLuaScript(c *server.Peer, script string, args []string) { + l := lua.NewState(lua.Options{SkipOpenLibs: true}) + defer l.Close() + + // Taken from the go-lua manual + for _, pair := range []struct { + n string + f lua.LGFunction + }{ + {lua.LoadLibName, lua.OpenPackage}, + {lua.BaseLibName, lua.OpenBase}, + {lua.CoroutineLibName, lua.OpenCoroutine}, + {lua.TabLibName, lua.OpenTable}, + {lua.StringLibName, lua.OpenString}, + {lua.MathLibName, lua.OpenMath}, + } { + if err := l.CallByParam(lua.P{ + Fn: l.NewFunction(pair.f), + NRet: 0, + Protect: true, + }, lua.LString(pair.n)); err != nil { + panic(err) + } + } + + luajson.Preload(l) + requireGlobal(l, "cjson", "json") + + m.Unlock() + conn := m.redigo() + m.Lock() + defer conn.Close() + + // set global variable KEYS + keysTable := l.NewTable() + keysS, args := args[0], args[1:] + keysLen, err := strconv.Atoi(keysS) + if err != nil { + c.WriteError(msgInvalidInt) + return + } + if keysLen < 0 { + c.WriteError(msgNegativeKeysNumber) + return + } + if keysLen > len(args) { + c.WriteError(msgInvalidKeysNumber) + return + } + keys, args := args[:keysLen], args[keysLen:] + for i, k := range keys { + l.RawSet(keysTable, lua.LNumber(i+1), lua.LString(k)) + } + l.SetGlobal("KEYS", keysTable) + + argvTable := l.NewTable() + for i, a := range args { + l.RawSet(argvTable, lua.LNumber(i+1), lua.LString(a)) + } + l.SetGlobal("ARGV", argvTable) + + redisFuncs := mkLuaFuncs(conn) + // Register command handlers + l.Push(l.NewFunction(func(l *lua.LState) int { + mod := l.RegisterModule("redis", redisFuncs).(*lua.LTable) + l.Push(mod) + return 1 + })) + + l.Push(lua.LString("redis")) + l.Call(1, 0) + + m.Unlock() // This runs in a transaction, but can access our db recursively + defer m.Lock() + if err := l.DoString(script); err != nil { + c.WriteError(errLuaParseError(err)) + return + } + + luaToRedis(l, c, l.Get(1)) +} + +func (m *Miniredis) cmdEval(c *server.Peer, cmd string, args []string) { + if len(args) < 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + script, args := args[0], args[1:] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + m.runLuaScript(c, script, args) + }) +} + +func (m *Miniredis) cmdEvalsha(c *server.Peer, cmd string, args []string) { + if len(args) < 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + sha, args := args[0], args[1:] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + script, ok := m.scripts[sha] + if !ok { + c.WriteError(msgNoScriptFound) + return + } + + m.runLuaScript(c, script, args) + }) +} + +func (m *Miniredis) cmdScript(c *server.Peer, cmd string, args []string) { + if len(args) < 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + subcmd, args := args[0], args[1:] + + 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 + } + script := args[0] + + if _, err := parse.Parse(strings.NewReader(script), "user_script"); err != nil { + c.WriteError(errLuaParseError(err)) + return + } + sha := sha1Hex(script) + m.scripts[sha] = script + c.WriteBulk(sha) + + case "exists": + c.WriteLen(len(args)) + for _, arg := range args { + if _, ok := m.scripts[arg]; ok { + c.WriteInt(1) + } else { + c.WriteInt(0) + } + } + + case "flush": + if len(args) != 0 { + c.WriteError(fmt.Sprintf(msgFScriptUsage, "FLUSH")) + return + } + + m.scripts = map[string]string{} + c.WriteOK() + + default: + c.WriteError(fmt.Sprintf(msgFScriptUsage, strings.ToUpper(subcmd))) + } + }) +} + +func sha1Hex(s string) string { + h := sha1.New() + io.WriteString(h, s) + return hex.EncodeToString(h.Sum(nil)) +} + +// requireGlobal imports module modName into the global namespace with the +// identifier id. panics if an error results from the function execution +func requireGlobal(l *lua.LState, id, modName string) { + if err := l.CallByParam(lua.P{ + Fn: l.GetGlobal("require"), + NRet: 1, + Protect: true, + }, lua.LString(modName)); err != nil { + panic(err) + } + mod := l.Get(-1) + l.Pop(1) + + l.SetGlobal(id, mod) +} diff --git a/vendor/github.com/alicebob/miniredis/cmd_server.go b/vendor/github.com/alicebob/miniredis/cmd_server.go new file mode 100644 index 00000000000..c021644cdbb --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/cmd_server.go @@ -0,0 +1,104 @@ +// Commands from https://redis.io/commands#server + +package miniredis + +import ( + "strconv" + "strings" + "time" + + "github.com/alicebob/miniredis/server" +) + +func commandsServer(m *Miniredis) { + m.srv.Register("DBSIZE", m.cmdDbsize) + m.srv.Register("FLUSHALL", m.cmdFlushall) + m.srv.Register("FLUSHDB", m.cmdFlushdb) + m.srv.Register("TIME", m.cmdTime) +} + +// DBSIZE +func (m *Miniredis) cmdDbsize(c *server.Peer, cmd string, args []string) { + if len(args) > 0 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + c.WriteInt(len(db.keys)) + }) +} + +// FLUSHALL +func (m *Miniredis) cmdFlushall(c *server.Peer, cmd string, args []string) { + if len(args) > 0 && strings.ToLower(args[0]) == "async" { + args = args[1:] + } + if len(args) > 0 { + setDirty(c) + c.WriteError(msgSyntaxError) + return + } + + if !m.handleAuth(c) { + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + m.flushAll() + c.WriteOK() + }) +} + +// FLUSHDB +func (m *Miniredis) cmdFlushdb(c *server.Peer, cmd string, args []string) { + if len(args) > 0 && strings.ToLower(args[0]) == "async" { + args = args[1:] + } + if len(args) > 0 { + setDirty(c) + c.WriteError(msgSyntaxError) + return + } + + if !m.handleAuth(c) { + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + m.db(ctx.selectedDB).flush() + c.WriteOK() + }) +} + +// TIME: time values are returned in string format instead of int +func (m *Miniredis) cmdTime(c *server.Peer, cmd string, args []string) { + if len(args) > 0 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + now := m.now + if now.IsZero() { + now = time.Now() + } + nanos := now.UnixNano() + seconds := nanos / 1000000000 + microseconds := (nanos / 1000) % 1000000 + + c.WriteLen(2) + c.WriteBulk(strconv.FormatInt(seconds, 10)) + c.WriteBulk(strconv.FormatInt(microseconds, 10)) + }) +} diff --git a/vendor/github.com/alicebob/miniredis/cmd_set.go b/vendor/github.com/alicebob/miniredis/cmd_set.go new file mode 100644 index 00000000000..2220cf55d7a --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/cmd_set.go @@ -0,0 +1,639 @@ +// Commands from https://redis.io/commands#set + +package miniredis + +import ( + "math/rand" + "strconv" + "strings" + + "github.com/alicebob/miniredis/server" +) + +// commandsSet handles all set value operations. +func commandsSet(m *Miniredis) { + m.srv.Register("SADD", m.cmdSadd) + m.srv.Register("SCARD", m.cmdScard) + m.srv.Register("SDIFF", m.cmdSdiff) + m.srv.Register("SDIFFSTORE", m.cmdSdiffstore) + 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("SMOVE", m.cmdSmove) + m.srv.Register("SPOP", m.cmdSpop) + m.srv.Register("SRANDMEMBER", m.cmdSrandmember) + m.srv.Register("SREM", m.cmdSrem) + m.srv.Register("SUNION", m.cmdSunion) + m.srv.Register("SUNIONSTORE", m.cmdSunionstore) + m.srv.Register("SSCAN", m.cmdSscan) +} + +// SADD +func (m *Miniredis) cmdSadd(c *server.Peer, cmd string, args []string) { + if len(args) < 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key, elems := args[0], args[1:] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if db.exists(key) && db.t(key) != "set" { + c.WriteError(ErrWrongType.Error()) + return + } + + added := db.setAdd(key, elems...) + c.WriteInt(added) + }) +} + +// SCARD +func (m *Miniredis) cmdScard(c *server.Peer, cmd string, args []string) { + if len(args) != 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if !db.exists(key) { + c.WriteInt(0) + return + } + + if db.t(key) != "set" { + c.WriteError(ErrWrongType.Error()) + return + } + + members := db.setMembers(key) + c.WriteInt(len(members)) + }) +} + +// SDIFF +func (m *Miniredis) cmdSdiff(c *server.Peer, cmd string, args []string) { + if len(args) < 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + keys := args + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + set, err := db.setDiff(keys) + if err != nil { + c.WriteError(err.Error()) + return + } + + c.WriteLen(len(set)) + for k := range set { + c.WriteBulk(k) + } + }) +} + +// SDIFFSTORE +func (m *Miniredis) cmdSdiffstore(c *server.Peer, cmd string, args []string) { + if len(args) < 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + dest, keys := args[0], args[1:] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + set, err := db.setDiff(keys) + if err != nil { + c.WriteError(err.Error()) + return + } + + db.del(dest, true) + db.setSet(dest, set) + c.WriteInt(len(set)) + }) +} + +// SINTER +func (m *Miniredis) cmdSinter(c *server.Peer, cmd string, args []string) { + if len(args) < 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + keys := args + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + set, err := db.setInter(keys) + if err != nil { + c.WriteError(err.Error()) + return + } + + c.WriteLen(len(set)) + for k := range set { + c.WriteBulk(k) + } + }) +} + +// SINTERSTORE +func (m *Miniredis) cmdSinterstore(c *server.Peer, cmd string, args []string) { + if len(args) < 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + dest, keys := args[0], args[1:] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + set, err := db.setInter(keys) + if err != nil { + c.WriteError(err.Error()) + return + } + + db.del(dest, true) + db.setSet(dest, set) + c.WriteInt(len(set)) + }) +} + +// SISMEMBER +func (m *Miniredis) cmdSismember(c *server.Peer, cmd string, args []string) { + if len(args) != 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key, value := args[0], args[1] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if !db.exists(key) { + c.WriteInt(0) + return + } + + if db.t(key) != "set" { + c.WriteError(ErrWrongType.Error()) + return + } + + if db.setIsMember(key, value) { + c.WriteInt(1) + return + } + c.WriteInt(0) + }) +} + +// SMEMBERS +func (m *Miniredis) cmdSmembers(c *server.Peer, cmd string, args []string) { + if len(args) != 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if !db.exists(key) { + c.WriteLen(0) + return + } + + if db.t(key) != "set" { + c.WriteError(ErrWrongType.Error()) + return + } + + members := db.setMembers(key) + + c.WriteLen(len(members)) + for _, elem := range members { + c.WriteBulk(elem) + } + }) +} + +// SMOVE +func (m *Miniredis) cmdSmove(c *server.Peer, cmd string, args []string) { + if len(args) != 3 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + src, dst, member := args[0], args[1], args[2] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if !db.exists(src) { + c.WriteInt(0) + return + } + + if db.t(src) != "set" { + c.WriteError(ErrWrongType.Error()) + return + } + + if db.exists(dst) && db.t(dst) != "set" { + c.WriteError(ErrWrongType.Error()) + return + } + + if !db.setIsMember(src, member) { + c.WriteInt(0) + return + } + db.setRem(src, member) + db.setAdd(dst, member) + c.WriteInt(1) + }) +} + +// SPOP +func (m *Miniredis) cmdSpop(c *server.Peer, cmd string, args []string) { + if len(args) == 0 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key, args := args[0], args[1:] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + withCount := false + count := 1 + if len(args) > 0 { + v, err := strconv.Atoi(args[0]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + count = v + withCount = true + args = args[1:] + } + if len(args) > 0 { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + + if !db.exists(key) { + if !withCount { + c.WriteNull() + return + } + c.WriteLen(0) + return + } + + if db.t(key) != "set" { + c.WriteError(ErrWrongType.Error()) + return + } + + var deleted []string + for i := 0; i < count; i++ { + members := db.setMembers(key) + if len(members) == 0 { + break + } + member := members[rand.Intn(len(members))] + db.setRem(key, member) + deleted = append(deleted, member) + } + // without `count` return a single value... + if !withCount { + if len(deleted) == 0 { + c.WriteNull() + return + } + c.WriteBulk(deleted[0]) + return + } + // ... with `count` return a list + c.WriteLen(len(deleted)) + for _, v := range deleted { + c.WriteBulk(v) + } + }) +} + +// SRANDMEMBER +func (m *Miniredis) cmdSrandmember(c *server.Peer, cmd string, args []string) { + if len(args) < 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if len(args) > 2 { + setDirty(c) + c.WriteError(msgSyntaxError) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + count := 0 + withCount := false + if len(args) == 2 { + var err error + count, err = strconv.Atoi(args[1]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + withCount = true + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if !db.exists(key) { + c.WriteNull() + return + } + + if db.t(key) != "set" { + c.WriteError(ErrWrongType.Error()) + return + } + + members := db.setMembers(key) + if count < 0 { + // Non-unique elements is allowed with negative count. + c.WriteLen(-count) + for count != 0 { + member := members[rand.Intn(len(members))] + c.WriteBulk(member) + count++ + } + return + } + + // Must be unique elements. + shuffle(members) + if count > len(members) { + count = len(members) + } + if !withCount { + c.WriteBulk(members[0]) + return + } + c.WriteLen(count) + for i := range make([]struct{}, count) { + c.WriteBulk(members[i]) + } + }) +} + +// SREM +func (m *Miniredis) cmdSrem(c *server.Peer, cmd string, args []string) { + if len(args) < 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key, fields := args[0], args[1:] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if !db.exists(key) { + c.WriteInt(0) + return + } + + if db.t(key) != "set" { + c.WriteError(ErrWrongType.Error()) + return + } + + c.WriteInt(db.setRem(key, fields...)) + }) +} + +// SUNION +func (m *Miniredis) cmdSunion(c *server.Peer, cmd string, args []string) { + if len(args) < 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + keys := args + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + set, err := db.setUnion(keys) + if err != nil { + c.WriteError(err.Error()) + return + } + + c.WriteLen(len(set)) + for k := range set { + c.WriteBulk(k) + } + }) +} + +// SUNIONSTORE +func (m *Miniredis) cmdSunionstore(c *server.Peer, cmd string, args []string) { + if len(args) < 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + dest, keys := args[0], args[1:] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + set, err := db.setUnion(keys) + if err != nil { + c.WriteError(err.Error()) + return + } + + db.del(dest, true) + db.setSet(dest, set) + c.WriteInt(len(set)) + }) +} + +// SSCAN +func (m *Miniredis) cmdSscan(c *server.Peer, cmd string, args []string) { + if len(args) < 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + cursor, err := strconv.Atoi(args[1]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidCursor) + 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 { + setDirty(c) + c.WriteError(msgSyntaxError) + return + } + _, err := strconv.Atoi(args[1]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + // We do nothing with count. + args = args[2:] + continue + } + if strings.ToLower(args[0]) == "match" { + if len(args) < 2 { + setDirty(c) + c.WriteError(msgSyntaxError) + return + } + withMatch = true + match = args[1] + args = args[2:] + continue + } + setDirty(c) + c.WriteError(msgSyntaxError) + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + // 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 + } + if db.exists(key) && db.t(key) != "set" { + c.WriteError(ErrWrongType.Error()) + return + } + + members := db.setMembers(key) + if withMatch { + members = matchKeys(members, match) + } + + c.WriteLen(2) + c.WriteBulk("0") // no next cursor + c.WriteLen(len(members)) + for _, k := range members { + c.WriteBulk(k) + } + }) +} + +// shuffle shuffles a string. Kinda. +func shuffle(m []string) { + for _ = range m { + i := rand.Intn(len(m)) + j := rand.Intn(len(m)) + m[i], m[j] = m[j], m[i] + } +} diff --git a/vendor/github.com/alicebob/miniredis/cmd_sorted_set.go b/vendor/github.com/alicebob/miniredis/cmd_sorted_set.go new file mode 100644 index 00000000000..5252b015f09 --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/cmd_sorted_set.go @@ -0,0 +1,1399 @@ +// Commands from https://redis.io/commands#sorted_set + +package miniredis + +import ( + "errors" + "sort" + "strconv" + "strings" + + "github.com/alicebob/miniredis/server" +) + +var ( + errInvalidRangeItem = errors.New(msgInvalidRangeItem) +) + +// commandsSortedSet handles all sorted set operations. +func commandsSortedSet(m *Miniredis) { + m.srv.Register("ZADD", m.cmdZadd) + 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("ZLEXCOUNT", m.cmdZlexcount) + m.srv.Register("ZRANGE", m.makeCmdZrange(false)) + m.srv.Register("ZRANGEBYLEX", m.makeCmdZrangebylex(false)) + m.srv.Register("ZRANGEBYSCORE", m.makeCmdZrangebyscore(false)) + m.srv.Register("ZRANK", m.makeCmdZrank(false)) + m.srv.Register("ZREM", m.cmdZrem) + m.srv.Register("ZREMRANGEBYLEX", m.cmdZremrangebylex) + m.srv.Register("ZREMRANGEBYRANK", m.cmdZremrangebyrank) + m.srv.Register("ZREMRANGEBYSCORE", m.cmdZremrangebyscore) + m.srv.Register("ZREVRANGE", m.makeCmdZrange(true)) + m.srv.Register("ZREVRANGEBYLEX", m.makeCmdZrangebylex(true)) + m.srv.Register("ZREVRANGEBYSCORE", m.makeCmdZrangebyscore(true)) + m.srv.Register("ZREVRANK", m.makeCmdZrank(true)) + m.srv.Register("ZSCORE", m.cmdZscore) + m.srv.Register("ZUNIONSTORE", m.cmdZunionstore) + m.srv.Register("ZSCAN", m.cmdZscan) + m.srv.Register("ZPOPMAX", m.cmdZpopmax(true)) + m.srv.Register("ZPOPMIN", m.cmdZpopmax(false)) +} + +// ZADD +func (m *Miniredis) cmdZadd(c *server.Peer, cmd string, args []string) { + if len(args) < 3 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key, args := args[0], args[1:] + var ( + nx = false + xx = false + ch = false + incr = false + elems = map[string]float64{} + ) + +outer: + for len(args) > 0 { + switch strings.ToUpper(args[0]) { + case "NX": + nx = true + args = args[1:] + continue + case "XX": + xx = true + args = args[1:] + continue + case "CH": + ch = true + args = args[1:] + continue + case "INCR": + incr = true + args = args[1:] + continue + default: + break outer + } + } + + if len(args) == 0 || len(args)%2 != 0 { + setDirty(c) + c.WriteError(msgSyntaxError) + return + } + for len(args) > 0 { + score, err := strconv.ParseFloat(args[0], 64) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidFloat) + return + } + elems[args[1]] = score + args = args[2:] + } + + if xx && nx { + setDirty(c) + c.WriteError(msgXXandNX) + return + } + + if incr && len(elems) > 1 { + setDirty(c) + c.WriteError(msgSingleElementPair) + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if db.exists(key) && db.t(key) != "zset" { + c.WriteError(ErrWrongType.Error()) + return + } + + if incr { + for member, delta := range elems { + if nx && db.ssetExists(key, member) { + c.WriteNull() + return + } + if xx && !db.ssetExists(key, member) { + c.WriteNull() + return + } + newScore := db.ssetIncrby(key, member, delta) + c.WriteBulk(formatFloat(newScore)) + } + return + } + + res := 0 + for member, score := range elems { + if nx && db.ssetExists(key, member) { + continue + } + if xx && !db.ssetExists(key, member) { + continue + } + old := db.ssetScore(key, member) + if db.ssetAdd(key, score, member) { + res++ + } else { + if ch && old != score { + // if 'CH' is specified, only count changed keys + res++ + } + } + } + c.WriteInt(res) + }) +} + +// ZCARD +func (m *Miniredis) cmdZcard(c *server.Peer, cmd string, args []string) { + if len(args) != 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if !db.exists(key) { + c.WriteInt(0) + return + } + + if db.t(key) != "zset" { + c.WriteError(ErrWrongType.Error()) + return + } + + c.WriteInt(db.ssetCard(key)) + }) +} + +// ZCOUNT +func (m *Miniredis) cmdZcount(c *server.Peer, cmd string, args []string) { + if len(args) != 3 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + min, minIncl, err := parseFloatRange(args[1]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidMinMax) + return + } + max, maxIncl, err := parseFloatRange(args[2]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidMinMax) + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if !db.exists(key) { + c.WriteInt(0) + return + } + + if db.t(key) != "zset" { + c.WriteError(ErrWrongType.Error()) + return + } + + members := db.ssetElements(key) + members = withSSRange(members, min, minIncl, max, maxIncl) + c.WriteInt(len(members)) + }) +} + +// ZINCRBY +func (m *Miniredis) cmdZincrby(c *server.Peer, cmd string, args []string) { + if len(args) != 3 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + delta, err := strconv.ParseFloat(args[1], 64) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidFloat) + return + } + 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" { + c.WriteError(msgWrongType) + return + } + newScore := db.ssetIncrby(key, member, delta) + c.WriteBulk(formatFloat(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 + } + + 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:] + + 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 { + setDirty(c) + c.WriteError("ERR weight value is not a float") + 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": + 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 + } + if db.t(key) != "zset" { + c.WriteError(msgWrongType) + return + } + for _, el := range db.ssetElements(key) { + score := el.score + if withWeights { + score *= weights[i] + } + counts[el.member]++ + old, ok := sset[el.member] + if !ok { + sset[el.member] = score + continue + } + switch aggregate { + default: + panic("Invalid aggregate") + case "sum": + sset[el.member] += score + case "min": + if score < old { + sset[el.member] = score + } + case "max": + if score > old { + sset[el.member] = score + } + } + } + } + for key, count := range counts { + if count != numKeys { + delete(sset, key) + } + } + db.ssetSet(destination, sset) + c.WriteInt(len(sset)) + }) +} + +// ZLEXCOUNT +func (m *Miniredis) cmdZlexcount(c *server.Peer, cmd string, args []string) { + if len(args) != 3 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + min, minIncl, err := parseLexrange(args[1]) + if err != nil { + setDirty(c) + c.WriteError(err.Error()) + return + } + max, maxIncl, err := parseLexrange(args[2]) + if err != nil { + setDirty(c) + c.WriteError(err.Error()) + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if !db.exists(key) { + c.WriteInt(0) + return + } + + if db.t(key) != "zset" { + c.WriteError(ErrWrongType.Error()) + return + } + + members := db.ssetMembers(key) + // Just key sort. If scores are not the same we don't care. + sort.Strings(members) + members = withLexRange(members, min, minIncl, max, maxIncl) + + c.WriteInt(len(members)) + }) +} + +// ZRANGE and ZREVRANGE +func (m *Miniredis) makeCmdZrange(reverse bool) server.Cmd { + return func(c *server.Peer, cmd string, args []string) { + if len(args) < 3 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + start, err := strconv.Atoi(args[1]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + end, err := strconv.Atoi(args[2]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + + withScores := false + if len(args) > 4 { + c.WriteError(msgSyntaxError) + return + } + if len(args) == 4 { + if strings.ToLower(args[3]) != "withscores" { + setDirty(c) + c.WriteError(msgSyntaxError) + return + } + withScores = true + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if !db.exists(key) { + c.WriteLen(0) + return + } + + if db.t(key) != "zset" { + c.WriteError(ErrWrongType.Error()) + return + } + + members := db.ssetMembers(key) + if reverse { + reverseSlice(members) + } + rs, re := redisRange(len(members), start, end, false) + if withScores { + c.WriteLen((re - rs) * 2) + } else { + c.WriteLen(re - rs) + } + for _, el := range members[rs:re] { + c.WriteBulk(el) + if withScores { + c.WriteBulk(formatFloat(db.ssetScore(key, el))) + } + } + }) + } +} + +// ZRANGEBYLEX and ZREVRANGEBYLEX +func (m *Miniredis) makeCmdZrangebylex(reverse bool) server.Cmd { + return func(c *server.Peer, cmd string, args []string) { + if len(args) < 3 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + min, minIncl, err := parseLexrange(args[1]) + if err != nil { + setDirty(c) + c.WriteError(err.Error()) + return + } + max, maxIncl, err := parseLexrange(args[2]) + if err != nil { + setDirty(c) + c.WriteError(err.Error()) + return + } + args = args[3:] + + withLimit := false + limitStart := 0 + limitEnd := 0 + for len(args) > 0 { + if strings.ToLower(args[0]) == "limit" { + withLimit = true + args = args[1:] + if len(args) < 2 { + c.WriteError(msgSyntaxError) + return + } + limitStart, err = strconv.Atoi(args[0]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + limitEnd, err = strconv.Atoi(args[1]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + args = args[2:] + continue + } + // Syntax error + setDirty(c) + c.WriteError(msgSyntaxError) + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if !db.exists(key) { + c.WriteLen(0) + return + } + + if db.t(key) != "zset" { + c.WriteError(ErrWrongType.Error()) + return + } + + members := db.ssetMembers(key) + // Just key sort. If scores are not the same we don't care. + sort.Strings(members) + if reverse { + min, max = max, min + minIncl, maxIncl = maxIncl, minIncl + } + members = withLexRange(members, min, minIncl, max, maxIncl) + if reverse { + reverseSlice(members) + } + + // Apply LIMIT ranges. That's . Unlike RANGE. + if withLimit { + if limitStart < 0 { + members = nil + } else { + if limitStart < len(members) { + members = members[limitStart:] + } else { + // out of range + members = nil + } + if limitEnd >= 0 { + if len(members) > limitEnd { + members = members[:limitEnd] + } + } + } + } + + c.WriteLen(len(members)) + for _, el := range members { + c.WriteBulk(el) + } + }) + } +} + +// ZRANGEBYSCORE and ZREVRANGEBYSCORE +func (m *Miniredis) makeCmdZrangebyscore(reverse bool) server.Cmd { + return func(c *server.Peer, cmd string, args []string) { + if len(args) < 3 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + min, minIncl, err := parseFloatRange(args[1]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidMinMax) + return + } + max, maxIncl, err := parseFloatRange(args[2]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidMinMax) + return + } + args = args[3:] + + withScores := false + withLimit := false + limitStart := 0 + limitEnd := 0 + for len(args) > 0 { + if strings.ToLower(args[0]) == "limit" { + withLimit = true + args = args[1:] + if len(args) < 2 { + c.WriteError(msgSyntaxError) + return + } + limitStart, err = strconv.Atoi(args[0]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + limitEnd, err = strconv.Atoi(args[1]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + args = args[2:] + continue + } + if strings.ToLower(args[0]) == "withscores" { + withScores = true + args = args[1:] + continue + } + setDirty(c) + c.WriteError(msgSyntaxError) + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if !db.exists(key) { + c.WriteLen(0) + return + } + + if db.t(key) != "zset" { + c.WriteError(ErrWrongType.Error()) + return + } + + members := db.ssetElements(key) + if reverse { + min, max = max, min + minIncl, maxIncl = maxIncl, minIncl + } + members = withSSRange(members, min, minIncl, max, maxIncl) + if reverse { + reverseElems(members) + } + + // Apply LIMIT ranges. That's . Unlike RANGE. + if withLimit { + if limitStart < 0 { + members = ssElems{} + } else { + if limitStart < len(members) { + members = members[limitStart:] + } else { + // out of range + members = ssElems{} + } + if limitEnd >= 0 { + if len(members) > limitEnd { + members = members[:limitEnd] + } + } + } + } + + if withScores { + c.WriteLen(len(members) * 2) + } else { + c.WriteLen(len(members)) + } + for _, el := range members { + c.WriteBulk(el.member) + if withScores { + c.WriteBulk(formatFloat(el.score)) + } + } + }) + } +} + +// ZRANK and ZREVRANK +func (m *Miniredis) makeCmdZrank(reverse bool) server.Cmd { + return func(c *server.Peer, cmd string, args []string) { + if len(args) != 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key, member := args[0], args[1] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if !db.exists(key) { + c.WriteNull() + return + } + + if db.t(key) != "zset" { + c.WriteError(ErrWrongType.Error()) + return + } + + direction := asc + if reverse { + direction = desc + } + rank, ok := db.ssetRank(key, member, direction) + if !ok { + c.WriteNull() + return + } + c.WriteInt(rank) + }) + } +} + +// ZREM +func (m *Miniredis) cmdZrem(c *server.Peer, cmd string, args []string) { + if len(args) < 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + 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.WriteInt(0) + return + } + + if db.t(key) != "zset" { + c.WriteError(ErrWrongType.Error()) + return + } + + deleted := 0 + for _, member := range members { + if db.ssetRem(key, member) { + deleted++ + } + } + c.WriteInt(deleted) + }) +} + +// ZREMRANGEBYLEX +func (m *Miniredis) cmdZremrangebylex(c *server.Peer, cmd string, args []string) { + if len(args) != 3 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + min, minIncl, err := parseLexrange(args[1]) + if err != nil { + setDirty(c) + c.WriteError(err.Error()) + return + } + max, maxIncl, err := parseLexrange(args[2]) + if err != nil { + setDirty(c) + c.WriteError(err.Error()) + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if !db.exists(key) { + c.WriteInt(0) + return + } + + if db.t(key) != "zset" { + c.WriteError(ErrWrongType.Error()) + return + } + + members := db.ssetMembers(key) + // Just key sort. If scores are not the same we don't care. + sort.Strings(members) + members = withLexRange(members, min, minIncl, max, maxIncl) + + for _, el := range members { + db.ssetRem(key, el) + } + c.WriteInt(len(members)) + }) +} + +// ZREMRANGEBYRANK +func (m *Miniredis) cmdZremrangebyrank(c *server.Peer, cmd string, args []string) { + if len(args) != 3 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + start, err := strconv.Atoi(args[1]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + end, err := strconv.Atoi(args[2]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if !db.exists(key) { + c.WriteInt(0) + return + } + + if db.t(key) != "zset" { + c.WriteError(ErrWrongType.Error()) + return + } + + members := db.ssetMembers(key) + rs, re := redisRange(len(members), start, end, false) + for _, el := range members[rs:re] { + db.ssetRem(key, el) + } + c.WriteInt(re - rs) + }) +} + +// ZREMRANGEBYSCORE +func (m *Miniredis) cmdZremrangebyscore(c *server.Peer, cmd string, args []string) { + if len(args) != 3 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + min, minIncl, err := parseFloatRange(args[1]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidMinMax) + return + } + max, maxIncl, err := parseFloatRange(args[2]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidMinMax) + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if !db.exists(key) { + c.WriteInt(0) + return + } + + if db.t(key) != "zset" { + c.WriteError(ErrWrongType.Error()) + return + } + + members := db.ssetElements(key) + members = withSSRange(members, min, minIncl, max, maxIncl) + + for _, el := range members { + db.ssetRem(key, el.member) + } + c.WriteInt(len(members)) + }) +} + +// ZSCORE +func (m *Miniredis) cmdZscore(c *server.Peer, cmd string, args []string) { + if len(args) != 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key, member := args[0], args[1] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if !db.exists(key) { + c.WriteNull() + return + } + + if db.t(key) != "zset" { + c.WriteError(ErrWrongType.Error()) + return + } + + if !db.ssetExists(key, member) { + c.WriteNull() + return + } + + c.WriteBulk(formatFloat(db.ssetScore(key, member))) + }) +} + +// parseFloatRange handles ZRANGEBYSCORE floats. They are inclusive unless the +// string starts with '(' +func parseFloatRange(s string) (float64, bool, error) { + if len(s) == 0 { + return 0, false, nil + } + inclusive := true + if s[0] == '(' { + s = s[1:] + inclusive = false + } + f, err := strconv.ParseFloat(s, 64) + return f, inclusive, err +} + +// parseLexrange handles ZRANGEBYLEX ranges. They start with '[', '(', or are +// '+' or '-'. +// Returns range, inclusive, error. +// On '+' or '-' that's just returned. +func parseLexrange(s string) (string, bool, error) { + if len(s) == 0 { + return "", false, errInvalidRangeItem + } + if s == "+" || s == "-" { + return s, false, nil + } + switch s[0] { + case '(': + return s[1:], false, nil + case '[': + return s[1:], true, nil + default: + return "", false, errInvalidRangeItem + } +} + +// withSSRange limits a list of sorted set elements by the ZRANGEBYSCORE range +// logic. +func withSSRange(members ssElems, min float64, minIncl bool, max float64, maxIncl bool) ssElems { + gt := func(a, b float64) bool { return a > b } + gteq := func(a, b float64) bool { return a >= b } + + mincmp := gt + if minIncl { + mincmp = gteq + } + for i, m := range members { + if mincmp(m.score, min) { + members = members[i:] + goto checkmax + } + } + // all elements were smaller + return nil + +checkmax: + maxcmp := gteq + if maxIncl { + maxcmp = gt + } + for i, m := range members { + if maxcmp(m.score, max) { + members = members[:i] + break + } + } + + return members +} + +// withLexRange limits a list of sorted set elements. +func withLexRange(members []string, min string, minIncl bool, max string, maxIncl bool) []string { + if max == "-" || min == "+" { + return nil + } + if min != "-" { + if minIncl { + for i, m := range members { + if m >= min { + members = members[i:] + break + } + } + } else { + // Excluding min + for i, m := range members { + if m > min { + members = members[i:] + break + } + } + } + } + if max != "+" { + if maxIncl { + for i, m := range members { + if m > max { + members = members[:i] + break + } + } + } else { + // Excluding max + for i, m := range members { + if m >= max { + members = members[:i] + break + } + } + } + } + return members +} + +// ZUNIONSTORE +func (m *Miniredis) cmdZunionstore(c *server.Peer, cmd string, args []string) { + if len(args) < 3 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + 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:] + + 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 { + setDirty(c) + c.WriteError("ERR weight value is not a float") + 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 { + default: + setDirty(c) + c.WriteError(msgSyntaxError) + return + case "sum", "min", "max": + } + args = args[2:] + default: + setDirty(c) + c.WriteError(msgSyntaxError) + return + } + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + deleteDest := true + for _, key := range keys { + if destination == key { + deleteDest = false + } + } + if deleteDest { + db.del(destination, true) + } + + sset := sortedSet{} + for i, key := range keys { + if !db.exists(key) { + continue + } + if db.t(key) != "zset" { + c.WriteError(msgWrongType) + return + } + for _, el := range db.ssetElements(key) { + score := el.score + if withWeights { + score *= weights[i] + } + old, ok := sset[el.member] + if !ok { + sset[el.member] = score + continue + } + switch aggregate { + default: + panic("Invalid aggregate") + case "sum": + sset[el.member] += score + case "min": + if score < old { + sset[el.member] = score + } + case "max": + if score > old { + sset[el.member] = score + } + } + } + } + db.ssetSet(destination, sset) + c.WriteInt(sset.card()) + }) +} + +// ZSCAN +func (m *Miniredis) cmdZscan(c *server.Peer, cmd string, args []string) { + if len(args) < 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + cursor, err := strconv.Atoi(args[1]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidCursor) + 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 { + setDirty(c) + c.WriteError(msgSyntaxError) + return + } + _, err := strconv.Atoi(args[1]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + // We do nothing with count. + args = args[2:] + continue + } + if strings.ToLower(args[0]) == "match" { + if len(args) < 2 { + setDirty(c) + c.WriteError(msgSyntaxError) + return + } + withMatch = true + match = args[1] + args = args[2:] + continue + } + setDirty(c) + c.WriteError(msgSyntaxError) + 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 + } + if db.exists(key) && db.t(key) != "zset" { + c.WriteError(ErrWrongType.Error()) + return + } + + members := db.ssetMembers(key) + if withMatch { + members = matchKeys(members, match) + } + + c.WriteLen(2) + c.WriteBulk("0") // no next cursor + // HSCAN gives key, values. + c.WriteLen(len(members) * 2) + for _, k := range members { + c.WriteBulk(k) + c.WriteBulk(formatFloat(db.ssetScore(key, k))) + } + }) +} + +// ZPOPMAX and ZPOPMIN +func (m *Miniredis) cmdZpopmax(reverse bool) 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 + } + + key := args[0] + count := 1 + var err error + if len(args) > 1 { + count, err = strconv.Atoi(args[1]) + + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + } + + withScores := true + if len(args) > 2 { + c.WriteError(msgSyntaxError) + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if !db.exists(key) { + c.WriteLen(0) + return + } + + if db.t(key) != "zset" { + c.WriteError(ErrWrongType.Error()) + return + } + + members := db.ssetMembers(key) + if reverse { + reverseSlice(members) + } + rs, re := redisRange(len(members), 0, count-1, false) + if withScores { + c.WriteLen((re - rs) * 2) + } else { + c.WriteLen(re - rs) + } + for _, el := range members[rs:re] { + c.WriteBulk(el) + if withScores { + c.WriteBulk(formatFloat(db.ssetScore(key, el))) + } + db.ssetRem(key, el) + } + }) + } +} diff --git a/vendor/github.com/alicebob/miniredis/cmd_string.go b/vendor/github.com/alicebob/miniredis/cmd_string.go new file mode 100644 index 00000000000..930da99210f --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/cmd_string.go @@ -0,0 +1,1075 @@ +// Commands from https://redis.io/commands#string + +package miniredis + +import ( + "strconv" + "strings" + "time" + + "github.com/alicebob/miniredis/server" +) + +// commandsString handles all string value operations. +func commandsString(m *Miniredis) { + m.srv.Register("APPEND", m.cmdAppend) + m.srv.Register("BITCOUNT", m.cmdBitcount) + m.srv.Register("BITOP", m.cmdBitop) + m.srv.Register("BITPOS", m.cmdBitpos) + m.srv.Register("DECRBY", m.cmdDecrby) + m.srv.Register("DECR", m.cmdDecr) + m.srv.Register("GETBIT", m.cmdGetbit) + m.srv.Register("GET", m.cmdGet) + m.srv.Register("GETRANGE", m.cmdGetrange) + m.srv.Register("GETSET", m.cmdGetset) + m.srv.Register("INCRBYFLOAT", m.cmdIncrbyfloat) + m.srv.Register("INCRBY", m.cmdIncrby) + m.srv.Register("INCR", m.cmdIncr) + m.srv.Register("MGET", m.cmdMget) + m.srv.Register("MSET", m.cmdMset) + m.srv.Register("MSETNX", m.cmdMsetnx) + m.srv.Register("PSETEX", m.cmdPsetex) + m.srv.Register("SETBIT", m.cmdSetbit) + m.srv.Register("SETEX", m.cmdSetex) + m.srv.Register("SET", m.cmdSet) + m.srv.Register("SETNX", m.cmdSetnx) + m.srv.Register("SETRANGE", m.cmdSetrange) + m.srv.Register("STRLEN", m.cmdStrlen) +} + +// SET +func (m *Miniredis) cmdSet(c *server.Peer, cmd string, args []string) { + if len(args) < 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + var ( + nx = false // set iff not exists + xx = false // set iff exists + ttl time.Duration + ) + + key, value, args := args[0], args[1], args[2:] + for len(args) > 0 { + timeUnit := time.Second + switch strings.ToUpper(args[0]) { + case "NX": + nx = true + args = args[1:] + continue + case "XX": + xx = true + args = args[1:] + continue + case "PX": + timeUnit = time.Millisecond + fallthrough + case "EX": + if len(args) < 2 { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + expire, err := strconv.Atoi(args[1]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + ttl = time.Duration(expire) * timeUnit + if ttl <= 0 { + setDirty(c) + c.WriteError(msgInvalidSETime) + return + } + + args = args[2:] + continue + default: + setDirty(c) + c.WriteError(msgSyntaxError) + return + } + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if nx { + if db.exists(key) { + c.WriteNull() + return + } + } + if xx { + if !db.exists(key) { + c.WriteNull() + return + } + } + + db.del(key, true) // be sure to remove existing values of other type keys. + // a vanilla SET clears the expire + db.stringSet(key, value) + if ttl != 0 { + db.ttl[key] = ttl + } + c.WriteOK() + }) +} + +// SETEX +func (m *Miniredis) cmdSetex(c *server.Peer, cmd string, args []string) { + if len(args) != 3 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + ttl, err := strconv.Atoi(args[1]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + if ttl <= 0 { + setDirty(c) + c.WriteError(msgInvalidSETEXTime) + return + } + 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.Second + c.WriteOK() + }) +} + +// PSETEX +func (m *Miniredis) cmdPsetex(c *server.Peer, cmd string, args []string) { + if len(args) != 3 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + ttl, err := strconv.Atoi(args[1]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + if ttl <= 0 { + setDirty(c) + c.WriteError(msgInvalidPSETEXTime) + return + } + 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 + c.WriteOK() + }) +} + +// SETNX +func (m *Miniredis) cmdSetnx(c *server.Peer, cmd string, args []string) { + if len(args) != 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key, value := args[0], args[1] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if _, ok := db.keys[key]; ok { + c.WriteInt(0) + return + } + + db.stringSet(key, value) + c.WriteInt(1) + }) +} + +// MSET +func (m *Miniredis) cmdMset(c *server.Peer, cmd string, args []string) { + if len(args) < 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + if len(args)%2 != 0 { + setDirty(c) + // non-default error message + c.WriteError("ERR wrong number of arguments for MSET") + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + for len(args) > 0 { + key, value := args[0], args[1] + args = args[2:] + + db.del(key, true) // clear TTL + db.stringSet(key, value) + } + c.WriteOK() + }) +} + +// MSETNX +func (m *Miniredis) cmdMsetnx(c *server.Peer, cmd string, args []string) { + if len(args) < 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + if len(args)%2 != 0 { + setDirty(c) + // non-default error message (yes, with 'MSET'). + c.WriteError("ERR wrong number of arguments for MSET") + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + keys := map[string]string{} + existing := false + for len(args) > 0 { + key := args[0] + value := args[1] + args = args[2:] + keys[key] = value + if _, ok := db.keys[key]; ok { + existing = true + } + } + + res := 0 + if !existing { + res = 1 + for k, v := range keys { + // Nothing to delete. That's the whole point. + db.stringSet(k, v) + } + } + c.WriteInt(res) + }) +} + +// GET +func (m *Miniredis) cmdGet(c *server.Peer, cmd string, args []string) { + if len(args) != 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if !db.exists(key) { + c.WriteNull() + return + } + if db.t(key) != "string" { + c.WriteError(msgWrongType) + return + } + + c.WriteBulk(db.stringGet(key)) + }) +} + +// GETSET +func (m *Miniredis) cmdGetset(c *server.Peer, cmd string, args []string) { + if len(args) != 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key, value := args[0], args[1] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if t, ok := db.keys[key]; ok && t != "string" { + c.WriteError(msgWrongType) + return + } + + old, ok := db.stringKeys[key] + db.stringSet(key, value) + // a GETSET clears the ttl + delete(db.ttl, key) + + if !ok { + c.WriteNull() + return + } + c.WriteBulk(old) + }) +} + +// MGET +func (m *Miniredis) cmdMget(c *server.Peer, cmd string, args []string) { + if len(args) < 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + c.WriteLen(len(args)) + for _, k := range args { + if t, ok := db.keys[k]; !ok || t != "string" { + c.WriteNull() + continue + } + v, ok := db.stringKeys[k] + if !ok { + // Should not happen, we just checked keys[] + c.WriteNull() + continue + } + c.WriteBulk(v) + } + }) +} + +// INCR +func (m *Miniredis) cmdIncr(c *server.Peer, cmd string, args []string) { + if len(args) != 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + key := args[0] + if t, ok := db.keys[key]; ok && t != "string" { + c.WriteError(msgWrongType) + return + } + v, err := db.stringIncr(key, +1) + if err != nil { + c.WriteError(err.Error()) + return + } + // Don't touch TTL + c.WriteInt(v) + }) +} + +// INCRBY +func (m *Miniredis) cmdIncrby(c *server.Peer, cmd string, args []string) { + if len(args) != 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + delta, err := strconv.Atoi(args[1]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if t, ok := db.keys[key]; ok && t != "string" { + c.WriteError(msgWrongType) + return + } + + v, err := db.stringIncr(key, delta) + if err != nil { + c.WriteError(err.Error()) + return + } + // Don't touch TTL + c.WriteInt(v) + }) +} + +// INCRBYFLOAT +func (m *Miniredis) cmdIncrbyfloat(c *server.Peer, cmd string, args []string) { + if len(args) != 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + delta, err := strconv.ParseFloat(args[1], 64) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidFloat) + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if t, ok := db.keys[key]; ok && t != "string" { + c.WriteError(msgWrongType) + return + } + + v, err := db.stringIncrfloat(key, delta) + if err != nil { + c.WriteError(err.Error()) + return + } + // Don't touch TTL + c.WriteBulk(formatFloat(v)) + }) +} + +// DECR +func (m *Miniredis) cmdDecr(c *server.Peer, cmd string, args []string) { + if len(args) != 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + key := args[0] + if t, ok := db.keys[key]; ok && t != "string" { + c.WriteError(msgWrongType) + return + } + v, err := db.stringIncr(key, -1) + if err != nil { + c.WriteError(err.Error()) + return + } + // Don't touch TTL + c.WriteInt(v) + }) +} + +// DECRBY +func (m *Miniredis) cmdDecrby(c *server.Peer, cmd string, args []string) { + if len(args) != 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + delta, err := strconv.Atoi(args[1]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if t, ok := db.keys[key]; ok && t != "string" { + c.WriteError(msgWrongType) + return + } + + v, err := db.stringIncr(key, -delta) + if err != nil { + c.WriteError(err.Error()) + return + } + // Don't touch TTL + c.WriteInt(v) + }) +} + +// STRLEN +func (m *Miniredis) cmdStrlen(c *server.Peer, cmd string, args []string) { + if len(args) != 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if t, ok := db.keys[key]; ok && t != "string" { + c.WriteError(msgWrongType) + return + } + + c.WriteInt(len(db.stringKeys[key])) + }) +} + +// APPEND +func (m *Miniredis) cmdAppend(c *server.Peer, cmd string, args []string) { + if len(args) != 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key, value := args[0], args[1] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if t, ok := db.keys[key]; ok && t != "string" { + c.WriteError(msgWrongType) + return + } + + newValue := db.stringKeys[key] + value + db.stringSet(key, newValue) + + c.WriteInt(len(newValue)) + }) +} + +// GETRANGE +func (m *Miniredis) cmdGetrange(c *server.Peer, cmd string, args []string) { + if len(args) != 3 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + start, err := strconv.Atoi(args[1]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + end, err := strconv.Atoi(args[2]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if t, ok := db.keys[key]; ok && t != "string" { + c.WriteError(msgWrongType) + return + } + + v := db.stringKeys[key] + c.WriteBulk(withRange(v, start, end)) + }) +} + +// SETRANGE +func (m *Miniredis) cmdSetrange(c *server.Peer, cmd string, args []string) { + if len(args) != 3 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + pos, err := strconv.Atoi(args[1]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + if pos < 0 { + setDirty(c) + c.WriteError("ERR offset is out of range") + return + } + 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" { + c.WriteError(msgWrongType) + return + } + + v := []byte(db.stringKeys[key]) + if len(v) < pos+len(subst) { + newV := make([]byte, pos+len(subst)) + copy(newV, v) + v = newV + } + copy(v[pos:pos+len(subst)], subst) + db.stringSet(key, string(v)) + c.WriteInt(len(v)) + }) +} + +// BITCOUNT +func (m *Miniredis) cmdBitcount(c *server.Peer, cmd string, args []string) { + if len(args) < 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + var ( + useRange = false + start, end = 0, 0 + key = args[0] + ) + args = args[1:] + if len(args) >= 2 { + useRange = true + var err error + start, err = strconv.Atoi(args[0]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + end, err = strconv.Atoi(args[1]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + args = args[2:] + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if !db.exists(key) { + c.WriteInt(0) + return + } + if db.t(key) != "string" { + c.WriteError(msgWrongType) + return + } + + // Real redis only checks after it knows the key is there and a string. + if len(args) != 0 { + c.WriteError(msgSyntaxError) + return + } + + v := db.stringKeys[key] + if useRange { + v = withRange(v, start, end) + } + + c.WriteInt(countBits([]byte(v))) + }) +} + +// BITOP +func (m *Miniredis) cmdBitop(c *server.Peer, cmd string, args []string) { + if len(args) < 3 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + var ( + op = strings.ToUpper(args[0]) + target = args[1] + 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 { + case "AND", "OR", "XOR": + first := input[0] + if t, ok := db.keys[first]; ok && t != "string" { + c.WriteError(msgWrongType) + return + } + res := []byte(db.stringKeys[first]) + for _, vk := range input[1:] { + if t, ok := db.keys[vk]; ok && t != "string" { + c.WriteError(msgWrongType) + return + } + v := db.stringKeys[vk] + cb := map[string]func(byte, byte) byte{ + "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] + res = sliceBinOp(cb, res, []byte(v)) + } + db.del(target, false) // Keep TTL + if len(res) == 0 { + db.del(target, true) + } else { + db.stringSet(target, string(res)) + } + c.WriteInt(len(res)) + case "NOT": + // NOT only takes a single argument. + if len(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" { + c.WriteError(msgWrongType) + return + } + value := []byte(db.stringKeys[key]) + for i := range value { + value[i] = ^value[i] + } + db.del(target, false) // Keep TTL + if len(value) == 0 { + db.del(target, true) + } else { + db.stringSet(target, string(value)) + } + c.WriteInt(len(value)) + default: + c.WriteError(msgSyntaxError) + } + }) +} + +// BITPOS +func (m *Miniredis) cmdBitpos(c *server.Peer, cmd string, args []string) { + if len(args) < 2 || len(args) > 4 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + bit, err := strconv.Atoi(args[1]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + var start, end int + withEnd := false + if len(args) > 2 { + start, err = strconv.Atoi(args[2]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + } + if len(args) > 3 { + end, err = strconv.Atoi(args[3]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + withEnd = true + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if t, ok := db.keys[key]; ok && t != "string" { + c.WriteError(msgWrongType) + return + } + value := db.stringKeys[key] + if start != 0 { + if start > len(value) { + start = len(value) + } + } + if withEnd { + end++ // redis end semantics. + if end < 0 { + end = len(value) + end + } + if end > len(value) { + end = len(value) + } + } else { + end = len(value) + } + if start != 0 || withEnd { + if end < start { + value = "" + } else { + value = value[start:end] + } + } + pos := bitPos([]byte(value), bit == 1) + if pos >= 0 { + pos += start * 8 + } + // Special case when looking for 0, but not when start and end are + // given. + if bit == 0 && pos == -1 && !withEnd { + pos = start*8 + len(value)*8 + } + c.WriteInt(pos) + }) +} + +// GETBIT +func (m *Miniredis) cmdGetbit(c *server.Peer, cmd string, args []string) { + if len(args) != 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + bit, err := strconv.Atoi(args[1]) + if err != nil { + setDirty(c) + c.WriteError("ERR bit offset is not an integer or out of range") + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if t, ok := db.keys[key]; ok && t != "string" { + c.WriteError(msgWrongType) + return + } + value := db.stringKeys[key] + + ourByteNr := bit / 8 + var ourByte byte + if ourByteNr > len(value)-1 { + ourByte = '\x00' + } else { + ourByte = value[ourByteNr] + } + res := 0 + if toBits(ourByte)[bit%8] { + res = 1 + } + c.WriteInt(res) + }) +} + +// SETBIT +func (m *Miniredis) cmdSetbit(c *server.Peer, cmd string, args []string) { + if len(args) != 3 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + bit, err := strconv.Atoi(args[1]) + if err != nil || 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) { + setDirty(c) + c.WriteError("ERR bit is not an integer or out of range") + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if t, ok := db.keys[key]; ok && t != "string" { + c.WriteError(msgWrongType) + return + } + value := []byte(db.stringKeys[key]) + + ourByteNr := bit / 8 + ourBitNr := bit % 8 + if ourByteNr > len(value)-1 { + // Too short. Expand. + newValue := make([]byte, ourByteNr+1) + copy(newValue, value) + value = newValue + } + old := 0 + if toBits(value[ourByteNr])[ourBitNr] { + old = 1 + } + if newBit == 0 { + value[ourByteNr] &^= 1 << uint8(7-ourBitNr) + } else { + value[ourByteNr] |= 1 << uint8(7-ourBitNr) + } + db.stringSet(key, string(value)) + + c.WriteInt(old) + }) +} + +// Redis range. both start and end can be negative. +func withRange(v string, start, end int) string { + s, e := redisRange(len(v), start, end, true /* string getrange symantics */) + return v[s:e] +} + +func countBits(v []byte) int { + count := 0 + for _, b := range []byte(v) { + for b > 0 { + count += int((b % uint8(2))) + b = b >> 1 + } + } + return count +} + +// sliceBinOp applies an operator to all slice elements, with Redis string +// padding logic. +func sliceBinOp(f func(a, b byte) byte, a, b []byte) []byte { + maxl := len(a) + if len(b) > maxl { + maxl = len(b) + } + lA := make([]byte, maxl) + copy(lA, a) + lB := make([]byte, maxl) + copy(lB, b) + res := make([]byte, maxl) + for i := range res { + res[i] = f(lA[i], lB[i]) + } + return res +} + +// Return the number of the first bit set/unset. +func bitPos(s []byte, bit bool) int { + for i, b := range s { + for j, set := range toBits(b) { + if set == bit { + return i*8 + j + } + } + } + return -1 +} + +// toBits changes a byte in 8 bools. +func toBits(s byte) [8]bool { + r := [8]bool{} + for i := range r { + if s&(uint8(1)< version { + // Abort! Abort! + stopTx(ctx) + c.WriteLen(0) + return + } + } + + c.WriteLen(len(ctx.transaction)) + for _, cb := range ctx.transaction { + cb(c, ctx) + } + // wake up anyone who waits on anything. + m.signal.Broadcast() + + stopTx(ctx) +} + +// DISCARD +func (m *Miniredis) cmdDiscard(c *server.Peer, cmd string, args []string) { + if len(args) != 0 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + ctx := getCtx(c) + if !inTx(ctx) { + c.WriteError("ERR DISCARD without MULTI") + return + } + + stopTx(ctx) + c.WriteOK() +} + +// WATCH +func (m *Miniredis) cmdWatch(c *server.Peer, cmd string, args []string) { + if len(args) == 0 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + ctx := getCtx(c) + if inTx(ctx) { + c.WriteError("ERR WATCH in MULTI") + return + } + + m.Lock() + defer m.Unlock() + db := m.db(ctx.selectedDB) + + for _, key := range args { + watch(db, ctx, key) + } + c.WriteOK() +} + +// UNWATCH +func (m *Miniredis) cmdUnwatch(c *server.Peer, cmd string, args []string) { + if len(args) != 0 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + // Doesn't matter if UNWATCH is in a TX or not. Looks like a Redis bug to me. + unwatch(getCtx(c)) + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + // Do nothing if it's called in a transaction. + c.WriteOK() + }) +} diff --git a/vendor/github.com/alicebob/miniredis/db.go b/vendor/github.com/alicebob/miniredis/db.go new file mode 100644 index 00000000000..5600afe854c --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/db.go @@ -0,0 +1,551 @@ +package miniredis + +import ( + "sort" + "strconv" + "time" +) + +func (db *RedisDB) exists(k string) bool { + _, ok := db.keys[k] + return ok +} + +// t gives the type of a key, or "" +func (db *RedisDB) t(k string) string { + return db.keys[k] +} + +// allKeys returns all keys. Sorted. +func (db *RedisDB) allKeys() []string { + res := make([]string, 0, len(db.keys)) + for k := range db.keys { + res = append(res, k) + } + sort.Strings(res) // To make things deterministic. + return res +} + +// flush removes all keys and values. +func (db *RedisDB) flush() { + db.keys = map[string]string{} + db.stringKeys = map[string]string{} + db.hashKeys = map[string]hashKey{} + db.listKeys = map[string]listKey{} + db.setKeys = map[string]setKey{} + db.sortedsetKeys = map[string]sortedSet{} + db.ttl = map[string]time.Duration{} +} + +// move something to another db. Will return ok. Or not. +func (db *RedisDB) move(key string, to *RedisDB) bool { + if _, ok := to.keys[key]; ok { + return false + } + + t, ok := db.keys[key] + if !ok { + return false + } + to.keys[key] = db.keys[key] + switch t { + case "string": + to.stringKeys[key] = db.stringKeys[key] + case "hash": + to.hashKeys[key] = db.hashKeys[key] + case "list": + to.listKeys[key] = db.listKeys[key] + case "set": + to.setKeys[key] = db.setKeys[key] + case "zset": + to.sortedsetKeys[key] = db.sortedsetKeys[key] + default: + panic("unhandled key type") + } + to.keyVersion[key]++ + if v, ok := db.ttl[key]; ok { + to.ttl[key] = v + } + db.del(key, true) + return true +} + +func (db *RedisDB) rename(from, to string) { + db.del(to, true) + switch db.t(from) { + case "string": + db.stringKeys[to] = db.stringKeys[from] + case "hash": + db.hashKeys[to] = db.hashKeys[from] + case "list": + db.listKeys[to] = db.listKeys[from] + case "set": + db.setKeys[to] = db.setKeys[from] + case "zset": + db.sortedsetKeys[to] = db.sortedsetKeys[from] + default: + panic("missing case") + } + db.keys[to] = db.keys[from] + db.keyVersion[to]++ + db.ttl[to] = db.ttl[from] + + db.del(from, true) +} + +func (db *RedisDB) del(k string, delTTL bool) { + if !db.exists(k) { + return + } + t := db.t(k) + delete(db.keys, k) + db.keyVersion[k]++ + if delTTL { + delete(db.ttl, k) + } + switch t { + case "string": + delete(db.stringKeys, k) + case "hash": + delete(db.hashKeys, k) + case "list": + delete(db.listKeys, k) + case "set": + delete(db.setKeys, k) + case "zset": + delete(db.sortedsetKeys, k) + default: + panic("Unknown key type: " + t) + } +} + +// 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" { + return "" + } + return db.stringKeys[k] +} + +// 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.stringKeys[k] = v + db.keyVersion[k]++ +} + +// change int key value +func (db *RedisDB) stringIncr(k string, delta int) (int, error) { + v := 0 + if sv, ok := db.stringKeys[k]; ok { + var err error + v, err = strconv.Atoi(sv) + if err != nil { + return 0, ErrIntValueError + } + } + v += delta + db.stringSet(k, strconv.Itoa(v)) + return v, nil +} + +// change float key value +func (db *RedisDB) stringIncrfloat(k string, delta float64) (float64, error) { + v := 0.0 + if sv, ok := db.stringKeys[k]; ok { + var err error + v, err = strconv.ParseFloat(sv, 64) + if err != nil { + return 0, ErrFloatValueError + } + } + v += delta + db.stringSet(k, formatFloat(v)) + return v, nil +} + +// listLpush is 'left push', aka unshift. Returns the new length. +func (db *RedisDB) listLpush(k, v string) int { + l, ok := db.listKeys[k] + if !ok { + db.keys[k] = "list" + } + l = append([]string{v}, l...) + db.listKeys[k] = l + db.keyVersion[k]++ + return len(l) +} + +// 'left pop', aka shift. +func (db *RedisDB) listLpop(k string) string { + l := db.listKeys[k] + el := l[0] + l = l[1:] + if len(l) == 0 { + db.del(k, true) + } else { + db.listKeys[k] = l + } + db.keyVersion[k]++ + return el +} + +func (db *RedisDB) listPush(k string, v ...string) int { + l, ok := db.listKeys[k] + if !ok { + db.keys[k] = "list" + } + l = append(l, v...) + db.listKeys[k] = l + db.keyVersion[k]++ + return len(l) +} + +func (db *RedisDB) listPop(k string) string { + l := db.listKeys[k] + el := l[len(l)-1] + l = l[:len(l)-1] + if len(l) == 0 { + db.del(k, true) + } else { + db.listKeys[k] = l + db.keyVersion[k]++ + } + return el +} + +// setset replaces a whole set. +func (db *RedisDB) setSet(k string, set setKey) { + db.keys[k] = "set" + db.setKeys[k] = set + db.keyVersion[k]++ +} + +// setadd adds members to a set. Returns nr of new keys. +func (db *RedisDB) setAdd(k string, elems ...string) int { + s, ok := db.setKeys[k] + if !ok { + s = setKey{} + db.keys[k] = "set" + } + added := 0 + for _, e := range elems { + if _, ok := s[e]; !ok { + added++ + } + s[e] = struct{}{} + } + db.setKeys[k] = s + db.keyVersion[k]++ + return added +} + +// setrem removes members from a set. Returns nr of deleted keys. +func (db *RedisDB) setRem(k string, fields ...string) int { + s, ok := db.setKeys[k] + if !ok { + return 0 + } + removed := 0 + for _, f := range fields { + if _, ok := s[f]; ok { + removed++ + delete(s, f) + } + } + if len(s) == 0 { + db.del(k, true) + } else { + db.setKeys[k] = s + } + db.keyVersion[k]++ + return removed +} + +// All members of a set. +func (db *RedisDB) setMembers(k string) []string { + set := db.setKeys[k] + members := make([]string, 0, len(set)) + for k := range set { + members = append(members, k) + } + sort.Strings(members) + return members +} + +// Is a SET value present? +func (db *RedisDB) setIsMember(k, v string) bool { + set, ok := db.setKeys[k] + if !ok { + return false + } + _, ok = set[v] + return ok +} + +// hashFields returns all (sorted) keys ('fields') for a hash key. +func (db *RedisDB) hashFields(k string) []string { + v := db.hashKeys[k] + r := make([]string, 0, len(v)) + for k := range v { + r = append(r, k) + } + sort.Strings(r) + return r +} + +// hashGet a value +func (db *RedisDB) hashGet(key, field string) string { + return db.hashKeys[key][field] +} + +// hashSet returns whether the key already existed +func (db *RedisDB) hashSet(k, f, v string) bool { + if t, ok := db.keys[k]; ok && t != "hash" { + db.del(k, true) + } + db.keys[k] = "hash" + if _, ok := db.hashKeys[k]; !ok { + db.hashKeys[k] = map[string]string{} + } + _, ok := db.hashKeys[k][f] + db.hashKeys[k][f] = v + db.keyVersion[k]++ + return ok +} + +// hashIncr changes int key value +func (db *RedisDB) hashIncr(key, field string, delta int) (int, error) { + v := 0 + if h, ok := db.hashKeys[key]; ok { + if f, ok := h[field]; ok { + var err error + v, err = strconv.Atoi(f) + if err != nil { + return 0, ErrIntValueError + } + } + } + v += delta + db.hashSet(key, field, strconv.Itoa(v)) + return v, nil +} + +// hashIncrfloat changes float key value +func (db *RedisDB) hashIncrfloat(key, field string, delta float64) (float64, error) { + v := 0.0 + if h, ok := db.hashKeys[key]; ok { + if f, ok := h[field]; ok { + var err error + v, err = strconv.ParseFloat(f, 64) + if err != nil { + return 0, ErrFloatValueError + } + } + } + v += delta + db.hashSet(key, field, formatFloat(v)) + return v, nil +} + +// sortedSet set returns a sortedSet as map +func (db *RedisDB) sortedSet(key string) map[string]float64 { + ss := db.sortedsetKeys[key] + return map[string]float64(ss) +} + +// ssetSet sets a complete sorted set. +func (db *RedisDB) ssetSet(key string, sset sortedSet) { + db.keys[key] = "zset" + db.keyVersion[key]++ + db.sortedsetKeys[key] = sset +} + +// ssetAdd adds member to a sorted set. Returns whether this was a new member. +func (db *RedisDB) ssetAdd(key string, score float64, member string) bool { + ss, ok := db.sortedsetKeys[key] + if !ok { + ss = newSortedSet() + db.keys[key] = "zset" + } + _, ok = ss[member] + ss[member] = score + db.sortedsetKeys[key] = ss + db.keyVersion[key]++ + return !ok +} + +// All members from a sorted set, ordered by score. +func (db *RedisDB) ssetMembers(key string) []string { + ss, ok := db.sortedsetKeys[key] + if !ok { + return nil + } + elems := ss.byScore(asc) + members := make([]string, 0, len(elems)) + for _, e := range elems { + members = append(members, e.member) + } + return members +} + +// All members+scores from a sorted set, ordered by score. +func (db *RedisDB) ssetElements(key string) ssElems { + ss, ok := db.sortedsetKeys[key] + if !ok { + return nil + } + return ss.byScore(asc) +} + +// ssetCard is the sorted set cardinality. +func (db *RedisDB) ssetCard(key string) int { + ss := db.sortedsetKeys[key] + return ss.card() +} + +// ssetRank is the sorted set rank. +func (db *RedisDB) ssetRank(key, member string, d direction) (int, bool) { + ss := db.sortedsetKeys[key] + return ss.rankByScore(member, d) +} + +// ssetScore is sorted set score. +func (db *RedisDB) ssetScore(key, member string) float64 { + ss := db.sortedsetKeys[key] + return ss[member] +} + +// ssetRem is sorted set key delete. +func (db *RedisDB) ssetRem(key, member string) bool { + ss := db.sortedsetKeys[key] + _, ok := ss[member] + delete(ss, member) + if len(ss) == 0 { + // Delete key on removal of last member + db.del(key, true) + } + return ok +} + +// ssetExists tells if a member exists in a sorted set. +func (db *RedisDB) ssetExists(key, member string) bool { + ss := db.sortedsetKeys[key] + _, ok := ss[member] + return ok +} + +// ssetIncrby changes float sorted set score. +func (db *RedisDB) ssetIncrby(k, m string, delta float64) float64 { + ss, ok := db.sortedsetKeys[k] + if !ok { + ss = newSortedSet() + db.keys[k] = "zset" + db.sortedsetKeys[k] = ss + } + + v, _ := ss.get(m) + v += delta + ss.set(v, m) + db.keyVersion[k]++ + return v +} + +// setDiff implements the logic behind SDIFF* +func (db *RedisDB) setDiff(keys []string) (setKey, error) { + key := keys[0] + keys = keys[1:] + if db.exists(key) && db.t(key) != "set" { + return nil, ErrWrongType + } + s := setKey{} + for k := range db.setKeys[key] { + s[k] = struct{}{} + } + for _, sk := range keys { + if !db.exists(sk) { + continue + } + if db.t(sk) != "set" { + return nil, ErrWrongType + } + for e := range db.setKeys[sk] { + delete(s, e) + } + } + return s, nil +} + +// setInter implements the logic behind SINTER* +func (db *RedisDB) setInter(keys []string) (setKey, error) { + key := keys[0] + keys = keys[1:] + if !db.exists(key) { + return setKey{}, nil + } + if db.t(key) != "set" { + return nil, ErrWrongType + } + s := setKey{} + for k := range db.setKeys[key] { + s[k] = struct{}{} + } + for _, sk := range keys { + if !db.exists(sk) { + return setKey{}, nil + } + if db.t(sk) != "set" { + return nil, ErrWrongType + } + other := db.setKeys[sk] + for e := range s { + if _, ok := other[e]; ok { + continue + } + delete(s, e) + } + } + return s, nil +} + +// setUnion implements the logic behind SUNION* +func (db *RedisDB) setUnion(keys []string) (setKey, error) { + key := keys[0] + keys = keys[1:] + if db.exists(key) && db.t(key) != "set" { + return nil, ErrWrongType + } + s := setKey{} + for k := range db.setKeys[key] { + s[k] = struct{}{} + } + for _, sk := range keys { + if !db.exists(sk) { + continue + } + if db.t(sk) != "set" { + return nil, ErrWrongType + } + for e := range db.setKeys[sk] { + s[e] = struct{}{} + } + } + return s, nil +} + +// fastForward proceeds the current timestamp with duration, works as a time machine +func (db *RedisDB) fastForward(duration time.Duration) { + for _, key := range db.allKeys() { + if value, ok := db.ttl[key]; ok { + db.ttl[key] = value - duration + db.checkTTL(key) + } + } +} + +func (db *RedisDB) checkTTL(key string) { + if v, ok := db.ttl[key]; ok && v <= 0 { + db.del(key, true) + } +} diff --git a/vendor/github.com/alicebob/miniredis/direct.go b/vendor/github.com/alicebob/miniredis/direct.go new file mode 100644 index 00000000000..ca41449fed7 --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/direct.go @@ -0,0 +1,549 @@ +package miniredis + +// Commands to modify and query our databases directly. + +import ( + "errors" + "time" +) + +var ( + // ErrKeyNotFound is returned when a key doesn't exist. + ErrKeyNotFound = errors.New(msgKeyNotFound) + // ErrWrongType when a key is not the right type. + ErrWrongType = errors.New(msgWrongType) + // ErrIntValueError can returned by INCRBY + ErrIntValueError = errors.New(msgInvalidInt) + // ErrFloatValueError can returned by INCRBYFLOAT + ErrFloatValueError = errors.New(msgInvalidFloat) +) + +// Select sets the DB id for all direct commands. +func (m *Miniredis) Select(i int) { + m.Lock() + defer m.Unlock() + m.selectedDB = i +} + +// Keys returns all keys from the selected database, sorted. +func (m *Miniredis) Keys() []string { + return m.DB(m.selectedDB).Keys() +} + +// Keys returns all keys, sorted. +func (db *RedisDB) Keys() []string { + db.master.Lock() + defer db.master.Unlock() + return db.allKeys() +} + +// FlushAll removes all keys from all databases. +func (m *Miniredis) FlushAll() { + m.Lock() + defer m.Unlock() + m.flushAll() +} + +func (m *Miniredis) flushAll() { + for _, db := range m.dbs { + db.flush() + } +} + +// FlushDB removes all keys from the selected database. +func (m *Miniredis) FlushDB() { + m.DB(m.selectedDB).FlushDB() +} + +// FlushDB removes all keys. +func (db *RedisDB) FlushDB() { + db.master.Lock() + defer db.master.Unlock() + db.flush() +} + +// Get returns string keys added with SET. +func (m *Miniredis) Get(k string) (string, error) { + return m.DB(m.selectedDB).Get(k) +} + +// Get returns a string key. +func (db *RedisDB) Get(k string) (string, error) { + db.master.Lock() + defer db.master.Unlock() + if !db.exists(k) { + return "", ErrKeyNotFound + } + if db.t(k) != "string" { + return "", ErrWrongType + } + return db.stringGet(k), nil +} + +// Set sets a string key. Removes expire. +func (m *Miniredis) Set(k, v string) error { + return m.DB(m.selectedDB).Set(k, v) +} + +// Set sets a string key. Removes expire. +// Unlike redis the key can't be an existing non-string key. +func (db *RedisDB) Set(k, v string) error { + db.master.Lock() + defer db.master.Unlock() + + if db.exists(k) && db.t(k) != "string" { + return ErrWrongType + } + db.del(k, true) // Remove expire + db.stringSet(k, v) + return nil +} + +// Incr changes a int string value by delta. +func (m *Miniredis) Incr(k string, delta int) (int, error) { + return m.DB(m.selectedDB).Incr(k, delta) +} + +// Incr changes a int string value by delta. +func (db *RedisDB) Incr(k string, delta int) (int, error) { + db.master.Lock() + defer db.master.Unlock() + + if db.exists(k) && db.t(k) != "string" { + return 0, ErrWrongType + } + + return db.stringIncr(k, delta) +} + +// Incrfloat changes a float string value by delta. +func (m *Miniredis) Incrfloat(k string, delta float64) (float64, error) { + return m.DB(m.selectedDB).Incrfloat(k, delta) +} + +// Incrfloat changes a float string value by delta. +func (db *RedisDB) Incrfloat(k string, delta float64) (float64, error) { + db.master.Lock() + defer db.master.Unlock() + + if db.exists(k) && db.t(k) != "string" { + return 0, ErrWrongType + } + + return db.stringIncrfloat(k, delta) +} + +// List returns the list k, or an error if it's not there or something else. +// This is the same as the Redis command `LRANGE 0 -1`, but you can do your own +// range-ing. +func (m *Miniredis) List(k string) ([]string, error) { + return m.DB(m.selectedDB).List(k) +} + +// List returns the list k, or an error if it's not there or something else. +// This is the same as the Redis command `LRANGE 0 -1`, but you can do your own +// range-ing. +func (db *RedisDB) List(k string) ([]string, error) { + db.master.Lock() + defer db.master.Unlock() + + if !db.exists(k) { + return nil, ErrKeyNotFound + } + if db.t(k) != "list" { + return nil, ErrWrongType + } + return db.listKeys[k], nil +} + +// Lpush is an unshift. Returns the new length. +func (m *Miniredis) Lpush(k, v string) (int, error) { + return m.DB(m.selectedDB).Lpush(k, v) +} + +// Lpush is an unshift. Returns the new length. +func (db *RedisDB) Lpush(k, v string) (int, error) { + db.master.Lock() + defer db.master.Unlock() + + if db.exists(k) && db.t(k) != "list" { + return 0, ErrWrongType + } + return db.listLpush(k, v), nil +} + +// Lpop is a shift. Returns the popped element. +func (m *Miniredis) Lpop(k string) (string, error) { + return m.DB(m.selectedDB).Lpop(k) +} + +// Lpop is a shift. Returns the popped element. +func (db *RedisDB) Lpop(k string) (string, error) { + db.master.Lock() + defer db.master.Unlock() + + if !db.exists(k) { + return "", ErrKeyNotFound + } + if db.t(k) != "list" { + return "", ErrWrongType + } + return db.listLpop(k), nil +} + +// Push add element at the end. Is called RPUSH in redis. Returns the new length. +func (m *Miniredis) Push(k string, v ...string) (int, error) { + return m.DB(m.selectedDB).Push(k, v...) +} + +// Push add element at the end. Is called RPUSH in redis. Returns the new length. +func (db *RedisDB) Push(k string, v ...string) (int, error) { + db.master.Lock() + defer db.master.Unlock() + + if db.exists(k) && db.t(k) != "list" { + return 0, ErrWrongType + } + return db.listPush(k, v...), nil +} + +// Pop removes and returns the last element. Is called RPOP in Redis. +func (m *Miniredis) Pop(k string) (string, error) { + return m.DB(m.selectedDB).Pop(k) +} + +// Pop removes and returns the last element. Is called RPOP in Redis. +func (db *RedisDB) Pop(k string) (string, error) { + db.master.Lock() + defer db.master.Unlock() + + if !db.exists(k) { + return "", ErrKeyNotFound + } + if db.t(k) != "list" { + return "", ErrWrongType + } + + return db.listPop(k), nil +} + +// SetAdd adds keys to a set. Returns the number of new keys. +func (m *Miniredis) SetAdd(k string, elems ...string) (int, error) { + return m.DB(m.selectedDB).SetAdd(k, elems...) +} + +// SetAdd adds keys to a set. Returns the number of new keys. +func (db *RedisDB) SetAdd(k string, elems ...string) (int, error) { + db.master.Lock() + defer db.master.Unlock() + if db.exists(k) && db.t(k) != "set" { + return 0, ErrWrongType + } + return db.setAdd(k, elems...), nil +} + +// Members gives all set keys. Sorted. +func (m *Miniredis) Members(k string) ([]string, error) { + return m.DB(m.selectedDB).Members(k) +} + +// Members gives all set keys. Sorted. +func (db *RedisDB) Members(k string) ([]string, error) { + db.master.Lock() + defer db.master.Unlock() + if !db.exists(k) { + return nil, ErrKeyNotFound + } + if db.t(k) != "set" { + return nil, ErrWrongType + } + return db.setMembers(k), nil +} + +// IsMember tells if value is in the set. +func (m *Miniredis) IsMember(k, v string) (bool, error) { + return m.DB(m.selectedDB).IsMember(k, v) +} + +// IsMember tells if value is in the set. +func (db *RedisDB) IsMember(k, v string) (bool, error) { + db.master.Lock() + defer db.master.Unlock() + if !db.exists(k) { + return false, ErrKeyNotFound + } + if db.t(k) != "set" { + return false, ErrWrongType + } + return db.setIsMember(k, v), nil +} + +// HKeys returns all (sorted) keys ('fields') for a hash key. +func (m *Miniredis) HKeys(k string) ([]string, error) { + return m.DB(m.selectedDB).HKeys(k) +} + +// HKeys returns all (sorted) keys ('fields') for a hash key. +func (db *RedisDB) HKeys(key string) ([]string, error) { + db.master.Lock() + defer db.master.Unlock() + if !db.exists(key) { + return nil, ErrKeyNotFound + } + if db.t(key) != "hash" { + return nil, ErrWrongType + } + return db.hashFields(key), nil +} + +// Del deletes a key and any expiration value. Returns whether there was a key. +func (m *Miniredis) Del(k string) bool { + return m.DB(m.selectedDB).Del(k) +} + +// Del deletes a key and any expiration value. Returns whether there was a key. +func (db *RedisDB) Del(k string) bool { + db.master.Lock() + defer db.master.Unlock() + if !db.exists(k) { + return false + } + db.del(k, true) + return true +} + +// TTL is the left over time to live. As set via EXPIRE, PEXPIRE, EXPIREAT, +// PEXPIREAT. +// 0 if not set. +func (m *Miniredis) TTL(k string) time.Duration { + return m.DB(m.selectedDB).TTL(k) +} + +// TTL is the left over time to live. As set via EXPIRE, PEXPIRE, EXPIREAT, +// PEXPIREAT. +// 0 if not set. +func (db *RedisDB) TTL(k string) time.Duration { + db.master.Lock() + defer db.master.Unlock() + return db.ttl[k] +} + +// SetTTL sets the TTL of a key. +func (m *Miniredis) SetTTL(k string, ttl time.Duration) { + m.DB(m.selectedDB).SetTTL(k, ttl) +} + +// SetTTL sets the time to live of a key. +func (db *RedisDB) SetTTL(k string, ttl time.Duration) { + db.master.Lock() + defer db.master.Unlock() + db.ttl[k] = ttl + db.keyVersion[k]++ +} + +// Type gives the type of a key, or "" +func (m *Miniredis) Type(k string) string { + return m.DB(m.selectedDB).Type(k) +} + +// Type gives the type of a key, or "" +func (db *RedisDB) Type(k string) string { + db.master.Lock() + defer db.master.Unlock() + return db.t(k) +} + +// Exists tells whether a key exists. +func (m *Miniredis) Exists(k string) bool { + return m.DB(m.selectedDB).Exists(k) +} + +// Exists tells whether a key exists. +func (db *RedisDB) Exists(k string) bool { + db.master.Lock() + defer db.master.Unlock() + return db.exists(k) +} + +// HGet returns hash keys added with HSET. +// This will return an empty string if the key is not set. Redis would return +// a nil. +// Returns empty string when the key is of a different type. +func (m *Miniredis) HGet(k, f string) string { + return m.DB(m.selectedDB).HGet(k, f) +} + +// HGet returns hash keys added with HSET. +// Returns empty string when the key is of a different type. +func (db *RedisDB) HGet(k, f string) string { + db.master.Lock() + defer db.master.Unlock() + h, ok := db.hashKeys[k] + if !ok { + return "" + } + return h[f] +} + +// HSet sets a hash key. +// If there is another key by the same name it will be gone. +func (m *Miniredis) HSet(k, f, v string) { + m.DB(m.selectedDB).HSet(k, f, v) +} + +// HSet sets a hash key. +// If there is another key by the same name it will be gone. +func (db *RedisDB) HSet(k, f, v string) { + db.master.Lock() + defer db.master.Unlock() + db.hashSet(k, f, v) +} + +// HDel deletes a hash key. +func (m *Miniredis) HDel(k, f string) { + m.DB(m.selectedDB).HDel(k, f) +} + +// HDel deletes a hash key. +func (db *RedisDB) HDel(k, f string) { + db.master.Lock() + defer db.master.Unlock() + db.hdel(k, f) +} + +func (db *RedisDB) hdel(k, f string) { + if _, ok := db.hashKeys[k]; !ok { + return + } + delete(db.hashKeys[k], f) + db.keyVersion[k]++ +} + +// HIncr increases a key/field by delta (int). +func (m *Miniredis) HIncr(k, f string, delta int) (int, error) { + return m.DB(m.selectedDB).HIncr(k, f, delta) +} + +// HIncr increases a key/field by delta (int). +func (db *RedisDB) HIncr(k, f string, delta int) (int, error) { + db.master.Lock() + defer db.master.Unlock() + return db.hashIncr(k, f, delta) +} + +// HIncrfloat increases a key/field by delta (float). +func (m *Miniredis) HIncrfloat(k, f string, delta float64) (float64, error) { + return m.DB(m.selectedDB).HIncrfloat(k, f, delta) +} + +// HIncrfloat increases a key/field by delta (float). +func (db *RedisDB) HIncrfloat(k, f string, delta float64) (float64, error) { + db.master.Lock() + defer db.master.Unlock() + return db.hashIncrfloat(k, f, delta) +} + +// SRem removes fields from a set. Returns number of deleted fields. +func (m *Miniredis) SRem(k string, fields ...string) (int, error) { + return m.DB(m.selectedDB).SRem(k, fields...) +} + +// SRem removes fields from a set. Returns number of deleted fields. +func (db *RedisDB) SRem(k string, fields ...string) (int, error) { + db.master.Lock() + defer db.master.Unlock() + if !db.exists(k) { + return 0, ErrKeyNotFound + } + if db.t(k) != "set" { + return 0, ErrWrongType + } + return db.setRem(k, fields...), nil +} + +// ZAdd adds a score,member to a sorted set. +func (m *Miniredis) ZAdd(k string, score float64, member string) (bool, error) { + return m.DB(m.selectedDB).ZAdd(k, score, member) +} + +// ZAdd adds a score,member to a sorted set. +func (db *RedisDB) ZAdd(k string, score float64, member string) (bool, error) { + db.master.Lock() + defer db.master.Unlock() + if db.exists(k) && db.t(k) != "zset" { + return false, ErrWrongType + } + return db.ssetAdd(k, score, member), nil +} + +// ZMembers returns all members by score +func (m *Miniredis) ZMembers(k string) ([]string, error) { + return m.DB(m.selectedDB).ZMembers(k) +} + +// ZMembers returns all members by score +func (db *RedisDB) ZMembers(k string) ([]string, error) { + db.master.Lock() + defer db.master.Unlock() + if !db.exists(k) { + return nil, ErrKeyNotFound + } + if db.t(k) != "zset" { + return nil, ErrWrongType + } + return db.ssetMembers(k), nil +} + +// SortedSet returns a raw string->float64 map. +func (m *Miniredis) SortedSet(k string) (map[string]float64, error) { + return m.DB(m.selectedDB).SortedSet(k) +} + +// SortedSet returns a raw string->float64 map. +func (db *RedisDB) SortedSet(k string) (map[string]float64, error) { + db.master.Lock() + defer db.master.Unlock() + if !db.exists(k) { + return nil, ErrKeyNotFound + } + if db.t(k) != "zset" { + return nil, ErrWrongType + } + return db.sortedSet(k), nil +} + +// ZRem deletes a member. Returns whether the was a key. +func (m *Miniredis) ZRem(k, member string) (bool, error) { + return m.DB(m.selectedDB).ZRem(k, member) +} + +// ZRem deletes a member. Returns whether the was a key. +func (db *RedisDB) ZRem(k, member string) (bool, error) { + db.master.Lock() + defer db.master.Unlock() + if !db.exists(k) { + return false, ErrKeyNotFound + } + if db.t(k) != "zset" { + return false, ErrWrongType + } + return db.ssetRem(k, member), nil +} + +// ZScore gives the score of a sorted set member. +func (m *Miniredis) ZScore(k, member string) (float64, error) { + return m.DB(m.selectedDB).ZScore(k, member) +} + +// ZScore gives the score of a sorted set member. +func (db *RedisDB) ZScore(k, member string) (float64, error) { + db.master.Lock() + defer db.master.Unlock() + if !db.exists(k) { + return 0, ErrKeyNotFound + } + if db.t(k) != "zset" { + return 0, ErrWrongType + } + return db.ssetScore(k, member), nil +} diff --git a/vendor/github.com/alicebob/miniredis/keys.go b/vendor/github.com/alicebob/miniredis/keys.go new file mode 100644 index 00000000000..b7cd98fba0d --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/keys.go @@ -0,0 +1,65 @@ +package miniredis + +// Translate the 'KEYS' argument ('foo*', 'f??', &c.) into a regexp. + +import ( + "bytes" + "regexp" +) + +// patternRE compiles a KEYS argument to a regexp. Returns nil if the given +// pattern will never match anything. +// The general strategy is to sandwich all non-meta characters between \Q...\E. +func patternRE(k string) *regexp.Regexp { + re := bytes.Buffer{} + re.WriteString(`^\Q`) + for i := 0; i < len(k); i++ { + p := k[i] + switch p { + case '*': + re.WriteString(`\E.*\Q`) + case '?': + re.WriteString(`\E.\Q`) + case '[': + charClass := bytes.Buffer{} + i++ + for ; i < len(k); i++ { + if k[i] == ']' { + break + } + if k[i] == '\\' { + if i == len(k)-1 { + // Ends with a '\'. U-huh. + return nil + } + charClass.WriteByte(k[i]) + i++ + charClass.WriteByte(k[i]) + continue + } + charClass.WriteByte(k[i]) + } + if charClass.Len() == 0 { + // '[]' is valid in Redis, but matches nothing. + return nil + } + re.WriteString(`\E[`) + re.Write(charClass.Bytes()) + re.WriteString(`]\Q`) + + case '\\': + if i == len(k)-1 { + // Ends with a '\'. U-huh. + return nil + } + // Forget the \, keep the next char. + i++ + re.WriteByte(k[i]) + continue + default: + re.WriteByte(p) + } + } + re.WriteString(`\E$`) + return regexp.MustCompile(re.String()) +} diff --git a/vendor/github.com/alicebob/miniredis/lua.go b/vendor/github.com/alicebob/miniredis/lua.go new file mode 100644 index 00000000000..a338425bfaf --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/lua.go @@ -0,0 +1,189 @@ +package miniredis + +import ( + redigo "github.com/gomodule/redigo/redis" + "github.com/yuin/gopher-lua" + + "github.com/alicebob/miniredis/server" +) + +func mkLuaFuncs(conn redigo.Conn) map[string]lua.LGFunction { + mkCall := func(failFast bool) func(l *lua.LState) int { + 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) + return 0 + } + var args []interface{} + for i := 1; i <= top; i++ { + switch a := l.Get(i).(type) { + // case lua.LBool: + // args[i-2] = a + case lua.LNumber: + // value, _ := strconv.ParseFloat(lua.LVAsString(arg), 64) + args = append(args, float64(a)) + case lua.LString: + args = append(args, string(a)) + default: + l.Error(lua.LString("Lua redis() command arguments must be strings or integers"), 1) + return 0 + } + } + cmd, ok := args[0].(string) + if !ok { + l.Error(lua.LString("Unknown Redis command called from Lua script"), 1) + return 0 + } + res, err := conn.Do(cmd, args[1:]...) + if err != nil { + if failFast { + // call() mode + l.Error(lua.LString(err.Error()), 1) + return 0 + } + // pcall() mode + l.Push(lua.LNil) + return 1 + } + + if res == nil { + l.Push(lua.LFalse) + } else { + switch r := res.(type) { + case int64: + l.Push(lua.LNumber(r)) + case []uint8: + l.Push(lua.LString(string(r))) + case []interface{}: + l.Push(redisToLua(l, r)) + case string: + l.Push(lua.LString(r)) + default: + panic("type not handled") + } + } + return 1 + } + } + + return map[string]lua.LGFunction{ + "call": mkCall(true), + "pcall": mkCall(false), + "error_reply": func(l *lua.LState) int { + msg := l.CheckString(1) + res := &lua.LTable{} + res.RawSetString("err", lua.LString(msg)) + l.Push(res) + return 1 + }, + "status_reply": func(l *lua.LState) int { + msg := l.CheckString(1) + res := &lua.LTable{} + res.RawSetString("ok", lua.LString(msg)) + l.Push(res) + return 1 + }, + "sha1hex": func(l *lua.LState) int { + top := l.GetTop() + if top != 1 { + l.Error(lua.LString("wrong number of arguments"), 1) + return 0 + } + msg := lua.LVAsString(l.Get(1)) + l.Push(lua.LString(sha1Hex(msg))) + return 1 + }, + "replicate_commands": func(l *lua.LState) int { + // ignored + return 1 + }, + } +} + +func luaToRedis(l *lua.LState, c *server.Peer, value lua.LValue) { + if value == nil { + c.WriteNull() + return + } + + switch t := value.(type) { + case *lua.LNilType: + c.WriteNull() + case lua.LBool: + if lua.LVAsBool(value) { + c.WriteInt(1) + } else { + c.WriteNull() + } + case lua.LNumber: + c.WriteInt(int(lua.LVAsNumber(value))) + case lua.LString: + s := lua.LVAsString(value) + if s == "OK" { + c.WriteInline(s) + } else { + c.WriteBulk(s) + } + case *lua.LTable: + // special case for tables with an 'err' or 'ok' field + // note: according to the docs this only counts when 'err' or 'ok' is + // the only field. + if s := t.RawGetString("err"); s.Type() != lua.LTNil { + c.WriteError(s.String()) + return + } + if s := t.RawGetString("ok"); s.Type() != lua.LTNil { + c.WriteInline(s.String()) + return + } + + result := []lua.LValue{} + for j := 1; true; j++ { + val := l.GetTable(value, lua.LNumber(j)) + if val == nil { + result = append(result, val) + continue + } + + if val.Type() == lua.LTNil { + break + } + + result = append(result, val) + } + + c.WriteLen(len(result)) + for _, r := range result { + luaToRedis(l, c, r) + } + default: + panic("....") + } +} + +func redisToLua(l *lua.LState, res []interface{}) *lua.LTable { + rettb := l.NewTable() + for _, e := range res { + var v lua.LValue + if e == nil { + v = lua.LFalse + } else { + switch et := e.(type) { + case int64: + v = lua.LNumber(et) + case []uint8: + v = lua.LString(string(et)) + case []interface{}: + v = redisToLua(l, et) + case string: + v = lua.LString(et) + default: + // TODO: oops? + v = lua.LString(e.(string)) + } + } + l.RawSet(rettb, lua.LNumber(rettb.Len()+1), v) + } + return rettb +} diff --git a/vendor/github.com/alicebob/miniredis/miniredis.go b/vendor/github.com/alicebob/miniredis/miniredis.go new file mode 100644 index 00000000000..0688bdfef0e --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/miniredis.go @@ -0,0 +1,373 @@ +// Package miniredis is a pure Go Redis test server, for use in Go unittests. +// There are no dependencies on system binaries, and every server you start +// will be empty. +// +// Start a server with `s, err := miniredis.Run()`. +// Stop it with `defer s.Close()`. +// +// Point your Redis client to `s.Addr()` or `s.Host(), s.Port()`. +// +// Set keys directly via s.Set(...) and similar commands, or use a Redis client. +// +// 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 ( + "fmt" + "net" + "strconv" + "sync" + "time" + + redigo "github.com/gomodule/redigo/redis" + + "github.com/alicebob/miniredis/server" +) + +type hashKey map[string]string +type listKey []string +type setKey map[string]struct{} + +// RedisDB holds a single (numbered) Redis database. +type RedisDB struct { + master *sync.Mutex // pointer to the lock in Miniredis + id int // db id + keys map[string]string // Master map of keys with their type + stringKeys map[string]string // GET/SET &c. keys + hashKeys map[string]hashKey // MGET/MSET &c. keys + listKeys map[string]listKey // LPUSH &c. keys + setKeys map[string]setKey // SADD &c. keys + sortedsetKeys map[string]sortedSet // ZADD &c. keys + ttl map[string]time.Duration // effective TTL values + keyVersion map[string]uint // used to watch values +} + +// Miniredis is a Redis server implementation. +type Miniredis struct { + sync.Mutex + srv *server.Server + port int + password string + dbs map[int]*RedisDB + selectedDB int // DB id used in the direct Get(), Set() &c. + scripts map[string]string // sha1 -> lua src + signal *sync.Cond + now time.Time // used to make a duration from EXPIREAT. time.Now() if not set. +} + +type txCmd func(*server.Peer, *connCtx) + +// database id + key combo +type dbKey struct { + db int + key string +} + +// connCtx has all state for a single connection. +type connCtx struct { + selectedDB int // selected DB + authenticated bool // auth enabled and a valid AUTH seen + transaction []txCmd // transaction callbacks. Or nil. + dirtyTransaction bool // any error during QUEUEing. + watch map[dbKey]uint // WATCHed keys. +} + +// NewMiniRedis makes a new, non-started, Miniredis object. +func NewMiniRedis() *Miniredis { + m := Miniredis{ + dbs: map[int]*RedisDB{}, + scripts: map[string]string{}, + } + m.signal = sync.NewCond(&m) + return &m +} + +func newRedisDB(id int, l *sync.Mutex) RedisDB { + return RedisDB{ + id: id, + master: l, + keys: map[string]string{}, + stringKeys: map[string]string{}, + hashKeys: map[string]hashKey{}, + listKeys: map[string]listKey{}, + setKeys: map[string]setKey{}, + sortedsetKeys: map[string]sortedSet{}, + ttl: map[string]time.Duration{}, + keyVersion: map[string]uint{}, + } +} + +// Run creates and Start()s a Miniredis. +func Run() (*Miniredis, error) { + m := NewMiniRedis() + return m, m.Start() +} + +// Start starts a server. It listens on a random port on localhost. See also +// Addr(). +func (m *Miniredis) Start() error { + s, err := server.NewServer(fmt.Sprintf("127.0.0.1:%d", m.port)) + if err != nil { + return err + } + return m.start(s) +} + +// StartAddr runs miniredis with a given addr. Examples: "127.0.0.1:6379", +// ":6379", or "127.0.0.1:0" +func (m *Miniredis) StartAddr(addr string) error { + s, err := server.NewServer(addr) + if err != nil { + return err + } + return m.start(s) +} + +func (m *Miniredis) start(s *server.Server) error { + m.Lock() + defer m.Unlock() + m.srv = s + m.port = s.Addr().Port + + commandsConnection(m) + commandsGeneric(m) + commandsServer(m) + commandsString(m) + commandsHash(m) + commandsList(m) + commandsSet(m) + commandsSortedSet(m) + commandsTransaction(m) + commandsScripting(m) + + return nil +} + +// Restart restarts a Close()d server on the same port. Values will be +// preserved. +func (m *Miniredis) Restart() error { + return m.Start() +} + +// Close shuts down a Miniredis. +func (m *Miniredis) Close() { + m.Lock() + defer m.Unlock() + if m.srv == nil { + return + } + m.srv.Close() + m.srv = nil +} + +// RequireAuth makes every connection need to AUTH first. Disable again by +// setting an empty string. +func (m *Miniredis) RequireAuth(pw string) { + m.Lock() + defer m.Unlock() + m.password = pw +} + +// DB returns a DB by ID. +func (m *Miniredis) DB(i int) *RedisDB { + m.Lock() + defer m.Unlock() + return m.db(i) +} + +// get DB. No locks! +func (m *Miniredis) db(i int) *RedisDB { + if db, ok := m.dbs[i]; ok { + return db + } + db := newRedisDB(i, &m.Mutex) // the DB has our lock. + m.dbs[i] = &db + return &db +} + +// Addr returns '127.0.0.1:12345'. Can be given to a Dial(). See also Host() +// and Port(), which return the same things. +func (m *Miniredis) Addr() string { + m.Lock() + defer m.Unlock() + return m.srv.Addr().String() +} + +// Host returns the host part of Addr(). +func (m *Miniredis) Host() string { + m.Lock() + defer m.Unlock() + return m.srv.Addr().IP.String() +} + +// Port returns the (random) port part of Addr(). +func (m *Miniredis) Port() string { + m.Lock() + defer m.Unlock() + return strconv.Itoa(m.srv.Addr().Port) +} + +// CommandCount returns the number of processed commands. +func (m *Miniredis) CommandCount() int { + m.Lock() + defer m.Unlock() + return int(m.srv.TotalCommands()) +} + +// CurrentConnectionCount returns the number of currently connected clients. +func (m *Miniredis) CurrentConnectionCount() int { + m.Lock() + defer m.Unlock() + return m.srv.ClientsLen() +} + +// TotalConnectionCount returns the number of client connections since server start. +func (m *Miniredis) TotalConnectionCount() int { + m.Lock() + defer m.Unlock() + return int(m.srv.TotalConnections()) +} + +// FastForward decreases all TTLs by the given duration. All TTLs <= 0 will be +// expired. +func (m *Miniredis) FastForward(duration time.Duration) { + m.Lock() + defer m.Unlock() + for _, db := range m.dbs { + db.fastForward(duration) + } +} + +// redigo returns a redigo.Conn, connected using net.Pipe +func (m *Miniredis) redigo() redigo.Conn { + c1, c2 := net.Pipe() + m.srv.ServeConn(c1) + c := redigo.NewConn(c2, 0, 0) + if m.password != "" { + if _, err := c.Do("AUTH", m.password); err != nil { + // ? + } + } + return c +} + +// Dump returns a text version of the selected DB, usable for debugging. +func (m *Miniredis) Dump() string { + m.Lock() + defer m.Unlock() + + var ( + maxLen = 60 + indent = " " + db = m.db(m.selectedDB) + r = "" + v = func(s string) string { + suffix := "" + if len(s) > maxLen { + suffix = fmt.Sprintf("...(%d)", len(s)) + s = s[:maxLen-len(suffix)] + } + return fmt.Sprintf("%q%s", s, suffix) + } + ) + for _, k := range db.allKeys() { + r += fmt.Sprintf("- %s\n", k) + t := db.t(k) + switch t { + case "string": + r += fmt.Sprintf("%s%s\n", indent, v(db.stringKeys[k])) + case "hash": + for _, hk := range db.hashFields(k) { + r += fmt.Sprintf("%s%s: %s\n", indent, hk, v(db.hashGet(k, hk))) + } + case "list": + for _, lk := range db.listKeys[k] { + r += fmt.Sprintf("%s%s\n", indent, v(lk)) + } + case "set": + for _, mk := range db.setMembers(k) { + r += fmt.Sprintf("%s%s\n", indent, v(mk)) + } + case "zset": + for _, el := range db.ssetElements(k) { + r += fmt.Sprintf("%s%f: %s\n", indent, el.score, v(el.member)) + } + default: + r += fmt.Sprintf("%s(a %s, fixme!)\n", indent, t) + } + } + return r +} + +// SetTime sets the time against which EXPIREAT values are compared. EXPIREAT +// will use time.Now() if this is not set. +func (m *Miniredis) SetTime(t time.Time) { + m.Lock() + defer m.Unlock() + m.now = t +} + +// handleAuth returns false if connection has no access. It sends the reply. +func (m *Miniredis) handleAuth(c *server.Peer) bool { + m.Lock() + defer m.Unlock() + if m.password == "" { + return true + } + if !getCtx(c).authenticated { + c.WriteError("NOAUTH Authentication required.") + return false + } + return true +} + +func getCtx(c *server.Peer) *connCtx { + if c.Ctx == nil { + c.Ctx = &connCtx{} + } + return c.Ctx.(*connCtx) +} + +func startTx(ctx *connCtx) { + ctx.transaction = []txCmd{} + ctx.dirtyTransaction = false +} + +func stopTx(ctx *connCtx) { + ctx.transaction = nil + unwatch(ctx) +} + +func inTx(ctx *connCtx) bool { + return ctx.transaction != nil +} + +func addTxCmd(ctx *connCtx, cb txCmd) { + ctx.transaction = append(ctx.transaction, cb) +} + +func watch(db *RedisDB, ctx *connCtx, key string) { + if ctx.watch == nil { + ctx.watch = map[dbKey]uint{} + } + ctx.watch[dbKey{db: db.id, key: key}] = db.keyVersion[key] // Can be 0. +} + +func unwatch(ctx *connCtx) { + ctx.watch = nil +} + +// setDirty can be called even when not in an tx. Is an no-op then. +func setDirty(c *server.Peer) { + if c.Ctx == nil { + // No transaction. Not relevant. + return + } + getCtx(c).dirtyTransaction = true +} + +func setAuthenticated(c *server.Peer) { + getCtx(c).authenticated = true +} diff --git a/vendor/github.com/alicebob/miniredis/redis.go b/vendor/github.com/alicebob/miniredis/redis.go new file mode 100644 index 00000000000..49ff7bc3c47 --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/redis.go @@ -0,0 +1,208 @@ +package miniredis + +import ( + "fmt" + "math" + "strings" + "sync" + "time" + + "github.com/alicebob/miniredis/server" +) + +const ( + msgWrongType = "WRONGTYPE Operation against a key holding the wrong kind of value" + msgInvalidInt = "ERR value is not an integer or out of range" + 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 an integer or out of range" + 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" + 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." + msgSingleElementPair = "ERR INCR option supports a single increment-element pair" + msgNoScriptFound = "NOSCRIPT No matching script. Please use EVAL." +) + +func errWrongNumber(cmd string) string { + return fmt.Sprintf("ERR wrong number of arguments for '%s' command", strings.ToLower(cmd)) +} + +func errLuaParseError(err error) string { + return fmt.Sprintf("ERR Error compiling script (new function): %s", err.Error()) +} + +// withTx wraps the non-argument-checking part of command handling code in +// transaction logic. +func withTx( + m *Miniredis, + c *server.Peer, + cb txCmd, +) { + ctx := getCtx(c) + if inTx(ctx) { + addTxCmd(ctx, cb) + c.WriteInline("QUEUED") + return + } + m.Lock() + cb(c, ctx) + // done, wake up anyone who waits on anything. + m.signal.Broadcast() + m.Unlock() +} + +// blockCmd is executed returns whether it is done +type blockCmd func(*server.Peer, *connCtx) bool + +// blocking keeps trying a command until the callback returns true. Calls +// onTimeout after the timeout (or when we call this in a transaction). +func blocking( + m *Miniredis, + c *server.Peer, + timeout time.Duration, + cb blockCmd, + onTimeout func(*server.Peer), +) { + var ( + ctx = getCtx(c) + dl *time.Timer + dlc <-chan time.Time + ) + if inTx(ctx) { + addTxCmd(ctx, func(c *server.Peer, ctx *connCtx) { + if !cb(c, ctx) { + onTimeout(c) + } + }) + c.WriteInline("QUEUED") + return + } + if timeout != 0 { + dl = time.NewTimer(timeout) + defer dl.Stop() + dlc = dl.C + } + + m.Lock() + defer m.Unlock() + for { + done := cb(c, ctx) + if done { + return + } + // there is no cond.WaitTimeout(), so hence the the goroutine to wait + // for a timeout + var ( + wg sync.WaitGroup + wakeup = make(chan struct{}, 1) + ) + wg.Add(1) + go func() { + m.signal.Wait() + wakeup <- struct{}{} + wg.Done() + }() + select { + case <-wakeup: + case <-dlc: + onTimeout(c) + m.signal.Broadcast() // to kill the wakeup go routine + wg.Wait() + return + } + wg.Wait() + } +} + +// formatFloat formats a float the way redis does (sort-of) +func formatFloat(v float64) string { + // Format with %f and strip trailing 0s. This is the most like Redis does + // it :( + // .12 is the magic number where most output is the same as Redis. + if math.IsInf(v, +1) { + return "inf" + } + if math.IsInf(v, -1) { + return "-inf" + } + sv := fmt.Sprintf("%.12f", v) + 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 +} + +// redisRange gives Go offsets for something l long with start/end in +// Redis semantics. Both start and end can be negative. +// Used for string range and list range things. +// The results can be used as: v[start:end] +// Note that GETRANGE (on a string key) never returns an empty string when end +// is a large negative number. +func redisRange(l, start, end int, stringSymantics bool) (int, int) { + if start < 0 { + start = l + start + if start < 0 { + start = 0 + } + } + if start > l { + start = l + } + + if end < 0 { + end = l + end + if end < 0 { + end = -1 + if stringSymantics { + end = 0 + } + } + } + end++ // end argument is inclusive in Redis. + if end > l { + end = l + } + + if end < start { + return 0, 0 + } + return start, end +} + +// matchKeys filters only matching keys. +// Will return an empty list on invalid match expression. +func matchKeys(keys []string, match string) []string { + re := patternRE(match) + if re == nil { + // Special case, the given pattern won't match anything / is + // invalid. + return nil + } + res := []string{} + for _, k := range keys { + if !re.MatchString(k) { + continue + } + res = append(res, k) + } + return res +} diff --git a/vendor/github.com/alicebob/miniredis/server/Makefile b/vendor/github.com/alicebob/miniredis/server/Makefile new file mode 100644 index 00000000000..c82e336f9d4 --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/server/Makefile @@ -0,0 +1,9 @@ +.PHONY: all build test + +all: build test + +build: + go build + +test: + go test diff --git a/vendor/github.com/alicebob/miniredis/server/proto.go b/vendor/github.com/alicebob/miniredis/server/proto.go new file mode 100644 index 00000000000..27e62d4f00b --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/server/proto.go @@ -0,0 +1,84 @@ +package server + +import ( + "bufio" + "errors" + "strconv" +) + +// ErrProtocol is the general error for unexpected input +var ErrProtocol = errors.New("invalid request") + +// client always sends arrays with bulk strings +func readArray(rd *bufio.Reader) ([]string, error) { + line, err := rd.ReadString('\n') + if err != nil { + return nil, err + } + if len(line) < 3 { + return nil, ErrProtocol + } + + switch line[0] { + default: + return nil, ErrProtocol + case '*': + l, err := strconv.Atoi(line[1 : len(line)-2]) + if err != nil { + return nil, err + } + // l can be -1 + var fields []string + for ; l > 0; l-- { + s, err := readString(rd) + if err != nil { + return nil, err + } + fields = append(fields, s) + } + return fields, nil + } +} + +func readString(rd *bufio.Reader) (string, error) { + line, err := rd.ReadString('\n') + if err != nil { + return "", err + } + if len(line) < 3 { + return "", ErrProtocol + } + + switch line[0] { + default: + return "", ErrProtocol + case '+', '-', ':': + // +: simple string + // -: errors + // :: integer + // Simple line based replies. + return string(line[1 : len(line)-2]), 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 "", nil + } + var ( + buf = make([]byte, length+2) + pos = 0 + ) + for pos < length+2 { + n, err := rd.Read(buf[pos:]) + if err != nil { + return "", err + } + pos += n + } + return string(buf[:length]), nil + } +} diff --git a/vendor/github.com/alicebob/miniredis/server/server.go b/vendor/github.com/alicebob/miniredis/server/server.go new file mode 100644 index 00000000000..1796453dd5a --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/server/server.go @@ -0,0 +1,242 @@ +package server + +import ( + "bufio" + "fmt" + "net" + "strings" + "sync" + "unicode" +) + +func errUnknownCommand(cmd string, args []string) string { + s := fmt.Sprintf("ERR unknown command `%s`, with args beginning with: ", cmd) + if len(args) > 20 { + args = args[:20] + } + for _, a := range args { + s += fmt.Sprintf("`%s`, ", a) + } + return s +} + +// Cmd is what Register expects +type Cmd func(c *Peer, cmd string, args []string) + +// Server is a simple redis server +type Server struct { + l net.Listener + cmds map[string]Cmd + peers map[net.Conn]struct{} + mu sync.Mutex + wg sync.WaitGroup + infoConns int + infoCmds int +} + +// NewServer makes a server listening on addr. Close with .Close(). +func NewServer(addr string) (*Server, error) { + s := Server{ + cmds: map[string]Cmd{}, + peers: map[net.Conn]struct{}{}, + } + + l, err := net.Listen("tcp", addr) + if err != nil { + return nil, err + } + s.l = l + + s.wg.Add(1) + go func() { + defer s.wg.Done() + s.serve(l) + }() + return &s, nil +} + +func (s *Server) serve(l net.Listener) { + for { + conn, err := l.Accept() + if err != nil { + return + } + s.ServeConn(conn) + } +} + +// ServeConn handles a net.Conn. Nice with net.Pipe() +func (s *Server) ServeConn(conn net.Conn) { + s.wg.Add(1) + go func() { + defer s.wg.Done() + defer conn.Close() + s.mu.Lock() + s.peers[conn] = struct{}{} + s.infoConns++ + s.mu.Unlock() + + s.servePeer(conn) + + s.mu.Lock() + delete(s.peers, conn) + s.mu.Unlock() + }() +} + +// Addr has the net.Addr struct +func (s *Server) Addr() *net.TCPAddr { + s.mu.Lock() + defer s.mu.Unlock() + if s.l == nil { + return nil + } + return s.l.Addr().(*net.TCPAddr) +} + +// Close a server started with NewServer. It will wait until all clients are +// closed. +func (s *Server) Close() { + s.mu.Lock() + if s.l != nil { + s.l.Close() + } + s.l = nil + for c := range s.peers { + c.Close() + } + s.mu.Unlock() + s.wg.Wait() +} + +// Register a command. It can't have been registered before. Safe to call on a +// running server. +func (s *Server) Register(cmd string, f Cmd) error { + s.mu.Lock() + defer s.mu.Unlock() + cmd = strings.ToUpper(cmd) + if _, ok := s.cmds[cmd]; ok { + return fmt.Errorf("command already registered: %s", cmd) + } + s.cmds[cmd] = f + return nil +} + +func (s *Server) servePeer(c net.Conn) { + r := bufio.NewReader(c) + cl := &Peer{ + w: bufio.NewWriter(c), + } + for { + args, err := readArray(r) + if err != nil { + return + } + s.dispatch(cl, args) + cl.w.Flush() + if cl.closed { + c.Close() + return + } + } +} + +func (s *Server) dispatch(c *Peer, args []string) { + cmd, args := args[0], args[1:] + cmdUp := strings.ToUpper(cmd) + s.mu.Lock() + cb, ok := s.cmds[cmdUp] + s.mu.Unlock() + if !ok { + c.WriteError(errUnknownCommand(cmd, args)) + return + } + + s.mu.Lock() + s.infoCmds++ + s.mu.Unlock() + cb(c, cmdUp, args) +} + +// TotalCommands is total (known) commands since this the server started +func (s *Server) TotalCommands() int { + s.mu.Lock() + defer s.mu.Unlock() + return s.infoCmds +} + +// ClientsLen gives the number of connected clients right now +func (s *Server) ClientsLen() int { + s.mu.Lock() + defer s.mu.Unlock() + return len(s.peers) +} + +// TotalConnections give the number of clients connected since the server +// started, including the currently connected ones +func (s *Server) TotalConnections() int { + s.mu.Lock() + defer s.mu.Unlock() + return s.infoConns +} + +// Peer is a client connected to the server +type Peer struct { + w *bufio.Writer + closed bool + Ctx interface{} // anything goes, server won't touch this +} + +// Flush the write buffer. Called automatically after every redis command +func (c *Peer) Flush() { + c.w.Flush() +} + +// Close the client connection after the current command is done. +func (c *Peer) Close() { + c.closed = true +} + +// WriteError writes a redis 'Error' +func (c *Peer) WriteError(e string) { + fmt.Fprintf(c.w, "-%s\r\n", toInline(e)) +} + +// WriteInline writes a redis inline string +func (c *Peer) WriteInline(s string) { + fmt.Fprintf(c.w, "+%s\r\n", toInline(s)) +} + +// WriteOK write the inline string `OK` +func (c *Peer) WriteOK() { + c.WriteInline("OK") +} + +// WriteBulk writes a bulk string +func (c *Peer) WriteBulk(s string) { + fmt.Fprintf(c.w, "$%d\r\n%s\r\n", len(s), s) +} + +// WriteNull writes a redis Null element +func (c *Peer) WriteNull() { + fmt.Fprintf(c.w, "$-1\r\n") +} + +// WriteLen starts an array with the given length +func (c *Peer) WriteLen(n int) { + fmt.Fprintf(c.w, "*%d\r\n", n) +} + +// WriteInt writes an integer +func (c *Peer) WriteInt(i int) { + fmt.Fprintf(c.w, ":%d\r\n", i) +} + +func toInline(s string) string { + return strings.Map(func(r rune) rune { + if unicode.IsSpace(r) { + return ' ' + } + return r + }, s) +} diff --git a/vendor/github.com/alicebob/miniredis/sorted_set.go b/vendor/github.com/alicebob/miniredis/sorted_set.go new file mode 100644 index 00000000000..9b1894d8adf --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/sorted_set.go @@ -0,0 +1,97 @@ +package miniredis + +// The most KISS way to implement a sorted set. Luckily we don't care about +// performance that much. + +import ( + "sort" +) + +type direction int + +const ( + asc direction = iota + desc +) + +type sortedSet map[string]float64 + +type ssElem struct { + score float64 + member string +} +type ssElems []ssElem + +type byScore ssElems + +func (sse byScore) Len() int { return len(sse) } +func (sse byScore) Swap(i, j int) { sse[i], sse[j] = sse[j], sse[i] } +func (sse byScore) Less(i, j int) bool { + if sse[i].score != sse[j].score { + return sse[i].score < sse[j].score + } + return sse[i].member < sse[j].member +} + +func newSortedSet() sortedSet { + return sortedSet{} +} + +func (ss *sortedSet) card() int { + return len(*ss) +} + +func (ss *sortedSet) set(score float64, member string) { + (*ss)[member] = score +} + +func (ss *sortedSet) get(member string) (float64, bool) { + v, ok := (*ss)[member] + return v, ok +} + +// elems gives the list of ssElem, ready to sort. +func (ss *sortedSet) elems() ssElems { + elems := make(ssElems, 0, len(*ss)) + for e, s := range *ss { + elems = append(elems, ssElem{s, e}) + } + return elems +} + +func (ss *sortedSet) byScore(d direction) ssElems { + elems := ss.elems() + sort.Sort(byScore(elems)) + if d == desc { + reverseElems(elems) + } + return ssElems(elems) +} + +// rankByScore gives the (0-based) index of member, or returns false. +func (ss *sortedSet) rankByScore(member string, d direction) (int, bool) { + if _, ok := (*ss)[member]; !ok { + return 0, false + } + for i, e := range ss.byScore(d) { + if e.member == member { + return i, true + } + } + // Can't happen + return 0, false +} + +func reverseSlice(o []string) { + for i := range make([]struct{}, len(o)/2) { + other := len(o) - 1 - i + o[i], o[other] = o[other], o[i] + } +} + +func reverseElems(o ssElems) { + for i := range make([]struct{}, len(o)/2) { + other := len(o) - 1 - i + o[i], o[other] = o[other], o[i] + } +} diff --git a/vendor/github.com/gomodule/redigo/LICENSE b/vendor/github.com/gomodule/redigo/LICENSE new file mode 100644 index 00000000000..67db8588217 --- /dev/null +++ b/vendor/github.com/gomodule/redigo/LICENSE @@ -0,0 +1,175 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. diff --git a/vendor/github.com/gomodule/redigo/internal/commandinfo.go b/vendor/github.com/gomodule/redigo/internal/commandinfo.go new file mode 100644 index 00000000000..b763efbdd8d --- /dev/null +++ b/vendor/github.com/gomodule/redigo/internal/commandinfo.go @@ -0,0 +1,54 @@ +// Copyright 2014 Gary Burd +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package internal // import "github.com/gomodule/redigo/internal" + +import ( + "strings" +) + +const ( + WatchState = 1 << iota + MultiState + SubscribeState + MonitorState +) + +type CommandInfo struct { + Set, Clear int +} + +var commandInfos = map[string]CommandInfo{ + "WATCH": {Set: WatchState}, + "UNWATCH": {Clear: WatchState}, + "MULTI": {Set: MultiState}, + "EXEC": {Clear: WatchState | MultiState}, + "DISCARD": {Clear: WatchState | MultiState}, + "PSUBSCRIBE": {Set: SubscribeState}, + "SUBSCRIBE": {Set: SubscribeState}, + "MONITOR": {Set: MonitorState}, +} + +func init() { + for n, ci := range commandInfos { + commandInfos[strings.ToLower(n)] = ci + } +} + +func LookupCommandInfo(commandName string) CommandInfo { + if ci, ok := commandInfos[commandName]; ok { + return ci + } + return commandInfos[strings.ToUpper(commandName)] +} diff --git a/vendor/github.com/gomodule/redigo/redis/conn.go b/vendor/github.com/gomodule/redigo/redis/conn.go new file mode 100644 index 00000000000..5aa0f32f2e2 --- /dev/null +++ b/vendor/github.com/gomodule/redigo/redis/conn.go @@ -0,0 +1,673 @@ +// Copyright 2012 Gary Burd +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package redis + +import ( + "bufio" + "bytes" + "crypto/tls" + "errors" + "fmt" + "io" + "net" + "net/url" + "regexp" + "strconv" + "sync" + "time" +) + +var ( + _ ConnWithTimeout = (*conn)(nil) +) + +// conn is the low-level implementation of Conn +type conn struct { + // Shared + mu sync.Mutex + pending int + err error + conn net.Conn + + // Read + readTimeout time.Duration + br *bufio.Reader + + // Write + writeTimeout time.Duration + bw *bufio.Writer + + // Scratch space for formatting argument length. + // '*' or '$', length, "\r\n" + lenScratch [32]byte + + // Scratch space for formatting integers and floats. + numScratch [40]byte +} + +// DialTimeout acts like Dial but takes timeouts for establishing the +// connection to the server, writing a command and reading a reply. +// +// Deprecated: Use Dial with options instead. +func DialTimeout(network, address string, connectTimeout, readTimeout, writeTimeout time.Duration) (Conn, error) { + return Dial(network, address, + DialConnectTimeout(connectTimeout), + DialReadTimeout(readTimeout), + DialWriteTimeout(writeTimeout)) +} + +// DialOption specifies an option for dialing a Redis server. +type DialOption struct { + f func(*dialOptions) +} + +type dialOptions struct { + readTimeout time.Duration + writeTimeout time.Duration + dialer *net.Dialer + dial func(network, addr string) (net.Conn, error) + db int + password string + useTLS bool + skipVerify bool + tlsConfig *tls.Config +} + +// DialReadTimeout specifies the timeout for reading a single command reply. +func DialReadTimeout(d time.Duration) DialOption { + return DialOption{func(do *dialOptions) { + do.readTimeout = d + }} +} + +// DialWriteTimeout specifies the timeout for writing a single command. +func DialWriteTimeout(d time.Duration) DialOption { + return DialOption{func(do *dialOptions) { + do.writeTimeout = d + }} +} + +// DialConnectTimeout specifies the timeout for connecting to the Redis server when +// no DialNetDial option is specified. +func DialConnectTimeout(d time.Duration) DialOption { + return DialOption{func(do *dialOptions) { + do.dialer.Timeout = d + }} +} + +// DialKeepAlive specifies the keep-alive period for TCP connections to the Redis server +// when no DialNetDial option is specified. +// If zero, keep-alives are not enabled. If no DialKeepAlive option is specified then +// the default of 5 minutes is used to ensure that half-closed TCP sessions are detected. +func DialKeepAlive(d time.Duration) DialOption { + return DialOption{func(do *dialOptions) { + do.dialer.KeepAlive = d + }} +} + +// DialNetDial specifies a custom dial function for creating TCP +// connections, otherwise a net.Dialer customized via the other options is used. +// DialNetDial overrides DialConnectTimeout and DialKeepAlive. +func DialNetDial(dial func(network, addr string) (net.Conn, error)) DialOption { + return DialOption{func(do *dialOptions) { + do.dial = dial + }} +} + +// DialDatabase specifies the database to select when dialing a connection. +func DialDatabase(db int) DialOption { + return DialOption{func(do *dialOptions) { + do.db = db + }} +} + +// DialPassword specifies the password to use when connecting to +// the Redis server. +func DialPassword(password string) DialOption { + return DialOption{func(do *dialOptions) { + do.password = password + }} +} + +// DialTLSConfig specifies the config to use when a TLS connection is dialed. +// Has no effect when not dialing a TLS connection. +func DialTLSConfig(c *tls.Config) DialOption { + return DialOption{func(do *dialOptions) { + do.tlsConfig = c + }} +} + +// DialTLSSkipVerify disables server name verification when connecting over +// TLS. Has no effect when not dialing a TLS connection. +func DialTLSSkipVerify(skip bool) DialOption { + return DialOption{func(do *dialOptions) { + do.skipVerify = skip + }} +} + +// DialUseTLS specifies whether TLS should be used when connecting to the +// server. This option is ignore by DialURL. +func DialUseTLS(useTLS bool) DialOption { + return DialOption{func(do *dialOptions) { + do.useTLS = useTLS + }} +} + +// Dial connects to the Redis server at the given network and +// address using the specified options. +func Dial(network, address string, options ...DialOption) (Conn, error) { + do := dialOptions{ + dialer: &net.Dialer{ + KeepAlive: time.Minute * 5, + }, + } + for _, option := range options { + option.f(&do) + } + if do.dial == nil { + do.dial = do.dialer.Dial + } + + netConn, err := do.dial(network, address) + if err != nil { + return nil, err + } + + if do.useTLS { + var tlsConfig *tls.Config + if do.tlsConfig == nil { + tlsConfig = &tls.Config{InsecureSkipVerify: do.skipVerify} + } else { + tlsConfig = cloneTLSConfig(do.tlsConfig) + } + if tlsConfig.ServerName == "" { + host, _, err := net.SplitHostPort(address) + if err != nil { + netConn.Close() + return nil, err + } + tlsConfig.ServerName = host + } + + tlsConn := tls.Client(netConn, tlsConfig) + if err := tlsConn.Handshake(); err != nil { + netConn.Close() + return nil, err + } + netConn = tlsConn + } + + c := &conn{ + conn: netConn, + bw: bufio.NewWriter(netConn), + br: bufio.NewReader(netConn), + readTimeout: do.readTimeout, + writeTimeout: do.writeTimeout, + } + + if do.password != "" { + if _, err := c.Do("AUTH", do.password); err != nil { + netConn.Close() + return nil, err + } + } + + if do.db != 0 { + if _, err := c.Do("SELECT", do.db); err != nil { + netConn.Close() + return nil, err + } + } + + return c, nil +} + +var pathDBRegexp = regexp.MustCompile(`/(\d*)\z`) + +// DialURL connects to a Redis server at the given URL using the Redis +// URI scheme. URLs should follow the draft IANA specification for the +// scheme (https://www.iana.org/assignments/uri-schemes/prov/redis). +func DialURL(rawurl string, options ...DialOption) (Conn, error) { + u, err := url.Parse(rawurl) + if err != nil { + return nil, err + } + + if u.Scheme != "redis" && u.Scheme != "rediss" { + return nil, fmt.Errorf("invalid redis URL scheme: %s", u.Scheme) + } + + // As per the IANA draft spec, the host defaults to localhost and + // the port defaults to 6379. + host, port, err := net.SplitHostPort(u.Host) + if err != nil { + // assume port is missing + host = u.Host + port = "6379" + } + if host == "" { + host = "localhost" + } + address := net.JoinHostPort(host, port) + + if u.User != nil { + password, isSet := u.User.Password() + if isSet { + options = append(options, DialPassword(password)) + } + } + + match := pathDBRegexp.FindStringSubmatch(u.Path) + if len(match) == 2 { + db := 0 + if len(match[1]) > 0 { + db, err = strconv.Atoi(match[1]) + if err != nil { + return nil, fmt.Errorf("invalid database: %s", u.Path[1:]) + } + } + if db != 0 { + options = append(options, DialDatabase(db)) + } + } else if u.Path != "" { + return nil, fmt.Errorf("invalid database: %s", u.Path[1:]) + } + + options = append(options, DialUseTLS(u.Scheme == "rediss")) + + return Dial("tcp", address, options...) +} + +// NewConn returns a new Redigo connection for the given net connection. +func NewConn(netConn net.Conn, readTimeout, writeTimeout time.Duration) Conn { + return &conn{ + conn: netConn, + bw: bufio.NewWriter(netConn), + br: bufio.NewReader(netConn), + readTimeout: readTimeout, + writeTimeout: writeTimeout, + } +} + +func (c *conn) Close() error { + c.mu.Lock() + err := c.err + if c.err == nil { + c.err = errors.New("redigo: closed") + err = c.conn.Close() + } + c.mu.Unlock() + return err +} + +func (c *conn) fatal(err error) error { + c.mu.Lock() + if c.err == nil { + c.err = err + // Close connection to force errors on subsequent calls and to unblock + // other reader or writer. + c.conn.Close() + } + c.mu.Unlock() + return err +} + +func (c *conn) Err() error { + c.mu.Lock() + err := c.err + c.mu.Unlock() + return err +} + +func (c *conn) writeLen(prefix byte, n int) error { + c.lenScratch[len(c.lenScratch)-1] = '\n' + c.lenScratch[len(c.lenScratch)-2] = '\r' + i := len(c.lenScratch) - 3 + for { + c.lenScratch[i] = byte('0' + n%10) + i -= 1 + n = n / 10 + if n == 0 { + break + } + } + c.lenScratch[i] = prefix + _, err := c.bw.Write(c.lenScratch[i:]) + return err +} + +func (c *conn) writeString(s string) error { + c.writeLen('$', len(s)) + c.bw.WriteString(s) + _, err := c.bw.WriteString("\r\n") + return err +} + +func (c *conn) writeBytes(p []byte) error { + c.writeLen('$', len(p)) + c.bw.Write(p) + _, err := c.bw.WriteString("\r\n") + return err +} + +func (c *conn) writeInt64(n int64) error { + return c.writeBytes(strconv.AppendInt(c.numScratch[:0], n, 10)) +} + +func (c *conn) writeFloat64(n float64) error { + return c.writeBytes(strconv.AppendFloat(c.numScratch[:0], n, 'g', -1, 64)) +} + +func (c *conn) writeCommand(cmd string, args []interface{}) error { + c.writeLen('*', 1+len(args)) + if err := c.writeString(cmd); err != nil { + return err + } + for _, arg := range args { + if err := c.writeArg(arg, true); err != nil { + return err + } + } + return nil +} + +func (c *conn) writeArg(arg interface{}, argumentTypeOK bool) (err error) { + switch arg := arg.(type) { + case string: + return c.writeString(arg) + case []byte: + return c.writeBytes(arg) + case int: + return c.writeInt64(int64(arg)) + case int64: + return c.writeInt64(arg) + case float64: + return c.writeFloat64(arg) + case bool: + if arg { + return c.writeString("1") + } else { + return c.writeString("0") + } + case nil: + return c.writeString("") + case Argument: + if argumentTypeOK { + return c.writeArg(arg.RedisArg(), false) + } + // See comment in default clause below. + var buf bytes.Buffer + fmt.Fprint(&buf, arg) + return c.writeBytes(buf.Bytes()) + default: + // This default clause is intended to handle builtin numeric types. + // The function should return an error for other types, but this is not + // done for compatibility with previous versions of the package. + var buf bytes.Buffer + fmt.Fprint(&buf, arg) + return c.writeBytes(buf.Bytes()) + } +} + +type protocolError string + +func (pe protocolError) Error() string { + return fmt.Sprintf("redigo: %s (possible server error or unsupported concurrent read by application)", string(pe)) +} + +func (c *conn) readLine() ([]byte, error) { + p, err := c.br.ReadSlice('\n') + if err == bufio.ErrBufferFull { + return nil, protocolError("long response line") + } + if err != nil { + return nil, err + } + i := len(p) - 2 + if i < 0 || p[i] != '\r' { + return nil, protocolError("bad response line terminator") + } + return p[:i], nil +} + +// parseLen parses bulk string and array lengths. +func parseLen(p []byte) (int, error) { + if len(p) == 0 { + return -1, protocolError("malformed length") + } + + if p[0] == '-' && len(p) == 2 && p[1] == '1' { + // handle $-1 and $-1 null replies. + return -1, nil + } + + var n int + for _, b := range p { + n *= 10 + if b < '0' || b > '9' { + return -1, protocolError("illegal bytes in length") + } + n += int(b - '0') + } + + return n, nil +} + +// parseInt parses an integer reply. +func parseInt(p []byte) (interface{}, error) { + if len(p) == 0 { + return 0, protocolError("malformed integer") + } + + var negate bool + if p[0] == '-' { + negate = true + p = p[1:] + if len(p) == 0 { + return 0, protocolError("malformed integer") + } + } + + var n int64 + for _, b := range p { + n *= 10 + if b < '0' || b > '9' { + return 0, protocolError("illegal bytes in length") + } + n += int64(b - '0') + } + + if negate { + n = -n + } + return n, nil +} + +var ( + okReply interface{} = "OK" + pongReply interface{} = "PONG" +) + +func (c *conn) readReply() (interface{}, error) { + line, err := c.readLine() + if err != nil { + return nil, err + } + if len(line) == 0 { + return nil, protocolError("short response line") + } + switch line[0] { + case '+': + switch { + case len(line) == 3 && line[1] == 'O' && line[2] == 'K': + // Avoid allocation for frequent "+OK" response. + return okReply, nil + case len(line) == 5 && line[1] == 'P' && line[2] == 'O' && line[3] == 'N' && line[4] == 'G': + // Avoid allocation in PING command benchmarks :) + return pongReply, nil + default: + return string(line[1:]), nil + } + case '-': + return Error(string(line[1:])), nil + case ':': + return parseInt(line[1:]) + case '$': + n, err := parseLen(line[1:]) + if n < 0 || err != nil { + return nil, err + } + p := make([]byte, n) + _, err = io.ReadFull(c.br, p) + if err != nil { + return nil, err + } + if line, err := c.readLine(); err != nil { + return nil, err + } else if len(line) != 0 { + return nil, protocolError("bad bulk string format") + } + return p, nil + case '*': + n, err := parseLen(line[1:]) + if n < 0 || err != nil { + return nil, err + } + r := make([]interface{}, n) + for i := range r { + r[i], err = c.readReply() + if err != nil { + return nil, err + } + } + return r, nil + } + return nil, protocolError("unexpected response line") +} + +func (c *conn) Send(cmd string, args ...interface{}) error { + c.mu.Lock() + c.pending += 1 + c.mu.Unlock() + if c.writeTimeout != 0 { + c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout)) + } + if err := c.writeCommand(cmd, args); err != nil { + return c.fatal(err) + } + return nil +} + +func (c *conn) Flush() error { + if c.writeTimeout != 0 { + c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout)) + } + if err := c.bw.Flush(); err != nil { + return c.fatal(err) + } + return nil +} + +func (c *conn) Receive() (interface{}, error) { + return c.ReceiveWithTimeout(c.readTimeout) +} + +func (c *conn) ReceiveWithTimeout(timeout time.Duration) (reply interface{}, err error) { + var deadline time.Time + if timeout != 0 { + deadline = time.Now().Add(timeout) + } + c.conn.SetReadDeadline(deadline) + + if reply, err = c.readReply(); err != nil { + return nil, c.fatal(err) + } + // When using pub/sub, the number of receives can be greater than the + // number of sends. To enable normal use of the connection after + // unsubscribing from all channels, we do not decrement pending to a + // negative value. + // + // The pending field is decremented after the reply is read to handle the + // case where Receive is called before Send. + c.mu.Lock() + if c.pending > 0 { + c.pending -= 1 + } + c.mu.Unlock() + if err, ok := reply.(Error); ok { + return nil, err + } + return +} + +func (c *conn) Do(cmd string, args ...interface{}) (interface{}, error) { + return c.DoWithTimeout(c.readTimeout, cmd, args...) +} + +func (c *conn) DoWithTimeout(readTimeout time.Duration, cmd string, args ...interface{}) (interface{}, error) { + c.mu.Lock() + pending := c.pending + c.pending = 0 + c.mu.Unlock() + + if cmd == "" && pending == 0 { + return nil, nil + } + + if c.writeTimeout != 0 { + c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout)) + } + + if cmd != "" { + if err := c.writeCommand(cmd, args); err != nil { + return nil, c.fatal(err) + } + } + + if err := c.bw.Flush(); err != nil { + return nil, c.fatal(err) + } + + var deadline time.Time + if readTimeout != 0 { + deadline = time.Now().Add(readTimeout) + } + c.conn.SetReadDeadline(deadline) + + if cmd == "" { + reply := make([]interface{}, pending) + for i := range reply { + r, e := c.readReply() + if e != nil { + return nil, c.fatal(e) + } + reply[i] = r + } + return reply, nil + } + + var err error + var reply interface{} + for i := 0; i <= pending; i++ { + var e error + if reply, e = c.readReply(); e != nil { + return nil, c.fatal(e) + } + if e, ok := reply.(Error); ok && err == nil { + err = e + } + } + return reply, err +} diff --git a/vendor/github.com/gomodule/redigo/redis/doc.go b/vendor/github.com/gomodule/redigo/redis/doc.go new file mode 100644 index 00000000000..70ec1ea6919 --- /dev/null +++ b/vendor/github.com/gomodule/redigo/redis/doc.go @@ -0,0 +1,177 @@ +// Copyright 2012 Gary Burd +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +// Package redis is a client for the Redis database. +// +// The Redigo FAQ (https://github.com/gomodule/redigo/wiki/FAQ) contains more +// documentation about this package. +// +// Connections +// +// The Conn interface is the primary interface for working with Redis. +// Applications create connections by calling the Dial, DialWithTimeout or +// NewConn functions. In the future, functions will be added for creating +// sharded and other types of connections. +// +// The application must call the connection Close method when the application +// is done with the connection. +// +// Executing Commands +// +// The Conn interface has a generic method for executing Redis commands: +// +// Do(commandName string, args ...interface{}) (reply interface{}, err error) +// +// The Redis command reference (http://redis.io/commands) lists the available +// commands. An example of using the Redis APPEND command is: +// +// n, err := conn.Do("APPEND", "key", "value") +// +// The Do method converts command arguments to bulk strings for transmission +// to the server as follows: +// +// Go Type Conversion +// []byte Sent as is +// string Sent as is +// int, int64 strconv.FormatInt(v) +// float64 strconv.FormatFloat(v, 'g', -1, 64) +// bool true -> "1", false -> "0" +// nil "" +// all other types fmt.Fprint(w, v) +// +// Redis command reply types are represented using the following Go types: +// +// Redis type Go type +// error redis.Error +// integer int64 +// simple string string +// bulk string []byte or nil if value not present. +// array []interface{} or nil if value not present. +// +// Use type assertions or the reply helper functions to convert from +// interface{} to the specific Go type for the command result. +// +// Pipelining +// +// Connections support pipelining using the Send, Flush and Receive methods. +// +// Send(commandName string, args ...interface{}) error +// Flush() error +// Receive() (reply interface{}, err error) +// +// Send writes the command to the connection's output buffer. Flush flushes the +// connection's output buffer to the server. Receive reads a single reply from +// the server. The following example shows a simple pipeline. +// +// c.Send("SET", "foo", "bar") +// c.Send("GET", "foo") +// c.Flush() +// c.Receive() // reply from SET +// v, err = c.Receive() // reply from GET +// +// The Do method combines the functionality of the Send, Flush and Receive +// methods. The Do method starts by writing the command and flushing the output +// buffer. Next, the Do method receives all pending replies including the reply +// for the command just sent by Do. If any of the received replies is an error, +// then Do returns the error. If there are no errors, then Do returns the last +// reply. If the command argument to the Do method is "", then the Do method +// will flush the output buffer and receive pending replies without sending a +// command. +// +// Use the Send and Do methods to implement pipelined transactions. +// +// c.Send("MULTI") +// c.Send("INCR", "foo") +// c.Send("INCR", "bar") +// r, err := c.Do("EXEC") +// fmt.Println(r) // prints [1, 1] +// +// Concurrency +// +// Connections support one concurrent caller to the Receive method and one +// concurrent caller to the Send and Flush methods. No other concurrency is +// supported including concurrent calls to the Do method. +// +// For full concurrent access to Redis, use the thread-safe Pool to get, use +// and release a connection from within a goroutine. Connections returned from +// a Pool have the concurrency restrictions described in the previous +// paragraph. +// +// Publish and Subscribe +// +// Use the Send, Flush and Receive methods to implement Pub/Sub subscribers. +// +// c.Send("SUBSCRIBE", "example") +// c.Flush() +// for { +// reply, err := c.Receive() +// if err != nil { +// return err +// } +// // process pushed message +// } +// +// The PubSubConn type wraps a Conn with convenience methods for implementing +// subscribers. The Subscribe, PSubscribe, Unsubscribe and PUnsubscribe methods +// send and flush a subscription management command. The receive method +// converts a pushed message to convenient types for use in a type switch. +// +// psc := redis.PubSubConn{Conn: c} +// psc.Subscribe("example") +// for { +// switch v := psc.Receive().(type) { +// case redis.Message: +// fmt.Printf("%s: message: %s\n", v.Channel, v.Data) +// case redis.Subscription: +// fmt.Printf("%s: %s %d\n", v.Channel, v.Kind, v.Count) +// case error: +// return v +// } +// } +// +// Reply Helpers +// +// The Bool, Int, Bytes, String, Strings and Values functions convert a reply +// to a value of a specific type. To allow convenient wrapping of calls to the +// connection Do and Receive methods, the functions take a second argument of +// type error. If the error is non-nil, then the helper function returns the +// error. If the error is nil, the function converts the reply to the specified +// type: +// +// exists, err := redis.Bool(c.Do("EXISTS", "foo")) +// if err != nil { +// // handle error return from c.Do or type conversion error. +// } +// +// The Scan function converts elements of a array reply to Go types: +// +// var value1 int +// var value2 string +// reply, err := redis.Values(c.Do("MGET", "key1", "key2")) +// if err != nil { +// // handle error +// } +// if _, err := redis.Scan(reply, &value1, &value2); err != nil { +// // handle error +// } +// +// Errors +// +// Connection methods return error replies from the server as type redis.Error. +// +// Call the connection Err() method to determine if the connection encountered +// non-recoverable error such as a network error or protocol parsing error. If +// Err() returns a non-nil value, then the connection is not usable and should +// be closed. +package redis // import "github.com/gomodule/redigo/redis" diff --git a/vendor/github.com/gomodule/redigo/redis/go16.go b/vendor/github.com/gomodule/redigo/redis/go16.go new file mode 100644 index 00000000000..f6b1a7ccdce --- /dev/null +++ b/vendor/github.com/gomodule/redigo/redis/go16.go @@ -0,0 +1,27 @@ +// +build !go1.7 + +package redis + +import "crypto/tls" + +func cloneTLSConfig(cfg *tls.Config) *tls.Config { + return &tls.Config{ + Rand: cfg.Rand, + Time: cfg.Time, + Certificates: cfg.Certificates, + NameToCertificate: cfg.NameToCertificate, + GetCertificate: cfg.GetCertificate, + RootCAs: cfg.RootCAs, + NextProtos: cfg.NextProtos, + ServerName: cfg.ServerName, + ClientAuth: cfg.ClientAuth, + ClientCAs: cfg.ClientCAs, + InsecureSkipVerify: cfg.InsecureSkipVerify, + CipherSuites: cfg.CipherSuites, + PreferServerCipherSuites: cfg.PreferServerCipherSuites, + ClientSessionCache: cfg.ClientSessionCache, + MinVersion: cfg.MinVersion, + MaxVersion: cfg.MaxVersion, + CurvePreferences: cfg.CurvePreferences, + } +} diff --git a/vendor/github.com/gomodule/redigo/redis/go17.go b/vendor/github.com/gomodule/redigo/redis/go17.go new file mode 100644 index 00000000000..5f36379113c --- /dev/null +++ b/vendor/github.com/gomodule/redigo/redis/go17.go @@ -0,0 +1,29 @@ +// +build go1.7,!go1.8 + +package redis + +import "crypto/tls" + +func cloneTLSConfig(cfg *tls.Config) *tls.Config { + return &tls.Config{ + Rand: cfg.Rand, + Time: cfg.Time, + Certificates: cfg.Certificates, + NameToCertificate: cfg.NameToCertificate, + GetCertificate: cfg.GetCertificate, + RootCAs: cfg.RootCAs, + NextProtos: cfg.NextProtos, + ServerName: cfg.ServerName, + ClientAuth: cfg.ClientAuth, + ClientCAs: cfg.ClientCAs, + InsecureSkipVerify: cfg.InsecureSkipVerify, + CipherSuites: cfg.CipherSuites, + PreferServerCipherSuites: cfg.PreferServerCipherSuites, + ClientSessionCache: cfg.ClientSessionCache, + MinVersion: cfg.MinVersion, + MaxVersion: cfg.MaxVersion, + CurvePreferences: cfg.CurvePreferences, + DynamicRecordSizingDisabled: cfg.DynamicRecordSizingDisabled, + Renegotiation: cfg.Renegotiation, + } +} diff --git a/vendor/github.com/gomodule/redigo/redis/go18.go b/vendor/github.com/gomodule/redigo/redis/go18.go new file mode 100644 index 00000000000..558363be39a --- /dev/null +++ b/vendor/github.com/gomodule/redigo/redis/go18.go @@ -0,0 +1,9 @@ +// +build go1.8 + +package redis + +import "crypto/tls" + +func cloneTLSConfig(cfg *tls.Config) *tls.Config { + return cfg.Clone() +} diff --git a/vendor/github.com/gomodule/redigo/redis/log.go b/vendor/github.com/gomodule/redigo/redis/log.go new file mode 100644 index 00000000000..b2996611c6d --- /dev/null +++ b/vendor/github.com/gomodule/redigo/redis/log.go @@ -0,0 +1,134 @@ +// Copyright 2012 Gary Burd +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package redis + +import ( + "bytes" + "fmt" + "log" + "time" +) + +var ( + _ ConnWithTimeout = (*loggingConn)(nil) +) + +// NewLoggingConn returns a logging wrapper around a connection. +func NewLoggingConn(conn Conn, logger *log.Logger, prefix string) Conn { + if prefix != "" { + prefix = prefix + "." + } + return &loggingConn{conn, logger, prefix} +} + +type loggingConn struct { + Conn + logger *log.Logger + prefix string +} + +func (c *loggingConn) Close() error { + err := c.Conn.Close() + var buf bytes.Buffer + fmt.Fprintf(&buf, "%sClose() -> (%v)", c.prefix, err) + c.logger.Output(2, buf.String()) + return err +} + +func (c *loggingConn) printValue(buf *bytes.Buffer, v interface{}) { + const chop = 32 + switch v := v.(type) { + case []byte: + if len(v) > chop { + fmt.Fprintf(buf, "%q...", v[:chop]) + } else { + fmt.Fprintf(buf, "%q", v) + } + case string: + if len(v) > chop { + fmt.Fprintf(buf, "%q...", v[:chop]) + } else { + fmt.Fprintf(buf, "%q", v) + } + case []interface{}: + if len(v) == 0 { + buf.WriteString("[]") + } else { + sep := "[" + fin := "]" + if len(v) > chop { + v = v[:chop] + fin = "...]" + } + for _, vv := range v { + buf.WriteString(sep) + c.printValue(buf, vv) + sep = ", " + } + buf.WriteString(fin) + } + default: + fmt.Fprint(buf, v) + } +} + +func (c *loggingConn) print(method, commandName string, args []interface{}, reply interface{}, err error) { + var buf bytes.Buffer + fmt.Fprintf(&buf, "%s%s(", c.prefix, method) + if method != "Receive" { + buf.WriteString(commandName) + for _, arg := range args { + buf.WriteString(", ") + c.printValue(&buf, arg) + } + } + buf.WriteString(") -> (") + if method != "Send" { + c.printValue(&buf, reply) + buf.WriteString(", ") + } + fmt.Fprintf(&buf, "%v)", err) + c.logger.Output(3, buf.String()) +} + +func (c *loggingConn) Do(commandName string, args ...interface{}) (interface{}, error) { + reply, err := c.Conn.Do(commandName, args...) + c.print("Do", commandName, args, reply, err) + return reply, err +} + +func (c *loggingConn) DoWithTimeout(timeout time.Duration, commandName string, args ...interface{}) (interface{}, error) { + reply, err := DoWithTimeout(c.Conn, timeout, commandName, args...) + c.print("DoWithTimeout", commandName, args, reply, err) + return reply, err +} + +func (c *loggingConn) Send(commandName string, args ...interface{}) error { + err := c.Conn.Send(commandName, args...) + c.print("Send", commandName, args, nil, err) + return err +} + +func (c *loggingConn) Receive() (interface{}, error) { + reply, err := c.Conn.Receive() + c.print("Receive", "", nil, reply, err) + return reply, err +} + +func (c *loggingConn) ReceiveWithTimeout(timeout time.Duration) (interface{}, error) { + reply, err := ReceiveWithTimeout(c.Conn, timeout) + c.print("ReceiveWithTimeout", "", nil, reply, err) + return reply, err +} diff --git a/vendor/github.com/gomodule/redigo/redis/pool.go b/vendor/github.com/gomodule/redigo/redis/pool.go new file mode 100644 index 00000000000..d77da3254a3 --- /dev/null +++ b/vendor/github.com/gomodule/redigo/redis/pool.go @@ -0,0 +1,562 @@ +// Copyright 2012 Gary Burd +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package redis + +import ( + "bytes" + "crypto/rand" + "crypto/sha1" + "errors" + "io" + "strconv" + "sync" + "sync/atomic" + "time" + + "github.com/gomodule/redigo/internal" +) + +var ( + _ ConnWithTimeout = (*activeConn)(nil) + _ ConnWithTimeout = (*errorConn)(nil) +) + +var nowFunc = time.Now // for testing + +// ErrPoolExhausted is returned from a pool connection method (Do, Send, +// Receive, Flush, Err) when the maximum number of database connections in the +// pool has been reached. +var ErrPoolExhausted = errors.New("redigo: connection pool exhausted") + +var ( + errPoolClosed = errors.New("redigo: connection pool closed") + errConnClosed = errors.New("redigo: connection closed") +) + +// Pool maintains a pool of connections. The application calls the Get method +// to get a connection from the pool and the connection's Close method to +// return the connection's resources to the pool. +// +// The following example shows how to use a pool in a web application. The +// application creates a pool at application startup and makes it available to +// request handlers using a package level variable. The pool configuration used +// here is an example, not a recommendation. +// +// func newPool(addr string) *redis.Pool { +// return &redis.Pool{ +// MaxIdle: 3, +// IdleTimeout: 240 * time.Second, +// Dial: func () (redis.Conn, error) { return redis.Dial("tcp", addr) }, +// } +// } +// +// var ( +// pool *redis.Pool +// redisServer = flag.String("redisServer", ":6379", "") +// ) +// +// func main() { +// flag.Parse() +// pool = newPool(*redisServer) +// ... +// } +// +// A request handler gets a connection from the pool and closes the connection +// when the handler is done: +// +// func serveHome(w http.ResponseWriter, r *http.Request) { +// conn := pool.Get() +// defer conn.Close() +// ... +// } +// +// Use the Dial function to authenticate connections with the AUTH command or +// select a database with the SELECT command: +// +// pool := &redis.Pool{ +// // Other pool configuration not shown in this example. +// Dial: func () (redis.Conn, error) { +// c, err := redis.Dial("tcp", server) +// if err != nil { +// return nil, err +// } +// if _, err := c.Do("AUTH", password); err != nil { +// c.Close() +// return nil, err +// } +// if _, err := c.Do("SELECT", db); err != nil { +// c.Close() +// return nil, err +// } +// return c, nil +// }, +// } +// +// Use the TestOnBorrow function to check the health of an idle connection +// before the connection is returned to the application. This example PINGs +// connections that have been idle more than a minute: +// +// pool := &redis.Pool{ +// // Other pool configuration not shown in this example. +// TestOnBorrow: func(c redis.Conn, t time.Time) error { +// if time.Since(t) < time.Minute { +// return nil +// } +// _, err := c.Do("PING") +// return err +// }, +// } +// +type Pool struct { + // Dial is an application supplied function for creating and configuring a + // connection. + // + // The connection returned from Dial must not be in a special state + // (subscribed to pubsub channel, transaction started, ...). + Dial func() (Conn, error) + + // TestOnBorrow is an optional application supplied function for checking + // the health of an idle connection before the connection is used again by + // the application. Argument t is the time that the connection was returned + // to the pool. If the function returns an error, then the connection is + // closed. + TestOnBorrow func(c Conn, t time.Time) error + + // Maximum number of idle connections in the pool. + MaxIdle int + + // Maximum number of connections allocated by the pool at a given time. + // When zero, there is no limit on the number of connections in the pool. + MaxActive int + + // Close connections after remaining idle for this duration. If the value + // is zero, then idle connections are not closed. Applications should set + // the timeout to a value less than the server's timeout. + IdleTimeout time.Duration + + // If Wait is true and the pool is at the MaxActive limit, then Get() waits + // for a connection to be returned to the pool before returning. + Wait bool + + // Close connections older than this duration. If the value is zero, then + // the pool does not close connections based on age. + MaxConnLifetime time.Duration + + chInitialized uint32 // set to 1 when field ch is initialized + + mu sync.Mutex // mu protects the following fields + closed bool // set to true when the pool is closed. + active int // the number of open connections in the pool + ch chan struct{} // limits open connections when p.Wait is true + idle idleList // idle connections +} + +// NewPool creates a new pool. +// +// Deprecated: Initialize the Pool directory as shown in the example. +func NewPool(newFn func() (Conn, error), maxIdle int) *Pool { + return &Pool{Dial: newFn, MaxIdle: maxIdle} +} + +// Get gets a connection. The application must close the returned connection. +// This method always returns a valid connection so that applications can defer +// error handling to the first use of the connection. If there is an error +// getting an underlying connection, then the connection Err, Do, Send, Flush +// and Receive methods return that error. +func (p *Pool) Get() Conn { + pc, err := p.get(nil) + if err != nil { + return errorConn{err} + } + return &activeConn{p: p, pc: pc} +} + +// PoolStats contains pool statistics. +type PoolStats struct { + // ActiveCount is the number of connections in the pool. The count includes + // idle connections and connections in use. + ActiveCount int + // IdleCount is the number of idle connections in the pool. + IdleCount int +} + +// Stats returns pool's statistics. +func (p *Pool) Stats() PoolStats { + p.mu.Lock() + stats := PoolStats{ + ActiveCount: p.active, + IdleCount: p.idle.count, + } + p.mu.Unlock() + + return stats +} + +// ActiveCount returns the number of connections in the pool. The count +// includes idle connections and connections in use. +func (p *Pool) ActiveCount() int { + p.mu.Lock() + active := p.active + p.mu.Unlock() + return active +} + +// IdleCount returns the number of idle connections in the pool. +func (p *Pool) IdleCount() int { + p.mu.Lock() + idle := p.idle.count + p.mu.Unlock() + return idle +} + +// Close releases the resources used by the pool. +func (p *Pool) Close() error { + p.mu.Lock() + if p.closed { + p.mu.Unlock() + return nil + } + p.closed = true + p.active -= p.idle.count + pc := p.idle.front + p.idle.count = 0 + p.idle.front, p.idle.back = nil, nil + if p.ch != nil { + close(p.ch) + } + p.mu.Unlock() + for ; pc != nil; pc = pc.next { + pc.c.Close() + } + return nil +} + +func (p *Pool) lazyInit() { + // Fast path. + if atomic.LoadUint32(&p.chInitialized) == 1 { + return + } + // Slow path. + p.mu.Lock() + if p.chInitialized == 0 { + p.ch = make(chan struct{}, p.MaxActive) + if p.closed { + close(p.ch) + } else { + for i := 0; i < p.MaxActive; i++ { + p.ch <- struct{}{} + } + } + atomic.StoreUint32(&p.chInitialized, 1) + } + p.mu.Unlock() +} + +// get prunes stale connections and returns a connection from the idle list or +// creates a new connection. +func (p *Pool) get(ctx interface { + Done() <-chan struct{} + Err() error +}) (*poolConn, error) { + + // Handle limit for p.Wait == true. + if p.Wait && p.MaxActive > 0 { + p.lazyInit() + if ctx == nil { + <-p.ch + } else { + select { + case <-p.ch: + case <-ctx.Done(): + return nil, ctx.Err() + } + } + } + + p.mu.Lock() + + // Prune stale connections at the back of the idle list. + if p.IdleTimeout > 0 { + n := p.idle.count + for i := 0; i < n && p.idle.back != nil && p.idle.back.t.Add(p.IdleTimeout).Before(nowFunc()); i++ { + pc := p.idle.back + p.idle.popBack() + p.mu.Unlock() + pc.c.Close() + p.mu.Lock() + p.active-- + } + } + + // Get idle connection from the front of idle list. + for p.idle.front != nil { + pc := p.idle.front + p.idle.popFront() + p.mu.Unlock() + if (p.TestOnBorrow == nil || p.TestOnBorrow(pc.c, pc.t) == nil) && + (p.MaxConnLifetime == 0 || nowFunc().Sub(pc.created) < p.MaxConnLifetime) { + return pc, nil + } + pc.c.Close() + p.mu.Lock() + p.active-- + } + + // Check for pool closed before dialing a new connection. + if p.closed { + p.mu.Unlock() + return nil, errors.New("redigo: get on closed pool") + } + + // Handle limit for p.Wait == false. + if !p.Wait && p.MaxActive > 0 && p.active >= p.MaxActive { + p.mu.Unlock() + return nil, ErrPoolExhausted + } + + p.active++ + p.mu.Unlock() + c, err := p.Dial() + if err != nil { + c = nil + p.mu.Lock() + p.active-- + if p.ch != nil && !p.closed { + p.ch <- struct{}{} + } + p.mu.Unlock() + } + return &poolConn{c: c, created: nowFunc()}, err +} + +func (p *Pool) put(pc *poolConn, forceClose bool) error { + p.mu.Lock() + if !p.closed && !forceClose { + pc.t = nowFunc() + p.idle.pushFront(pc) + if p.idle.count > p.MaxIdle { + pc = p.idle.back + p.idle.popBack() + } else { + pc = nil + } + } + + if pc != nil { + p.mu.Unlock() + pc.c.Close() + p.mu.Lock() + p.active-- + } + + if p.ch != nil && !p.closed { + p.ch <- struct{}{} + } + p.mu.Unlock() + return nil +} + +type activeConn struct { + p *Pool + pc *poolConn + state int +} + +var ( + sentinel []byte + sentinelOnce sync.Once +) + +func initSentinel() { + p := make([]byte, 64) + if _, err := rand.Read(p); err == nil { + sentinel = p + } else { + h := sha1.New() + io.WriteString(h, "Oops, rand failed. Use time instead.") + io.WriteString(h, strconv.FormatInt(time.Now().UnixNano(), 10)) + sentinel = h.Sum(nil) + } +} + +func (ac *activeConn) Close() error { + pc := ac.pc + if pc == nil { + return nil + } + ac.pc = nil + + if ac.state&internal.MultiState != 0 { + pc.c.Send("DISCARD") + ac.state &^= (internal.MultiState | internal.WatchState) + } else if ac.state&internal.WatchState != 0 { + pc.c.Send("UNWATCH") + ac.state &^= internal.WatchState + } + if ac.state&internal.SubscribeState != 0 { + pc.c.Send("UNSUBSCRIBE") + pc.c.Send("PUNSUBSCRIBE") + // To detect the end of the message stream, ask the server to echo + // a sentinel value and read until we see that value. + sentinelOnce.Do(initSentinel) + pc.c.Send("ECHO", sentinel) + pc.c.Flush() + for { + p, err := pc.c.Receive() + if err != nil { + break + } + if p, ok := p.([]byte); ok && bytes.Equal(p, sentinel) { + ac.state &^= internal.SubscribeState + break + } + } + } + pc.c.Do("") + ac.p.put(pc, ac.state != 0 || pc.c.Err() != nil) + return nil +} + +func (ac *activeConn) Err() error { + pc := ac.pc + if pc == nil { + return errConnClosed + } + return pc.c.Err() +} + +func (ac *activeConn) Do(commandName string, args ...interface{}) (reply interface{}, err error) { + pc := ac.pc + if pc == nil { + return nil, errConnClosed + } + ci := internal.LookupCommandInfo(commandName) + ac.state = (ac.state | ci.Set) &^ ci.Clear + return pc.c.Do(commandName, args...) +} + +func (ac *activeConn) DoWithTimeout(timeout time.Duration, commandName string, args ...interface{}) (reply interface{}, err error) { + pc := ac.pc + if pc == nil { + return nil, errConnClosed + } + cwt, ok := pc.c.(ConnWithTimeout) + if !ok { + return nil, errTimeoutNotSupported + } + ci := internal.LookupCommandInfo(commandName) + ac.state = (ac.state | ci.Set) &^ ci.Clear + return cwt.DoWithTimeout(timeout, commandName, args...) +} + +func (ac *activeConn) Send(commandName string, args ...interface{}) error { + pc := ac.pc + if pc == nil { + return errConnClosed + } + ci := internal.LookupCommandInfo(commandName) + ac.state = (ac.state | ci.Set) &^ ci.Clear + return pc.c.Send(commandName, args...) +} + +func (ac *activeConn) Flush() error { + pc := ac.pc + if pc == nil { + return errConnClosed + } + return pc.c.Flush() +} + +func (ac *activeConn) Receive() (reply interface{}, err error) { + pc := ac.pc + if pc == nil { + return nil, errConnClosed + } + return pc.c.Receive() +} + +func (ac *activeConn) ReceiveWithTimeout(timeout time.Duration) (reply interface{}, err error) { + pc := ac.pc + if pc == nil { + return nil, errConnClosed + } + cwt, ok := pc.c.(ConnWithTimeout) + if !ok { + return nil, errTimeoutNotSupported + } + return cwt.ReceiveWithTimeout(timeout) +} + +type errorConn struct{ err error } + +func (ec errorConn) Do(string, ...interface{}) (interface{}, error) { return nil, ec.err } +func (ec errorConn) DoWithTimeout(time.Duration, string, ...interface{}) (interface{}, error) { + return nil, ec.err +} +func (ec errorConn) Send(string, ...interface{}) error { return ec.err } +func (ec errorConn) Err() error { return ec.err } +func (ec errorConn) Close() error { return nil } +func (ec errorConn) Flush() error { return ec.err } +func (ec errorConn) Receive() (interface{}, error) { return nil, ec.err } +func (ec errorConn) ReceiveWithTimeout(time.Duration) (interface{}, error) { return nil, ec.err } + +type idleList struct { + count int + front, back *poolConn +} + +type poolConn struct { + c Conn + t time.Time + created time.Time + next, prev *poolConn +} + +func (l *idleList) pushFront(pc *poolConn) { + pc.next = l.front + pc.prev = nil + if l.count == 0 { + l.back = pc + } else { + l.front.prev = pc + } + l.front = pc + l.count++ + return +} + +func (l *idleList) popFront() { + pc := l.front + l.count-- + if l.count == 0 { + l.front, l.back = nil, nil + } else { + pc.next.prev = nil + l.front = pc.next + } + pc.next, pc.prev = nil, nil +} + +func (l *idleList) popBack() { + pc := l.back + l.count-- + if l.count == 0 { + l.front, l.back = nil, nil + } else { + pc.prev.next = nil + l.back = pc.prev + } + pc.next, pc.prev = nil, nil +} diff --git a/vendor/github.com/gomodule/redigo/redis/pool17.go b/vendor/github.com/gomodule/redigo/redis/pool17.go new file mode 100644 index 00000000000..c1ea18ee37d --- /dev/null +++ b/vendor/github.com/gomodule/redigo/redis/pool17.go @@ -0,0 +1,35 @@ +// Copyright 2018 Gary Burd +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +// +build go1.7 + +package redis + +import "context" + +// GetContext gets a connection using the provided context. +// +// The provided Context must be non-nil. If the context expires before the +// connection is complete, an error is returned. Any expiration on the context +// will not affect the returned connection. +// +// If the function completes without error, then the application must close the +// returned connection. +func (p *Pool) GetContext(ctx context.Context) (Conn, error) { + pc, err := p.get(ctx) + if err != nil { + return errorConn{err}, err + } + return &activeConn{p: p, pc: pc}, nil +} diff --git a/vendor/github.com/gomodule/redigo/redis/pubsub.go b/vendor/github.com/gomodule/redigo/redis/pubsub.go new file mode 100644 index 00000000000..2da60211d9f --- /dev/null +++ b/vendor/github.com/gomodule/redigo/redis/pubsub.go @@ -0,0 +1,148 @@ +// Copyright 2012 Gary Burd +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package redis + +import ( + "errors" + "time" +) + +// Subscription represents a subscribe or unsubscribe notification. +type Subscription struct { + // Kind is "subscribe", "unsubscribe", "psubscribe" or "punsubscribe" + Kind string + + // The channel that was changed. + Channel string + + // The current number of subscriptions for connection. + Count int +} + +// Message represents a message notification. +type Message struct { + // The originating channel. + Channel string + + // The matched pattern, if any + Pattern string + + // The message data. + Data []byte +} + +// Pong represents a pubsub pong notification. +type Pong struct { + Data string +} + +// PubSubConn wraps a Conn with convenience methods for subscribers. +type PubSubConn struct { + Conn Conn +} + +// Close closes the connection. +func (c PubSubConn) Close() error { + return c.Conn.Close() +} + +// Subscribe subscribes the connection to the specified channels. +func (c PubSubConn) Subscribe(channel ...interface{}) error { + c.Conn.Send("SUBSCRIBE", channel...) + return c.Conn.Flush() +} + +// PSubscribe subscribes the connection to the given patterns. +func (c PubSubConn) PSubscribe(channel ...interface{}) error { + c.Conn.Send("PSUBSCRIBE", channel...) + return c.Conn.Flush() +} + +// Unsubscribe unsubscribes the connection from the given channels, or from all +// of them if none is given. +func (c PubSubConn) Unsubscribe(channel ...interface{}) error { + c.Conn.Send("UNSUBSCRIBE", channel...) + return c.Conn.Flush() +} + +// PUnsubscribe unsubscribes the connection from the given patterns, or from all +// of them if none is given. +func (c PubSubConn) PUnsubscribe(channel ...interface{}) error { + c.Conn.Send("PUNSUBSCRIBE", channel...) + return c.Conn.Flush() +} + +// Ping sends a PING to the server with the specified data. +// +// The connection must be subscribed to at least one channel or pattern when +// calling this method. +func (c PubSubConn) Ping(data string) error { + c.Conn.Send("PING", data) + return c.Conn.Flush() +} + +// Receive returns a pushed message as a Subscription, Message, Pong or error. +// The return value is intended to be used directly in a type switch as +// illustrated in the PubSubConn example. +func (c PubSubConn) Receive() interface{} { + return c.receiveInternal(c.Conn.Receive()) +} + +// ReceiveWithTimeout is like Receive, but it allows the application to +// override the connection's default timeout. +func (c PubSubConn) ReceiveWithTimeout(timeout time.Duration) interface{} { + return c.receiveInternal(ReceiveWithTimeout(c.Conn, timeout)) +} + +func (c PubSubConn) receiveInternal(replyArg interface{}, errArg error) interface{} { + reply, err := Values(replyArg, errArg) + if err != nil { + return err + } + + var kind string + reply, err = Scan(reply, &kind) + if err != nil { + return err + } + + switch kind { + case "message": + var m Message + if _, err := Scan(reply, &m.Channel, &m.Data); err != nil { + return err + } + return m + case "pmessage": + var m Message + if _, err := Scan(reply, &m.Pattern, &m.Channel, &m.Data); err != nil { + return err + } + return m + case "subscribe", "psubscribe", "unsubscribe", "punsubscribe": + s := Subscription{Kind: kind} + if _, err := Scan(reply, &s.Channel, &s.Count); err != nil { + return err + } + return s + case "pong": + var p Pong + if _, err := Scan(reply, &p.Data); err != nil { + return err + } + return p + } + return errors.New("redigo: unknown pubsub notification") +} diff --git a/vendor/github.com/gomodule/redigo/redis/redis.go b/vendor/github.com/gomodule/redigo/redis/redis.go new file mode 100644 index 00000000000..141fa4a9121 --- /dev/null +++ b/vendor/github.com/gomodule/redigo/redis/redis.go @@ -0,0 +1,117 @@ +// Copyright 2012 Gary Burd +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package redis + +import ( + "errors" + "time" +) + +// Error represents an error returned in a command reply. +type Error string + +func (err Error) Error() string { return string(err) } + +// Conn represents a connection to a Redis server. +type Conn interface { + // Close closes the connection. + Close() error + + // Err returns a non-nil value when the connection is not usable. + Err() error + + // Do sends a command to the server and returns the received reply. + Do(commandName string, args ...interface{}) (reply interface{}, err error) + + // Send writes the command to the client's output buffer. + Send(commandName string, args ...interface{}) error + + // Flush flushes the output buffer to the Redis server. + Flush() error + + // Receive receives a single reply from the Redis server + Receive() (reply interface{}, err error) +} + +// Argument is the interface implemented by an object which wants to control how +// the object is converted to Redis bulk strings. +type Argument interface { + // RedisArg returns a value to be encoded as a bulk string per the + // conversions listed in the section 'Executing Commands'. + // Implementations should typically return a []byte or string. + RedisArg() interface{} +} + +// Scanner is implemented by an object which wants to control its value is +// interpreted when read from Redis. +type Scanner interface { + // RedisScan assigns a value from a Redis value. The argument src is one of + // the reply types listed in the section `Executing Commands`. + // + // An error should be returned if the value cannot be stored without + // loss of information. + RedisScan(src interface{}) error +} + +// ConnWithTimeout is an optional interface that allows the caller to override +// a connection's default read timeout. This interface is useful for executing +// the BLPOP, BRPOP, BRPOPLPUSH, XREAD and other commands that block at the +// server. +// +// A connection's default read timeout is set with the DialReadTimeout dial +// option. Applications should rely on the default timeout for commands that do +// not block at the server. +// +// All of the Conn implementations in this package satisfy the ConnWithTimeout +// interface. +// +// Use the DoWithTimeout and ReceiveWithTimeout helper functions to simplify +// use of this interface. +type ConnWithTimeout interface { + Conn + + // Do sends a command to the server and returns the received reply. + // The timeout overrides the read timeout set when dialing the + // connection. + DoWithTimeout(timeout time.Duration, commandName string, args ...interface{}) (reply interface{}, err error) + + // Receive receives a single reply from the Redis server. The timeout + // overrides the read timeout set when dialing the connection. + ReceiveWithTimeout(timeout time.Duration) (reply interface{}, err error) +} + +var errTimeoutNotSupported = errors.New("redis: connection does not support ConnWithTimeout") + +// DoWithTimeout executes a Redis command with the specified read timeout. If +// the connection does not satisfy the ConnWithTimeout interface, then an error +// is returned. +func DoWithTimeout(c Conn, timeout time.Duration, cmd string, args ...interface{}) (interface{}, error) { + cwt, ok := c.(ConnWithTimeout) + if !ok { + return nil, errTimeoutNotSupported + } + return cwt.DoWithTimeout(timeout, cmd, args...) +} + +// ReceiveWithTimeout receives a reply with the specified read timeout. If the +// connection does not satisfy the ConnWithTimeout interface, then an error is +// returned. +func ReceiveWithTimeout(c Conn, timeout time.Duration) (interface{}, error) { + cwt, ok := c.(ConnWithTimeout) + if !ok { + return nil, errTimeoutNotSupported + } + return cwt.ReceiveWithTimeout(timeout) +} diff --git a/vendor/github.com/gomodule/redigo/redis/reply.go b/vendor/github.com/gomodule/redigo/redis/reply.go new file mode 100644 index 00000000000..c2b3b2b6e74 --- /dev/null +++ b/vendor/github.com/gomodule/redigo/redis/reply.go @@ -0,0 +1,479 @@ +// Copyright 2012 Gary Burd +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package redis + +import ( + "errors" + "fmt" + "strconv" +) + +// ErrNil indicates that a reply value is nil. +var ErrNil = errors.New("redigo: nil returned") + +// Int is a helper that converts a command reply to an integer. If err is not +// equal to nil, then Int returns 0, err. Otherwise, Int converts the +// reply to an int as follows: +// +// Reply type Result +// integer int(reply), nil +// bulk string parsed reply, nil +// nil 0, ErrNil +// other 0, error +func Int(reply interface{}, err error) (int, error) { + if err != nil { + return 0, err + } + switch reply := reply.(type) { + case int64: + x := int(reply) + if int64(x) != reply { + return 0, strconv.ErrRange + } + return x, nil + case []byte: + n, err := strconv.ParseInt(string(reply), 10, 0) + return int(n), err + case nil: + return 0, ErrNil + case Error: + return 0, reply + } + return 0, fmt.Errorf("redigo: unexpected type for Int, got type %T", reply) +} + +// Int64 is a helper that converts a command reply to 64 bit integer. If err is +// not equal to nil, then Int returns 0, err. Otherwise, Int64 converts the +// reply to an int64 as follows: +// +// Reply type Result +// integer reply, nil +// bulk string parsed reply, nil +// nil 0, ErrNil +// other 0, error +func Int64(reply interface{}, err error) (int64, error) { + if err != nil { + return 0, err + } + switch reply := reply.(type) { + case int64: + return reply, nil + case []byte: + n, err := strconv.ParseInt(string(reply), 10, 64) + return n, err + case nil: + return 0, ErrNil + case Error: + return 0, reply + } + return 0, fmt.Errorf("redigo: unexpected type for Int64, got type %T", reply) +} + +var errNegativeInt = errors.New("redigo: unexpected value for Uint64") + +// Uint64 is a helper that converts a command reply to 64 bit integer. If err is +// not equal to nil, then Int returns 0, err. Otherwise, Int64 converts the +// reply to an int64 as follows: +// +// Reply type Result +// integer reply, nil +// bulk string parsed reply, nil +// nil 0, ErrNil +// other 0, error +func Uint64(reply interface{}, err error) (uint64, error) { + if err != nil { + return 0, err + } + switch reply := reply.(type) { + case int64: + if reply < 0 { + return 0, errNegativeInt + } + return uint64(reply), nil + case []byte: + n, err := strconv.ParseUint(string(reply), 10, 64) + return n, err + case nil: + return 0, ErrNil + case Error: + return 0, reply + } + return 0, fmt.Errorf("redigo: unexpected type for Uint64, got type %T", reply) +} + +// Float64 is a helper that converts a command reply to 64 bit float. If err is +// not equal to nil, then Float64 returns 0, err. Otherwise, Float64 converts +// the reply to an int as follows: +// +// Reply type Result +// bulk string parsed reply, nil +// nil 0, ErrNil +// other 0, error +func Float64(reply interface{}, err error) (float64, error) { + if err != nil { + return 0, err + } + switch reply := reply.(type) { + case []byte: + n, err := strconv.ParseFloat(string(reply), 64) + return n, err + case nil: + return 0, ErrNil + case Error: + return 0, reply + } + return 0, fmt.Errorf("redigo: unexpected type for Float64, got type %T", reply) +} + +// String is a helper that converts a command reply to a string. If err is not +// equal to nil, then String returns "", err. Otherwise String converts the +// reply to a string as follows: +// +// Reply type Result +// bulk string string(reply), nil +// simple string reply, nil +// nil "", ErrNil +// other "", error +func String(reply interface{}, err error) (string, error) { + if err != nil { + return "", err + } + switch reply := reply.(type) { + case []byte: + return string(reply), nil + case string: + return reply, nil + case nil: + return "", ErrNil + case Error: + return "", reply + } + return "", fmt.Errorf("redigo: unexpected type for String, got type %T", reply) +} + +// Bytes is a helper that converts a command reply to a slice of bytes. If err +// is not equal to nil, then Bytes returns nil, err. Otherwise Bytes converts +// the reply to a slice of bytes as follows: +// +// Reply type Result +// bulk string reply, nil +// simple string []byte(reply), nil +// nil nil, ErrNil +// other nil, error +func Bytes(reply interface{}, err error) ([]byte, error) { + if err != nil { + return nil, err + } + switch reply := reply.(type) { + case []byte: + return reply, nil + case string: + return []byte(reply), nil + case nil: + return nil, ErrNil + case Error: + return nil, reply + } + return nil, fmt.Errorf("redigo: unexpected type for Bytes, got type %T", reply) +} + +// Bool is a helper that converts a command reply to a boolean. If err is not +// equal to nil, then Bool returns false, err. Otherwise Bool converts the +// reply to boolean as follows: +// +// Reply type Result +// integer value != 0, nil +// bulk string strconv.ParseBool(reply) +// nil false, ErrNil +// other false, error +func Bool(reply interface{}, err error) (bool, error) { + if err != nil { + return false, err + } + switch reply := reply.(type) { + case int64: + return reply != 0, nil + case []byte: + return strconv.ParseBool(string(reply)) + case nil: + return false, ErrNil + case Error: + return false, reply + } + return false, fmt.Errorf("redigo: unexpected type for Bool, got type %T", reply) +} + +// MultiBulk is a helper that converts an array command reply to a []interface{}. +// +// Deprecated: Use Values instead. +func MultiBulk(reply interface{}, err error) ([]interface{}, error) { return Values(reply, err) } + +// Values is a helper that converts an array command reply to a []interface{}. +// If err is not equal to nil, then Values returns nil, err. Otherwise, Values +// converts the reply as follows: +// +// Reply type Result +// array reply, nil +// nil nil, ErrNil +// other nil, error +func Values(reply interface{}, err error) ([]interface{}, error) { + if err != nil { + return nil, err + } + switch reply := reply.(type) { + case []interface{}: + return reply, nil + case nil: + return nil, ErrNil + case Error: + return nil, reply + } + return nil, fmt.Errorf("redigo: unexpected type for Values, got type %T", reply) +} + +func sliceHelper(reply interface{}, err error, name string, makeSlice func(int), assign func(int, interface{}) error) error { + if err != nil { + return err + } + switch reply := reply.(type) { + case []interface{}: + makeSlice(len(reply)) + for i := range reply { + if reply[i] == nil { + continue + } + if err := assign(i, reply[i]); err != nil { + return err + } + } + return nil + case nil: + return ErrNil + case Error: + return reply + } + return fmt.Errorf("redigo: unexpected type for %s, got type %T", name, reply) +} + +// Float64s is a helper that converts an array command reply to a []float64. If +// err is not equal to nil, then Float64s returns nil, err. Nil array items are +// converted to 0 in the output slice. Floats64 returns an error if an array +// item is not a bulk string or nil. +func Float64s(reply interface{}, err error) ([]float64, error) { + var result []float64 + err = sliceHelper(reply, err, "Float64s", func(n int) { result = make([]float64, n) }, func(i int, v interface{}) error { + p, ok := v.([]byte) + if !ok { + return fmt.Errorf("redigo: unexpected element type for Floats64, got type %T", v) + } + f, err := strconv.ParseFloat(string(p), 64) + result[i] = f + return err + }) + return result, err +} + +// Strings is a helper that converts an array command reply to a []string. If +// err is not equal to nil, then Strings returns nil, err. Nil array items are +// converted to "" in the output slice. Strings returns an error if an array +// item is not a bulk string or nil. +func Strings(reply interface{}, err error) ([]string, error) { + var result []string + err = sliceHelper(reply, err, "Strings", func(n int) { result = make([]string, n) }, func(i int, v interface{}) error { + switch v := v.(type) { + case string: + result[i] = v + return nil + case []byte: + result[i] = string(v) + return nil + default: + return fmt.Errorf("redigo: unexpected element type for Strings, got type %T", v) + } + }) + return result, err +} + +// ByteSlices is a helper that converts an array command reply to a [][]byte. +// If err is not equal to nil, then ByteSlices returns nil, err. Nil array +// items are stay nil. ByteSlices returns an error if an array item is not a +// bulk string or nil. +func ByteSlices(reply interface{}, err error) ([][]byte, error) { + var result [][]byte + err = sliceHelper(reply, err, "ByteSlices", func(n int) { result = make([][]byte, n) }, func(i int, v interface{}) error { + p, ok := v.([]byte) + if !ok { + return fmt.Errorf("redigo: unexpected element type for ByteSlices, got type %T", v) + } + result[i] = p + return nil + }) + return result, err +} + +// Int64s is a helper that converts an array command reply to a []int64. +// If err is not equal to nil, then Int64s returns nil, err. Nil array +// items are stay nil. Int64s returns an error if an array item is not a +// bulk string or nil. +func Int64s(reply interface{}, err error) ([]int64, error) { + var result []int64 + err = sliceHelper(reply, err, "Int64s", func(n int) { result = make([]int64, n) }, func(i int, v interface{}) error { + switch v := v.(type) { + case int64: + result[i] = v + return nil + case []byte: + n, err := strconv.ParseInt(string(v), 10, 64) + result[i] = n + return err + default: + return fmt.Errorf("redigo: unexpected element type for Int64s, got type %T", v) + } + }) + return result, err +} + +// Ints is a helper that converts an array command reply to a []in. +// If err is not equal to nil, then Ints returns nil, err. Nil array +// items are stay nil. Ints returns an error if an array item is not a +// bulk string or nil. +func Ints(reply interface{}, err error) ([]int, error) { + var result []int + err = sliceHelper(reply, err, "Ints", func(n int) { result = make([]int, n) }, func(i int, v interface{}) error { + switch v := v.(type) { + case int64: + n := int(v) + if int64(n) != v { + return strconv.ErrRange + } + result[i] = n + return nil + case []byte: + n, err := strconv.Atoi(string(v)) + result[i] = n + return err + default: + return fmt.Errorf("redigo: unexpected element type for Ints, got type %T", v) + } + }) + return result, err +} + +// StringMap is a helper that converts an array of strings (alternating key, value) +// into a map[string]string. The HGETALL and CONFIG GET commands return replies in this format. +// Requires an even number of values in result. +func StringMap(result interface{}, err error) (map[string]string, error) { + values, err := Values(result, err) + if err != nil { + return nil, err + } + if len(values)%2 != 0 { + return nil, errors.New("redigo: StringMap expects even number of values result") + } + m := make(map[string]string, len(values)/2) + for i := 0; i < len(values); i += 2 { + key, okKey := values[i].([]byte) + value, okValue := values[i+1].([]byte) + if !okKey || !okValue { + return nil, errors.New("redigo: StringMap key not a bulk string value") + } + m[string(key)] = string(value) + } + return m, nil +} + +// IntMap is a helper that converts an array of strings (alternating key, value) +// into a map[string]int. The HGETALL commands return replies in this format. +// Requires an even number of values in result. +func IntMap(result interface{}, err error) (map[string]int, error) { + values, err := Values(result, err) + if err != nil { + return nil, err + } + if len(values)%2 != 0 { + return nil, errors.New("redigo: IntMap expects even number of values result") + } + m := make(map[string]int, len(values)/2) + for i := 0; i < len(values); i += 2 { + key, ok := values[i].([]byte) + if !ok { + return nil, errors.New("redigo: IntMap key not a bulk string value") + } + value, err := Int(values[i+1], nil) + if err != nil { + return nil, err + } + m[string(key)] = value + } + return m, nil +} + +// Int64Map is a helper that converts an array of strings (alternating key, value) +// into a map[string]int64. The HGETALL commands return replies in this format. +// Requires an even number of values in result. +func Int64Map(result interface{}, err error) (map[string]int64, error) { + values, err := Values(result, err) + if err != nil { + return nil, err + } + if len(values)%2 != 0 { + return nil, errors.New("redigo: Int64Map expects even number of values result") + } + m := make(map[string]int64, len(values)/2) + for i := 0; i < len(values); i += 2 { + key, ok := values[i].([]byte) + if !ok { + return nil, errors.New("redigo: Int64Map key not a bulk string value") + } + value, err := Int64(values[i+1], nil) + if err != nil { + return nil, err + } + m[string(key)] = value + } + return m, nil +} + +// Positions is a helper that converts an array of positions (lat, long) +// into a [][2]float64. The GEOPOS command returns replies in this format. +func Positions(result interface{}, err error) ([]*[2]float64, error) { + values, err := Values(result, err) + if err != nil { + return nil, err + } + positions := make([]*[2]float64, len(values)) + for i := range values { + if values[i] == nil { + continue + } + p, ok := values[i].([]interface{}) + if !ok { + return nil, fmt.Errorf("redigo: unexpected element type for interface slice, got type %T", values[i]) + } + if len(p) != 2 { + return nil, fmt.Errorf("redigo: unexpected number of values for a member position, got %d", len(p)) + } + lat, err := Float64(p[0], nil) + if err != nil { + return nil, err + } + long, err := Float64(p[1], nil) + if err != nil { + return nil, err + } + positions[i] = &[2]float64{lat, long} + } + return positions, nil +} diff --git a/vendor/github.com/gomodule/redigo/redis/scan.go b/vendor/github.com/gomodule/redigo/redis/scan.go new file mode 100644 index 00000000000..ef9551bd476 --- /dev/null +++ b/vendor/github.com/gomodule/redigo/redis/scan.go @@ -0,0 +1,585 @@ +// Copyright 2012 Gary Burd +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package redis + +import ( + "errors" + "fmt" + "reflect" + "strconv" + "strings" + "sync" +) + +func ensureLen(d reflect.Value, n int) { + if n > d.Cap() { + d.Set(reflect.MakeSlice(d.Type(), n, n)) + } else { + d.SetLen(n) + } +} + +func cannotConvert(d reflect.Value, s interface{}) error { + var sname string + switch s.(type) { + case string: + sname = "Redis simple string" + case Error: + sname = "Redis error" + case int64: + sname = "Redis integer" + case []byte: + sname = "Redis bulk string" + case []interface{}: + sname = "Redis array" + default: + sname = reflect.TypeOf(s).String() + } + return fmt.Errorf("cannot convert from %s to %s", sname, d.Type()) +} + +func convertAssignBulkString(d reflect.Value, s []byte) (err error) { + switch d.Type().Kind() { + case reflect.Float32, reflect.Float64: + var x float64 + x, err = strconv.ParseFloat(string(s), d.Type().Bits()) + d.SetFloat(x) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + var x int64 + x, err = strconv.ParseInt(string(s), 10, d.Type().Bits()) + d.SetInt(x) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + var x uint64 + x, err = strconv.ParseUint(string(s), 10, d.Type().Bits()) + d.SetUint(x) + case reflect.Bool: + var x bool + x, err = strconv.ParseBool(string(s)) + d.SetBool(x) + case reflect.String: + d.SetString(string(s)) + case reflect.Slice: + if d.Type().Elem().Kind() != reflect.Uint8 { + err = cannotConvert(d, s) + } else { + d.SetBytes(s) + } + default: + err = cannotConvert(d, s) + } + return +} + +func convertAssignInt(d reflect.Value, s int64) (err error) { + switch d.Type().Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + d.SetInt(s) + if d.Int() != s { + err = strconv.ErrRange + d.SetInt(0) + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + if s < 0 { + err = strconv.ErrRange + } else { + x := uint64(s) + d.SetUint(x) + if d.Uint() != x { + err = strconv.ErrRange + d.SetUint(0) + } + } + case reflect.Bool: + d.SetBool(s != 0) + default: + err = cannotConvert(d, s) + } + return +} + +func convertAssignValue(d reflect.Value, s interface{}) (err error) { + if d.Kind() != reflect.Ptr { + if d.CanAddr() { + d2 := d.Addr() + if d2.CanInterface() { + if scanner, ok := d2.Interface().(Scanner); ok { + return scanner.RedisScan(s) + } + } + } + } else if d.CanInterface() { + // Already a reflect.Ptr + if d.IsNil() { + d.Set(reflect.New(d.Type().Elem())) + } + if scanner, ok := d.Interface().(Scanner); ok { + return scanner.RedisScan(s) + } + } + + switch s := s.(type) { + case []byte: + err = convertAssignBulkString(d, s) + case int64: + err = convertAssignInt(d, s) + default: + err = cannotConvert(d, s) + } + return err +} + +func convertAssignArray(d reflect.Value, s []interface{}) error { + if d.Type().Kind() != reflect.Slice { + return cannotConvert(d, s) + } + ensureLen(d, len(s)) + for i := 0; i < len(s); i++ { + if err := convertAssignValue(d.Index(i), s[i]); err != nil { + return err + } + } + return nil +} + +func convertAssign(d interface{}, s interface{}) (err error) { + if scanner, ok := d.(Scanner); ok { + return scanner.RedisScan(s) + } + + // Handle the most common destination types using type switches and + // fall back to reflection for all other types. + switch s := s.(type) { + case nil: + // ignore + case []byte: + switch d := d.(type) { + case *string: + *d = string(s) + case *int: + *d, err = strconv.Atoi(string(s)) + case *bool: + *d, err = strconv.ParseBool(string(s)) + case *[]byte: + *d = s + case *interface{}: + *d = s + case nil: + // skip value + default: + if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr { + err = cannotConvert(d, s) + } else { + err = convertAssignBulkString(d.Elem(), s) + } + } + case int64: + switch d := d.(type) { + case *int: + x := int(s) + if int64(x) != s { + err = strconv.ErrRange + x = 0 + } + *d = x + case *bool: + *d = s != 0 + case *interface{}: + *d = s + case nil: + // skip value + default: + if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr { + err = cannotConvert(d, s) + } else { + err = convertAssignInt(d.Elem(), s) + } + } + case string: + switch d := d.(type) { + case *string: + *d = s + case *interface{}: + *d = s + case nil: + // skip value + default: + err = cannotConvert(reflect.ValueOf(d), s) + } + case []interface{}: + switch d := d.(type) { + case *[]interface{}: + *d = s + case *interface{}: + *d = s + case nil: + // skip value + default: + if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr { + err = cannotConvert(d, s) + } else { + err = convertAssignArray(d.Elem(), s) + } + } + case Error: + err = s + default: + err = cannotConvert(reflect.ValueOf(d), s) + } + return +} + +// Scan copies from src to the values pointed at by dest. +// +// Scan uses RedisScan if available otherwise: +// +// The values pointed at by dest must be an integer, float, boolean, string, +// []byte, interface{} or slices of these types. Scan uses the standard strconv +// package to convert bulk strings to numeric and boolean types. +// +// If a dest value is nil, then the corresponding src value is skipped. +// +// If a src element is nil, then the corresponding dest value is not modified. +// +// To enable easy use of Scan in a loop, Scan returns the slice of src +// following the copied values. +func Scan(src []interface{}, dest ...interface{}) ([]interface{}, error) { + if len(src) < len(dest) { + return nil, errors.New("redigo.Scan: array short") + } + var err error + for i, d := range dest { + err = convertAssign(d, src[i]) + if err != nil { + err = fmt.Errorf("redigo.Scan: cannot assign to dest %d: %v", i, err) + break + } + } + return src[len(dest):], err +} + +type fieldSpec struct { + name string + index []int + omitEmpty bool +} + +type structSpec struct { + m map[string]*fieldSpec + l []*fieldSpec +} + +func (ss *structSpec) fieldSpec(name []byte) *fieldSpec { + return ss.m[string(name)] +} + +func compileStructSpec(t reflect.Type, depth map[string]int, index []int, ss *structSpec) { + for i := 0; i < t.NumField(); i++ { + f := t.Field(i) + switch { + case f.PkgPath != "" && !f.Anonymous: + // Ignore unexported fields. + case f.Anonymous: + // TODO: Handle pointers. Requires change to decoder and + // protection against infinite recursion. + if f.Type.Kind() == reflect.Struct { + compileStructSpec(f.Type, depth, append(index, i), ss) + } + default: + fs := &fieldSpec{name: f.Name} + tag := f.Tag.Get("redis") + p := strings.Split(tag, ",") + if len(p) > 0 { + if p[0] == "-" { + continue + } + if len(p[0]) > 0 { + fs.name = p[0] + } + for _, s := range p[1:] { + switch s { + case "omitempty": + fs.omitEmpty = true + default: + panic(fmt.Errorf("redigo: unknown field tag %s for type %s", s, t.Name())) + } + } + } + d, found := depth[fs.name] + if !found { + d = 1 << 30 + } + switch { + case len(index) == d: + // At same depth, remove from result. + delete(ss.m, fs.name) + j := 0 + for i := 0; i < len(ss.l); i++ { + if fs.name != ss.l[i].name { + ss.l[j] = ss.l[i] + j += 1 + } + } + ss.l = ss.l[:j] + case len(index) < d: + fs.index = make([]int, len(index)+1) + copy(fs.index, index) + fs.index[len(index)] = i + depth[fs.name] = len(index) + ss.m[fs.name] = fs + ss.l = append(ss.l, fs) + } + } + } +} + +var ( + structSpecMutex sync.RWMutex + structSpecCache = make(map[reflect.Type]*structSpec) + defaultFieldSpec = &fieldSpec{} +) + +func structSpecForType(t reflect.Type) *structSpec { + + structSpecMutex.RLock() + ss, found := structSpecCache[t] + structSpecMutex.RUnlock() + if found { + return ss + } + + structSpecMutex.Lock() + defer structSpecMutex.Unlock() + ss, found = structSpecCache[t] + if found { + return ss + } + + ss = &structSpec{m: make(map[string]*fieldSpec)} + compileStructSpec(t, make(map[string]int), nil, ss) + structSpecCache[t] = ss + return ss +} + +var errScanStructValue = errors.New("redigo.ScanStruct: value must be non-nil pointer to a struct") + +// ScanStruct scans alternating names and values from src to a struct. The +// HGETALL and CONFIG GET commands return replies in this format. +// +// ScanStruct uses exported field names to match values in the response. Use +// 'redis' field tag to override the name: +// +// Field int `redis:"myName"` +// +// Fields with the tag redis:"-" are ignored. +// +// Each field uses RedisScan if available otherwise: +// Integer, float, boolean, string and []byte fields are supported. Scan uses the +// standard strconv package to convert bulk string values to numeric and +// boolean types. +// +// If a src element is nil, then the corresponding field is not modified. +func ScanStruct(src []interface{}, dest interface{}) error { + d := reflect.ValueOf(dest) + if d.Kind() != reflect.Ptr || d.IsNil() { + return errScanStructValue + } + d = d.Elem() + if d.Kind() != reflect.Struct { + return errScanStructValue + } + ss := structSpecForType(d.Type()) + + if len(src)%2 != 0 { + return errors.New("redigo.ScanStruct: number of values not a multiple of 2") + } + + for i := 0; i < len(src); i += 2 { + s := src[i+1] + if s == nil { + continue + } + name, ok := src[i].([]byte) + if !ok { + return fmt.Errorf("redigo.ScanStruct: key %d not a bulk string value", i) + } + fs := ss.fieldSpec(name) + if fs == nil { + continue + } + if err := convertAssignValue(d.FieldByIndex(fs.index), s); err != nil { + return fmt.Errorf("redigo.ScanStruct: cannot assign field %s: %v", fs.name, err) + } + } + return nil +} + +var ( + errScanSliceValue = errors.New("redigo.ScanSlice: dest must be non-nil pointer to a struct") +) + +// ScanSlice scans src to the slice pointed to by dest. The elements the dest +// slice must be integer, float, boolean, string, struct or pointer to struct +// values. +// +// Struct fields must be integer, float, boolean or string values. All struct +// fields are used unless a subset is specified using fieldNames. +func ScanSlice(src []interface{}, dest interface{}, fieldNames ...string) error { + d := reflect.ValueOf(dest) + if d.Kind() != reflect.Ptr || d.IsNil() { + return errScanSliceValue + } + d = d.Elem() + if d.Kind() != reflect.Slice { + return errScanSliceValue + } + + isPtr := false + t := d.Type().Elem() + if t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct { + isPtr = true + t = t.Elem() + } + + if t.Kind() != reflect.Struct { + ensureLen(d, len(src)) + for i, s := range src { + if s == nil { + continue + } + if err := convertAssignValue(d.Index(i), s); err != nil { + return fmt.Errorf("redigo.ScanSlice: cannot assign element %d: %v", i, err) + } + } + return nil + } + + ss := structSpecForType(t) + fss := ss.l + if len(fieldNames) > 0 { + fss = make([]*fieldSpec, len(fieldNames)) + for i, name := range fieldNames { + fss[i] = ss.m[name] + if fss[i] == nil { + return fmt.Errorf("redigo.ScanSlice: ScanSlice bad field name %s", name) + } + } + } + + if len(fss) == 0 { + return errors.New("redigo.ScanSlice: no struct fields") + } + + n := len(src) / len(fss) + if n*len(fss) != len(src) { + return errors.New("redigo.ScanSlice: length not a multiple of struct field count") + } + + ensureLen(d, n) + for i := 0; i < n; i++ { + d := d.Index(i) + if isPtr { + if d.IsNil() { + d.Set(reflect.New(t)) + } + d = d.Elem() + } + for j, fs := range fss { + s := src[i*len(fss)+j] + if s == nil { + continue + } + if err := convertAssignValue(d.FieldByIndex(fs.index), s); err != nil { + return fmt.Errorf("redigo.ScanSlice: cannot assign element %d to field %s: %v", i*len(fss)+j, fs.name, err) + } + } + } + return nil +} + +// Args is a helper for constructing command arguments from structured values. +type Args []interface{} + +// Add returns the result of appending value to args. +func (args Args) Add(value ...interface{}) Args { + return append(args, value...) +} + +// AddFlat returns the result of appending the flattened value of v to args. +// +// Maps are flattened by appending the alternating keys and map values to args. +// +// Slices are flattened by appending the slice elements to args. +// +// Structs are flattened by appending the alternating names and values of +// exported fields to args. If v is a nil struct pointer, then nothing is +// appended. The 'redis' field tag overrides struct field names. See ScanStruct +// for more information on the use of the 'redis' field tag. +// +// Other types are appended to args as is. +func (args Args) AddFlat(v interface{}) Args { + rv := reflect.ValueOf(v) + switch rv.Kind() { + case reflect.Struct: + args = flattenStruct(args, rv) + case reflect.Slice: + for i := 0; i < rv.Len(); i++ { + args = append(args, rv.Index(i).Interface()) + } + case reflect.Map: + for _, k := range rv.MapKeys() { + args = append(args, k.Interface(), rv.MapIndex(k).Interface()) + } + case reflect.Ptr: + if rv.Type().Elem().Kind() == reflect.Struct { + if !rv.IsNil() { + args = flattenStruct(args, rv.Elem()) + } + } else { + args = append(args, v) + } + default: + args = append(args, v) + } + return args +} + +func flattenStruct(args Args, v reflect.Value) Args { + ss := structSpecForType(v.Type()) + for _, fs := range ss.l { + fv := v.FieldByIndex(fs.index) + if fs.omitEmpty { + var empty = false + switch fv.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + empty = fv.Len() == 0 + case reflect.Bool: + empty = !fv.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + empty = fv.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + empty = fv.Uint() == 0 + case reflect.Float32, reflect.Float64: + empty = fv.Float() == 0 + case reflect.Interface, reflect.Ptr: + empty = fv.IsNil() + } + if empty { + continue + } + } + args = append(args, fs.name, fv.Interface()) + } + return args +} diff --git a/vendor/github.com/gomodule/redigo/redis/script.go b/vendor/github.com/gomodule/redigo/redis/script.go new file mode 100644 index 00000000000..0ef1c821f71 --- /dev/null +++ b/vendor/github.com/gomodule/redigo/redis/script.go @@ -0,0 +1,91 @@ +// Copyright 2012 Gary Burd +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package redis + +import ( + "crypto/sha1" + "encoding/hex" + "io" + "strings" +) + +// Script encapsulates the source, hash and key count for a Lua script. See +// http://redis.io/commands/eval for information on scripts in Redis. +type Script struct { + keyCount int + src string + hash string +} + +// NewScript returns a new script object. If keyCount is greater than or equal +// to zero, then the count is automatically inserted in the EVAL command +// argument list. If keyCount is less than zero, then the application supplies +// the count as the first value in the keysAndArgs argument to the Do, Send and +// SendHash methods. +func NewScript(keyCount int, src string) *Script { + h := sha1.New() + io.WriteString(h, src) + return &Script{keyCount, src, hex.EncodeToString(h.Sum(nil))} +} + +func (s *Script) args(spec string, keysAndArgs []interface{}) []interface{} { + var args []interface{} + if s.keyCount < 0 { + args = make([]interface{}, 1+len(keysAndArgs)) + args[0] = spec + copy(args[1:], keysAndArgs) + } else { + args = make([]interface{}, 2+len(keysAndArgs)) + args[0] = spec + args[1] = s.keyCount + copy(args[2:], keysAndArgs) + } + return args +} + +// Hash returns the script hash. +func (s *Script) Hash() string { + return s.hash +} + +// Do evaluates the script. Under the covers, Do optimistically evaluates the +// script using the EVALSHA command. If the command fails because the script is +// not loaded, then Do evaluates the script using the EVAL command (thus +// causing the script to load). +func (s *Script) Do(c Conn, keysAndArgs ...interface{}) (interface{}, error) { + v, err := c.Do("EVALSHA", s.args(s.hash, keysAndArgs)...) + if e, ok := err.(Error); ok && strings.HasPrefix(string(e), "NOSCRIPT ") { + v, err = c.Do("EVAL", s.args(s.src, keysAndArgs)...) + } + return v, err +} + +// SendHash evaluates the script without waiting for the reply. The script is +// evaluated with the EVALSHA command. The application must ensure that the +// script is loaded by a previous call to Send, Do or Load methods. +func (s *Script) SendHash(c Conn, keysAndArgs ...interface{}) error { + return c.Send("EVALSHA", s.args(s.hash, keysAndArgs)...) +} + +// Send evaluates the script without waiting for the reply. +func (s *Script) Send(c Conn, keysAndArgs ...interface{}) error { + return c.Send("EVAL", s.args(s.src, keysAndArgs)...) +} + +// Load loads the script without evaluating it. +func (s *Script) Load(c Conn) error { + _, err := c.Do("SCRIPT", "LOAD", s.src) + return err +} diff --git a/vendor/github.com/yuin/gopher-lua/.travis.yml b/vendor/github.com/yuin/gopher-lua/.travis.yml new file mode 100644 index 00000000000..68df5e7b1a1 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/.travis.yml @@ -0,0 +1,18 @@ +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/LICENSE b/vendor/github.com/yuin/gopher-lua/LICENSE new file mode 100644 index 00000000000..4daf480a2fd --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Yusuke Inuzuka + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/yuin/gopher-lua/Makefile b/vendor/github.com/yuin/gopher-lua/Makefile new file mode 100644 index 00000000000..6d9e55c35f7 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/Makefile @@ -0,0 +1,10 @@ +.PHONY: build test glua + +build: + ./_tools/go-inline *.go && go fmt . && go build + +glua: *.go pm/*.go cmd/glua/glua.go + ./_tools/go-inline *.go && go fmt . && go build cmd/glua/glua.go + +test: + ./_tools/go-inline *.go && go fmt . && go test diff --git a/vendor/github.com/yuin/gopher-lua/README.rst b/vendor/github.com/yuin/gopher-lua/README.rst new file mode 100644 index 00000000000..b479e46357e --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/README.rst @@ -0,0 +1,887 @@ + +=============================================================================== +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://travis-ci.org/yuin/gopher-lua.svg + :target: https://travis-ci.org/yuin/gopher-lua + +.. image:: https://coveralls.io/repos/yuin/gopher-lua/badge.svg + :target: https://coveralls.io/r/yuin/gopher-lua + +.. image:: https://badges.gitter.im/Join%20Chat.svg + :alt: Join the chat at https://gitter.im/yuin/gopher-lua + :target: https://gitter.im/yuin/gopher-lua?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge + +| + + +GopherLua is a Lua5.1 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. + +.. contents:: + :depth: 1 + +---------------------------------------------------------------- +Design principle +---------------------------------------------------------------- + +- Be a scripting language with extensible semantics. +- User-friendly Go API + - The stack based API like the one used in the original Lua + implementation will cause a performance improvements in GopherLua + (It will reduce memory allocations and concrete type <-> interface conversions). + GopherLua API is **not** the stack based API. + GopherLua give preference to the user-friendliness over the performance. + +---------------------------------------------------------------- +How about performance? +---------------------------------------------------------------- +GopherLua is not fast but not too slow, I think. + +GopherLua has almost equivalent ( or little bit better ) performance as Python3 on micro benchmarks. + +There are some benchmarks on the `wiki page `_ . + +---------------------------------------------------------------- +Installation +---------------------------------------------------------------- + +.. code-block:: bash + + go get github.com/yuin/gopher-lua + +GopherLua supports >= Go1.9. + +---------------------------------------------------------------- +Usage +---------------------------------------------------------------- +GopherLua APIs perform in much the same way as Lua, **but the stack is used only +for passing arguments and receiving returned values.** + +GopherLua supports channel operations. See **"Goroutines"** section. + +Import a package. + +.. code-block:: go + + import ( + "github.com/yuin/gopher-lua" + ) + +Run scripts in the VM. + +.. code-block:: go + + L := lua.NewState() + defer L.Close() + if err := L.DoString(`print("hello")`); err != nil { + panic(err) + } + +.. code-block:: go + + L := lua.NewState() + defer L.Close() + if err := L.DoFile("hello.lua"); err != nil { + panic(err) + } + +Refer to `Lua Reference Manual `_ and `Go doc `_ for further information. + +Note that elements that are not commented in `Go doc `_ equivalent to `Lua Reference Manual `_ , except GopherLua uses objects instead of Lua stack indices. + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Data model +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +All data in a GopherLua program is an ``LValue`` . ``LValue`` is an interface +type that has following methods. + +- ``String() string`` +- ``Type() LValueType`` + + +Objects implement an LValue interface are + +================ ========================= ================== ======================= + Type name Go type Type() value Constants +================ ========================= ================== ======================= + ``LNilType`` (constants) ``LTNil`` ``LNil`` + ``LBool`` (constants) ``LTBool`` ``LTrue``, ``LFalse`` + ``LNumber`` float64 ``LTNumber`` ``-`` + ``LString`` string ``LTString`` ``-`` + ``LFunction`` struct pointer ``LTFunction`` ``-`` + ``LUserData`` struct pointer ``LTUserData`` ``-`` + ``LState`` struct pointer ``LTThread`` ``-`` + ``LTable`` struct pointer ``LTTable`` ``-`` + ``LChannel`` chan LValue ``LTChannel`` ``-`` +================ ========================= ================== ======================= + +You can test an object type in Go way(type assertion) or using a ``Type()`` value. + +.. code-block:: go + + lv := L.Get(-1) // get the value at the top of the stack + if str, ok := lv.(lua.LString); ok { + // lv is LString + fmt.Println(string(str)) + } + if lv.Type() != lua.LTString { + panic("string required.") + } + +.. code-block:: go + + lv := L.Get(-1) // get the value at the top of the stack + if tbl, ok := lv.(*lua.LTable); ok { + // lv is LTable + fmt.Println(L.ObjLen(tbl)) + } + +Note that ``LBool`` , ``LNumber`` , ``LString`` is not a pointer. + +To test ``LNilType`` and ``LBool``, You **must** use pre-defined constants. + +.. code-block:: go + + lv := L.Get(-1) // get the value at the top of the stack + + if lv == lua.LTrue { // correct + } + + if bl, ok := lv.(lua.LBool); ok && bool(bl) { // wrong + } + +In Lua, both ``nil`` and ``false`` make a condition false. ``LVIsFalse`` and ``LVAsBool`` implement this specification. + +.. code-block:: go + + lv := L.Get(-1) // get the value at the top of the stack + if lua.LVIsFalse(lv) { // lv is nil or false + } + + if lua.LVAsBool(lv) { // lv is neither nil nor false + } + +Objects that based on go structs(``LFunction``. ``LUserData``, ``LTable``) +have some public methods and fields. You can use these methods and fields for +performance and debugging, but there are some limitations. + +- Metatable does not work. +- No error handlings. + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Callstack & Registry size +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The size of an ``LState``'s callstack controls the maximum call depth for Lua functions within a script (Go function calls do not count). + +The registry of an ``LState`` implements stack storage for calling functions (both Lua and Go functions) and also for temporary variables in expressions. Its storage requirements will increase with callstack usage and also with code complexity. + +Both the registry and the callstack can be set to either a fixed size or to auto size. + +When you have a large number of ``LStates`` instantiated in a process, it's worth taking the time to tune the registry and callstack options. + ++++++++++ +Registry ++++++++++ + +The registry can have an initial size, a maximum size and a step size configured on a per ``LState`` basis. This will allow the registry to grow as needed. It will not shrink again after growing. + +.. code-block:: go + + L := lua.NewState(lua.Options{ + RegistrySize: 1024 * 20, // this is the initial size of the registry + RegistryMaxSize: 1024 * 80, // this is the maximum size that the registry can grow to. If set to `0` (the default) then the registry will not auto grow + RegistryGrowStep: 32, // this is how much to step up the registry by each time it runs out of space. The default is `32`. + }) + defer L.Close() + +A registry which is too small for a given script will ultimately result in a panic. A registry which is too big will waste memory (which can be significant if many ``LStates`` are instantiated). +Auto growing registries incur a small performance hit at the point they are resized but will not otherwise affect performance. + ++++++++++ +Callstack ++++++++++ + +The callstack can operate in two different modes, fixed or auto size. +A fixed size callstack has the highest performance and has a fixed memory overhead. +An auto sizing callstack will allocate and release callstack pages on demand which will ensure the minimum amount of memory is in use at any time. The downside is it will incur a small performance impact every time a new page of callframes is allocated. +By default an ``LState`` will allocate and free callstack frames in pages of 8, so the allocation overhead is not incurred on every function call. It is very likely that the performance impact of an auto resizing callstack will be negligible for most use cases. + +.. code-block:: go + + L := lua.NewState(lua.Options{ + CallStackSize: 120, // this is the maximum callstack size of this LState + MinimizeStackMemory: true, // Defaults to `false` if not specified. If set, the callstack will auto grow and shrink as needed up to a max of `CallStackSize`. If not set, the callstack will be fixed at `CallStackSize`. + }) + defer L.Close() + +++++++++++++++++ +Option defaults +++++++++++++++++ + +The above examples show how to customize the callstack and registry size on a per ``LState`` basis. You can also adjust some defaults for when options are not specified by altering the values of ``lua.RegistrySize``, ``lua.RegistryGrowStep`` and ``lua.CallStackSize``. + +An ``LState`` object that has been created by ``*LState#NewThread()`` inherits the callstack & registry size from the parent ``LState`` object. + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Miscellaneous lua.NewState options +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +- **Options.SkipOpenLibs bool(default false)** + - By default, GopherLua opens all built-in libraries when new LState is created. + - You can skip this behaviour by setting this to ``true`` . + - Using the various `OpenXXX(L *LState) int` functions you can open only those libraries that you require, for an example see below. +- **Options.IncludeGoStackTrace bool(default false)** + - By default, GopherLua does not show Go stack traces when panics occur. + - You can get Go stack traces by setting this to ``true`` . + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +API +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Refer to `Lua Reference Manual `_ and `Go doc(LState methods) `_ for further information. + ++++++++++++++++++++++++++++++++++++++++++ +Calling Go from Lua ++++++++++++++++++++++++++++++++++++++++++ + +.. code-block:: go + + func Double(L *lua.LState) int { + lv := L.ToInt(1) /* get argument */ + L.Push(lua.LNumber(lv * 2)) /* push result */ + return 1 /* number of results */ + } + + func main() { + L := lua.NewState() + defer L.Close() + L.SetGlobal("double", L.NewFunction(Double)) /* Original lua_setglobal uses stack... */ + } + +.. code-block:: lua + + print(double(20)) -- > "40" + +Any function registered with GopherLua is a ``lua.LGFunction``, defined in ``value.go`` + +.. code-block:: go + + type LGFunction func(*LState) int + +Working with coroutines. + +.. code-block:: go + + co, _ := L.NewThread() /* create a new thread */ + fn := L.GetGlobal("coro").(*lua.LFunction) /* get function from lua */ + for { + st, err, values := L.Resume(co, fn) + if st == lua.ResumeError { + fmt.Println("yield break(error)") + fmt.Println(err.Error()) + break + } + + for i, lv := range values { + fmt.Printf("%v : %v\n", i, lv) + } + + if st == lua.ResumeOK { + fmt.Println("yield break(ok)") + break + } + } + ++++++++++++++++++++++++++++++++++++++++++ +Opening a subset of builtin modules ++++++++++++++++++++++++++++++++++++++++++ + +The following demonstrates how to open a subset of the built-in modules in Lua, say for example to avoid enabling modules with access to local files or system calls. + +main.go + +.. code-block:: go + + func main() { + L := lua.NewState(lua.Options{SkipOpenLibs: true}) + defer L.Close() + for _, pair := range []struct { + n string + f lua.LGFunction + }{ + {lua.LoadLibName, lua.OpenPackage}, // Must be first + {lua.BaseLibName, lua.OpenBase}, + {lua.TabLibName, lua.OpenTable}, + } { + if err := L.CallByParam(lua.P{ + Fn: L.NewFunction(pair.f), + NRet: 0, + Protect: true, + }, lua.LString(pair.n)); err != nil { + panic(err) + } + } + if err := L.DoFile("main.lua"); err != nil { + panic(err) + } + } + ++++++++++++++++++++++++++++++++++++++++++ +Creating a module by Go ++++++++++++++++++++++++++++++++++++++++++ + +mymodule.go + +.. code-block:: go + + package mymodule + + import ( + "github.com/yuin/gopher-lua" + ) + + func Loader(L *lua.LState) int { + // register functions to the table + mod := L.SetFuncs(L.NewTable(), exports) + // register other stuff + L.SetField(mod, "name", lua.LString("value")) + + // returns the module + L.Push(mod) + return 1 + } + + var exports = map[string]lua.LGFunction{ + "myfunc": myfunc, + } + + func myfunc(L *lua.LState) int { + return 0 + } + +mymain.go + +.. code-block:: go + + package main + + import ( + "./mymodule" + "github.com/yuin/gopher-lua" + ) + + func main() { + L := lua.NewState() + defer L.Close() + L.PreloadModule("mymodule", mymodule.Loader) + if err := L.DoFile("main.lua"); err != nil { + panic(err) + } + } + +main.lua + +.. code-block:: lua + + local m = require("mymodule") + m.myfunc() + print(m.name) + + ++++++++++++++++++++++++++++++++++++++++++ +Calling Lua from Go ++++++++++++++++++++++++++++++++++++++++++ + +.. code-block:: go + + L := lua.NewState() + defer L.Close() + if err := L.DoFile("double.lua"); err != nil { + panic(err) + } + if err := L.CallByParam(lua.P{ + Fn: L.GetGlobal("double"), + NRet: 1, + Protect: true, + }, lua.LNumber(10)); err != nil { + panic(err) + } + ret := L.Get(-1) // returned value + L.Pop(1) // remove received value + +If ``Protect`` is false, GopherLua will panic instead of returning an ``error`` value. + ++++++++++++++++++++++++++++++++++++++++++ +User-Defined types ++++++++++++++++++++++++++++++++++++++++++ +You can extend GopherLua with new types written in Go. +``LUserData`` is provided for this purpose. + +.. code-block:: go + + type Person struct { + Name string + } + + const luaPersonTypeName = "person" + + // Registers my person type to given L. + func registerPersonType(L *lua.LState) { + mt := L.NewTypeMetatable(luaPersonTypeName) + L.SetGlobal("person", mt) + // static attributes + L.SetField(mt, "new", L.NewFunction(newPerson)) + // methods + L.SetField(mt, "__index", L.SetFuncs(L.NewTable(), personMethods)) + } + + // Constructor + func newPerson(L *lua.LState) int { + person := &Person{L.CheckString(1)} + ud := L.NewUserData() + ud.Value = person + L.SetMetatable(ud, L.GetTypeMetatable(luaPersonTypeName)) + L.Push(ud) + return 1 + } + + // Checks whether the first lua argument is a *LUserData with *Person and returns this *Person. + func checkPerson(L *lua.LState) *Person { + ud := L.CheckUserData(1) + if v, ok := ud.Value.(*Person); ok { + return v + } + L.ArgError(1, "person expected") + return nil + } + + var personMethods = map[string]lua.LGFunction{ + "name": personGetSetName, + } + + // Getter and setter for the Person#Name + func personGetSetName(L *lua.LState) int { + p := checkPerson(L) + if L.GetTop() == 2 { + p.Name = L.CheckString(2) + return 0 + } + L.Push(lua.LString(p.Name)) + return 1 + } + + func main() { + L := lua.NewState() + defer L.Close() + registerPersonType(L) + if err := L.DoString(` + p = person.new("Steeve") + print(p:name()) -- "Steeve" + p:name("Alice") + print(p:name()) -- "Alice" + `); err != nil { + panic(err) + } + } + ++++++++++++++++++++++++++++++++++++++++++ +Terminating a running LState ++++++++++++++++++++++++++++++++++++++++++ +GopherLua supports the `Go Concurrency Patterns: Context `_ . + + +.. code-block:: go + + L := lua.NewState() + defer L.Close() + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + // set the context to our LState + L.SetContext(ctx) + err := L.DoString(` + local clock = os.clock + function sleep(n) -- seconds + local t0 = clock() + while clock() - t0 <= n do end + end + sleep(3) + `) + // err.Error() contains "context deadline exceeded" + +With coroutines + +.. code-block:: go + + L := lua.NewState() + defer L.Close() + ctx, cancel := context.WithCancel(context.Background()) + L.SetContext(ctx) + defer cancel() + L.DoString(` + function coro() + local i = 0 + while true do + coroutine.yield(i) + i = i+1 + end + return i + end + `) + co, cocancel := L.NewThread() + defer cocancel() + fn := L.GetGlobal("coro").(*LFunction) + + _, err, values := L.Resume(co, fn) // err is nil + + cancel() // cancel the parent context + + _, err, values = L.Resume(co, fn) // err is NOT nil : child context was canceled + +**Note that using a context causes performance degradation.** + +.. code-block:: + + time ./glua-with-context.exe fib.lua + 9227465 + 0.01s user 0.11s system 1% cpu 7.505 total + + time ./glua-without-context.exe fib.lua + 9227465 + 0.01s user 0.01s system 0% cpu 5.306 total + ++++++++++++++++++++++++++++++++++++++++++ +Sharing Lua byte code between LStates ++++++++++++++++++++++++++++++++++++++++++ +Calling ``DoFile`` will load a Lua script, compile it to byte code and run the byte code in a ``LState``. + +If you have multiple ``LStates`` which are all required to run the same script, you can share the byte code between them, +which will save on memory. +Sharing byte code is safe as it is read only and cannot be altered by lua scripts. + +.. code-block:: go + + // CompileLua reads the passed lua file from disk and compiles it. + func CompileLua(filePath string) (*lua.FunctionProto, error) { + file, err := os.Open(filePath) + defer file.Close() + if err != nil { + return nil, err + } + reader := bufio.NewReader(file) + chunk, err := parse.Parse(reader, filePath) + if err != nil { + return nil, err + } + proto, err := lua.Compile(chunk, filePath) + if err != nil { + return nil, err + } + return proto, nil + } + + // DoCompiledFile takes a FunctionProto, as returned by CompileLua, and runs it in the LState. It is equivalent + // to calling DoFile on the LState with the original source file. + func DoCompiledFile(L *lua.LState, proto *lua.FunctionProto) error { + lfunc := L.NewFunctionFromProto(proto) + L.Push(lfunc) + return L.PCall(0, lua.MultRet, nil) + } + + // Example shows how to share the compiled byte code from a lua script between multiple VMs. + func Example() { + codeToShare := CompileLua("mylua.lua") + a := lua.NewState() + b := lua.NewState() + c := lua.NewState() + DoCompiledFile(a, codeToShare) + DoCompiledFile(b, codeToShare) + DoCompiledFile(c, codeToShare) + } + ++++++++++++++++++++++++++++++++++++++++++ +Goroutines ++++++++++++++++++++++++++++++++++++++++++ +The ``LState`` is not goroutine-safe. It is recommended to use one LState per goroutine and communicate between goroutines by using channels. + +Channels are represented by ``channel`` objects in GopherLua. And a ``channel`` table provides functions for performing channel operations. + +Some objects can not be sent over channels due to having non-goroutine-safe objects inside itself. + +- a thread(state) +- a function +- an userdata +- a table with a metatable + +You **must not** send these objects from Go APIs to channels. + + + +.. code-block:: go + + func receiver(ch, quit chan lua.LValue) { + L := lua.NewState() + defer L.Close() + L.SetGlobal("ch", lua.LChannel(ch)) + L.SetGlobal("quit", lua.LChannel(quit)) + if err := L.DoString(` + local exit = false + while not exit do + channel.select( + {"|<-", ch, function(ok, v) + if not ok then + print("channel closed") + exit = true + else + print("received:", v) + end + end}, + {"|<-", quit, function(ok, v) + print("quit") + exit = true + end} + ) + end + `); err != nil { + panic(err) + } + } + + func sender(ch, quit chan lua.LValue) { + L := lua.NewState() + defer L.Close() + L.SetGlobal("ch", lua.LChannel(ch)) + L.SetGlobal("quit", lua.LChannel(quit)) + if err := L.DoString(` + ch:send("1") + ch:send("2") + `); err != nil { + panic(err) + } + ch <- lua.LString("3") + quit <- lua.LTrue + } + + func main() { + ch := make(chan lua.LValue) + quit := make(chan lua.LValue) + go receiver(ch, quit) + go sender(ch, quit) + time.Sleep(3 * time.Second) + } + +''''''''''''''' +Go API +''''''''''''''' + +``ToChannel``, ``CheckChannel``, ``OptChannel`` are available. + +Refer to `Go doc(LState methods) `_ for further information. + +''''''''''''''' +Lua API +''''''''''''''' + +- **channel.make([buf:int]) -> ch:channel** + - Create new channel that has a buffer size of ``buf``. By default, ``buf`` is 0. + +- **channel.select(case:table [, case:table, case:table ...]) -> {index:int, recv:any, ok}** + - Same as the ``select`` statement in Go. It returns the index of the chosen case and, if that + case was a receive operation, the value received and a boolean indicating whether the channel has been closed. + - ``case`` is a table that outlined below. + - receiving: `{"|<-", ch:channel [, handler:func(ok, data:any)]}` + - sending: `{"<-|", ch:channel, data:any [, handler:func(data:any)]}` + - default: `{"default" [, handler:func()]}` + +``channel.select`` examples: + +.. code-block:: lua + + local idx, recv, ok = channel.select( + {"|<-", ch1}, + {"|<-", ch2} + ) + if not ok then + print("closed") + elseif idx == 1 then -- received from ch1 + print(recv) + elseif idx == 2 then -- received from ch2 + print(recv) + end + +.. code-block:: lua + + channel.select( + {"|<-", ch1, function(ok, data) + print(ok, data) + end}, + {"<-|", ch2, "value", function(data) + print(data) + end}, + {"default", function() + print("default action") + end} + ) + +- **channel:send(data:any)** + - Send ``data`` over the channel. +- **channel:receive() -> ok:bool, data:any** + - Receive some data over the channel. +- **channel:close()** + - Close the channel. + +'''''''''''''''''''''''''''''' +The LState pool pattern +'''''''''''''''''''''''''''''' +To create per-thread LState instances, You can use the ``sync.Pool`` like mechanism. + +.. code-block:: go + + type lStatePool struct { + m sync.Mutex + saved []*lua.LState + } + + func (pl *lStatePool) Get() *lua.LState { + pl.m.Lock() + defer pl.m.Unlock() + n := len(pl.saved) + if n == 0 { + return pl.New() + } + x := pl.saved[n-1] + pl.saved = pl.saved[0 : n-1] + return x + } + + func (pl *lStatePool) New() *lua.LState { + L := lua.NewState() + // setting the L up here. + // load scripts, set global variables, share channels, etc... + return L + } + + func (pl *lStatePool) Put(L *lua.LState) { + pl.m.Lock() + defer pl.m.Unlock() + pl.saved = append(pl.saved, L) + } + + func (pl *lStatePool) Shutdown() { + for _, L := range pl.saved { + L.Close() + } + } + + // Global LState pool + var luaPool = &lStatePool{ + saved: make([]*lua.LState, 0, 4), + } + +Now, you can get per-thread LState objects from the ``luaPool`` . + +.. code-block:: go + + func MyWorker() { + L := luaPool.Get() + defer luaPool.Put(L) + /* your code here */ + } + + func main() { + defer luaPool.Shutdown() + go MyWorker() + go MyWorker() + /* etc... */ + } + + +---------------------------------------------------------------- +Differences between Lua and GopherLua +---------------------------------------------------------------- +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Goroutines +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- GopherLua supports channel operations. + - GopherLua has a type named ``channel``. + - The ``channel`` table provides functions for performing channel operations. + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Unsupported functions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- ``string.dump`` +- ``os.setlocale`` +- ``lua_Debug.namewhat`` +- ``package.loadlib`` +- debug hooks + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Miscellaneous notes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- ``collectgarbage`` does not take any arguments and runs the garbage collector for the entire Go program. +- ``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)`` + +---------------------------------------------------------------- +Standalone interpreter +---------------------------------------------------------------- +Lua has an interpreter called ``lua`` . GopherLua has an interpreter called ``glua`` . + +.. code-block:: bash + + go get github.com/yuin/gopher-lua/cmd/glua + +``glua`` has same options as ``lua`` . + +---------------------------------------------------------------- +How to Contribute +---------------------------------------------------------------- +See `Guidlines for contributors `_ . + +---------------------------------------------------------------- +Libraries for GopherLua +---------------------------------------------------------------- + +- `gopher-luar `_ : Simplifies data passing to and from gopher-lua +- `gluamapper `_ : Mapping a Lua table to a Go struct +- `gluare `_ : Regular expressions for gopher-lua +- `gluahttp `_ : HTTP request module for gopher-lua +- `gopher-json `_ : A simple JSON encoder/decoder for gopher-lua +- `gluayaml `_ : Yaml parser for gopher-lua +- `glua-lfs `_ : Partially implements the luafilesystem module for gopher-lua +- `gluaurl `_ : A url parser/builder module for gopher-lua +- `gluahttpscrape `_ : A simple HTML scraper module for gopher-lua +- `gluaxmlpath `_ : An xmlpath module for gopher-lua +- `gmoonscript `_ : Moonscript Compiler for the Gopher Lua VM +- `loguago `_ : Zerolog wrapper for Gopher-Lua +- `gluacrypto `_ : A native Go implementation of crypto library for the GopherLua VM. +- `gluasql `_ : A native Go implementation of SQL client for the GopherLua VM. +- `purr `_ : A http mock testing tool. +- `vadv/gopher-lua-libs `_ : Some usefull libraries for GopherLua VM. +- `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 +---------------------------------------------------------------- +Donation +---------------------------------------------------------------- + +BTC: 1NEDSyUmo4SMTDP83JJQSWi1MvQUGGNMZB + +---------------------------------------------------------------- +License +---------------------------------------------------------------- +MIT + +---------------------------------------------------------------- +Author +---------------------------------------------------------------- +Yusuke Inuzuka diff --git a/vendor/github.com/yuin/gopher-lua/_state.go b/vendor/github.com/yuin/gopher-lua/_state.go new file mode 100644 index 00000000000..54af061536c --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/_state.go @@ -0,0 +1,2081 @@ +package lua + +import ( + "context" + "fmt" + "io" + "math" + "os" + "runtime" + "strings" + "sync" + "sync/atomic" + "time" + + "github.com/yuin/gopher-lua/parse" +) + +const MultRet = -1 +const RegistryIndex = -10000 +const EnvironIndex = -10001 +const GlobalsIndex = -10002 + +/* ApiError {{{ */ + +type ApiError struct { + Type ApiErrorType + Object LValue + StackTrace string + // Underlying error. This attribute is set only if the Type is ApiErrorFile or ApiErrorSyntax + Cause error +} + +func newApiError(code ApiErrorType, object LValue) *ApiError { + return &ApiError{code, object, "", nil} +} + +func newApiErrorS(code ApiErrorType, message string) *ApiError { + return newApiError(code, LString(message)) +} + +func newApiErrorE(code ApiErrorType, err error) *ApiError { + return &ApiError{code, LString(err.Error()), "", err} +} + +func (e *ApiError) Error() string { + if len(e.StackTrace) > 0 { + return fmt.Sprintf("%s\n%s", e.Object.String(), e.StackTrace) + } + return e.Object.String() +} + +type ApiErrorType int + +const ( + ApiErrorSyntax ApiErrorType = iota + ApiErrorFile + ApiErrorRun + ApiErrorError + ApiErrorPanic +) + +/* }}} */ + +/* ResumeState {{{ */ + +type ResumeState int + +const ( + ResumeOK ResumeState = iota + ResumeYield + ResumeError +) + +/* }}} */ + +/* P {{{ */ + +type P struct { + Fn LValue + NRet int + Protect bool + Handler *LFunction +} + +/* }}} */ + +/* Options {{{ */ + +// Options is a configuration that is used to create a new LState. +type Options struct { + // Call stack size. This defaults to `lua.CallStackSize`. + CallStackSize int + // Data stack size. This defaults to `lua.RegistrySize`. + RegistrySize int + // Allow the registry to grow from the registry size specified up to a value of RegistryMaxSize. A value of 0 + // indicates no growth is permitted. The registry will not shrink again after any growth. + RegistryMaxSize int + // If growth is enabled, step up by an additional `RegistryGrowStep` each time to avoid having to resize too often. + // This defaults to `lua.RegistryGrowStep` + RegistryGrowStep int + // Controls whether or not libraries are opened by default + SkipOpenLibs bool + // Tells whether a Go stacktrace should be included in a Lua stacktrace when panics occur. + IncludeGoStackTrace bool + // If `MinimizeStackMemory` is set, the call stack will be automatically grown or shrank up to a limit of + // `CallStackSize` in order to minimize memory usage. This does incur a slight performance penalty. + MinimizeStackMemory bool +} + +/* }}} */ + +/* Debug {{{ */ + +type Debug struct { + frame *callFrame + Name string + What string + Source string + CurrentLine int + NUpvalues int + LineDefined int + LastLineDefined int +} + +/* }}} */ + +/* callFrame {{{ */ + +type callFrame struct { + Idx int + Fn *LFunction + Parent *callFrame + Pc int + Base int + LocalBase int + ReturnBase int + NArgs int + NRet int + TailCall int +} + +type callFrameStack interface { + Push(v callFrame) + Pop() *callFrame + Last() *callFrame + + SetSp(sp int) + Sp() int + At(sp int) *callFrame + + IsFull() bool + IsEmpty() bool + + FreeAll() +} + +type fixedCallFrameStack struct { + array []callFrame + sp int +} + +func newFixedCallFrameStack(size int) callFrameStack { + return &fixedCallFrameStack{ + array: make([]callFrame, size), + sp: 0, + } +} + +func (cs *fixedCallFrameStack) IsEmpty() bool { return cs.sp == 0 } + +func (cs *fixedCallFrameStack) IsFull() bool { return cs.sp == len(cs.array) } + +func (cs *fixedCallFrameStack) Clear() { + cs.sp = 0 +} + +func (cs *fixedCallFrameStack) Push(v callFrame) { + cs.array[cs.sp] = v + cs.array[cs.sp].Idx = cs.sp + cs.sp++ +} + +func (cs *fixedCallFrameStack) Sp() int { + return cs.sp +} + +func (cs *fixedCallFrameStack) SetSp(sp int) { + cs.sp = sp +} + +func (cs *fixedCallFrameStack) Last() *callFrame { + if cs.sp == 0 { + return nil + } + return &cs.array[cs.sp-1] +} + +func (cs *fixedCallFrameStack) At(sp int) *callFrame { + return &cs.array[sp] +} + +func (cs *fixedCallFrameStack) Pop() *callFrame { + cs.sp-- + return &cs.array[cs.sp] +} + +func (cs *fixedCallFrameStack) FreeAll() { + // nothing to do for fixed callframestack +} + +// FramesPerSegment should be a power of 2 constant for performance reasons. It will allow the go compiler to change +// the divs and mods into bitshifts. Max is 256 due to current use of uint8 to count how many frames in a segment are +// used. +const FramesPerSegment = 8 + +type callFrameStackSegment struct { + array [FramesPerSegment]callFrame +} +type segIdx uint16 +type autoGrowingCallFrameStack struct { + segments []*callFrameStackSegment + segIdx segIdx + // segSp is the number of frames in the current segment which are used. Full 'sp' value is segIdx * FramesPerSegment + segSp. + // It points to the next stack slot to use, so 0 means to use the 0th element in the segment, and a value of + // FramesPerSegment indicates that the segment is full and cannot accommodate another frame. + segSp uint8 +} + +var segmentPool sync.Pool + +func newCallFrameStackSegment() *callFrameStackSegment { + seg := segmentPool.Get() + if seg == nil { + return &callFrameStackSegment{} + } + return seg.(*callFrameStackSegment) +} + +func freeCallFrameStackSegment(seg *callFrameStackSegment) { + segmentPool.Put(seg) +} + +// newCallFrameStack allocates a new stack for a lua state, which will auto grow up to a max size of at least maxSize. +// it will actually grow up to the next segment size multiple after maxSize, where the segment size is dictated by +// FramesPerSegment. +func newAutoGrowingCallFrameStack(maxSize int) callFrameStack { + cs := &autoGrowingCallFrameStack{ + segments: make([]*callFrameStackSegment, (maxSize+(FramesPerSegment-1))/FramesPerSegment), + segIdx: 0, + } + cs.segments[0] = newCallFrameStackSegment() + return cs +} + +func (cs *autoGrowingCallFrameStack) IsEmpty() bool { + return cs.segIdx == 0 && cs.segSp == 0 +} + +// IsFull returns true if the stack cannot receive any more stack pushes without overflowing +func (cs *autoGrowingCallFrameStack) IsFull() bool { + return int(cs.segIdx) == len(cs.segments) && cs.segSp >= FramesPerSegment +} + +func (cs *autoGrowingCallFrameStack) Clear() { + for i := segIdx(1); i <= cs.segIdx; i++ { + freeCallFrameStackSegment(cs.segments[i]) + cs.segments[i] = nil + } + cs.segIdx = 0 + cs.segSp = 0 +} + +func (cs *autoGrowingCallFrameStack) FreeAll() { + for i := segIdx(0); i <= cs.segIdx; i++ { + freeCallFrameStackSegment(cs.segments[i]) + cs.segments[i] = nil + } +} + +// Push pushes the passed callFrame onto the stack. it panics if the stack is full, caller should call IsFull() before +// invoking this to avoid this. +func (cs *autoGrowingCallFrameStack) Push(v callFrame) { + curSeg := cs.segments[cs.segIdx] + if cs.segSp >= FramesPerSegment { + // segment full, push new segment if allowed + if cs.segIdx < segIdx(len(cs.segments)-1) { + curSeg = newCallFrameStackSegment() + cs.segIdx++ + cs.segments[cs.segIdx] = curSeg + cs.segSp = 0 + } else { + panic("lua callstack overflow") + } + } + curSeg.array[cs.segSp] = v + curSeg.array[cs.segSp].Idx = int(cs.segSp) + FramesPerSegment*int(cs.segIdx) + cs.segSp++ +} + +// Sp retrieves the current stack depth, which is the number of frames currently pushed on the stack. +func (cs *autoGrowingCallFrameStack) Sp() int { + return int(cs.segSp) + int(cs.segIdx)*FramesPerSegment +} + +// SetSp can be used to rapidly unwind the stack, freeing all stack frames on the way. It should not be used to +// allocate new stack space, use Push() for that. +func (cs *autoGrowingCallFrameStack) SetSp(sp int) { + desiredSegIdx := segIdx(sp / FramesPerSegment) + desiredFramesInLastSeg := uint8(sp % FramesPerSegment) + for { + if cs.segIdx <= desiredSegIdx { + break + } + freeCallFrameStackSegment(cs.segments[cs.segIdx]) + cs.segments[cs.segIdx] = nil + cs.segIdx-- + } + cs.segSp = desiredFramesInLastSeg +} + +func (cs *autoGrowingCallFrameStack) Last() *callFrame { + curSeg := cs.segments[cs.segIdx] + segSp := cs.segSp + if segSp == 0 { + if cs.segIdx == 0 { + return nil + } + curSeg = cs.segments[cs.segIdx-1] + segSp = FramesPerSegment + } + return &curSeg.array[segSp-1] +} + +func (cs *autoGrowingCallFrameStack) At(sp int) *callFrame { + segIdx := segIdx(sp / FramesPerSegment) + frameIdx := uint8(sp % FramesPerSegment) + return &cs.segments[segIdx].array[frameIdx] +} + +// Pop pops off the most recent stack frame and returns it +func (cs *autoGrowingCallFrameStack) Pop() *callFrame { + curSeg := cs.segments[cs.segIdx] + if cs.segSp == 0 { + if cs.segIdx == 0 { + // stack empty + return nil + } + freeCallFrameStackSegment(curSeg) + cs.segments[cs.segIdx] = nil + cs.segIdx-- + cs.segSp = FramesPerSegment + curSeg = cs.segments[cs.segIdx] + } + cs.segSp-- + return &curSeg.array[cs.segSp] +} + +/* }}} */ + +/* registry {{{ */ + +type registryHandler interface { + registryOverflow() +} +type registry struct { + array []LValue + top int + growBy int + maxSize int + alloc *allocator + handler registryHandler +} + +func newRegistry(handler registryHandler, initialSize int, growBy int, maxSize int, alloc *allocator) *registry { + return ®istry{make([]LValue, initialSize), 0, growBy, maxSize, alloc, handler} +} + +func (rg *registry) checkSize(requiredSize int) { // +inline-start + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } +} // +inline-end + +func (rg *registry) resize(requiredSize int) { // +inline-start + newSize := requiredSize + rg.growBy // give some padding + if newSize > rg.maxSize { + newSize = rg.maxSize + } + if newSize < requiredSize { + rg.handler.registryOverflow() + return + } + rg.forceResize(newSize) +} // +inline-end + +func (rg *registry) forceResize(newSize int) { + newSlice := make([]LValue, newSize) + 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++ { + 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] + for i := range nilRange { + nilRange[i] = nil + } + } + //for i := rg.top; i < oldtop; i++ { + // rg.array[i] = LNil + //} +} + +func (rg *registry) Top() int { + return rg.top +} + +func (rg *registry) Push(v LValue) { + newSize := rg.top + 1 + // +inline-call rg.checkSize newSize + rg.array[rg.top] = v + rg.top++ +} + +func (rg *registry) Pop() LValue { + v := rg.array[rg.top-1] + rg.array[rg.top-1] = LNil + rg.top-- + return v +} + +func (rg *registry) Get(reg int) LValue { + return rg.array[reg] +} + +// CopyRange will move a section of values from index `start` to index `regv` +// It will move `n` values. +// `limit` specifies the maximum end range that can be copied from. If it's set to -1, then it defaults to stopping at +// the top of the registry (values beyond the top are not initialized, so if specifying an alternative `limit` you should +// pass a value <= rg.top. +// If start+n is beyond the limit, then nil values will be copied to the destination slots. +// After the copy, the registry is truncated to be at the end of the copied range, ie the original of the copied values +// are nilled out. (So top will be regv+n) +// CopyRange should ideally be renamed to MoveRange. +func (rg *registry) CopyRange(regv, start, limit, n int) { // +inline-start + newSize := regv + n + // +inline-call rg.checkSize newSize + if limit == -1 || limit > rg.top { + limit = rg.top + } + for i := 0; i < n; i++ { + srcIdx := start + i + if srcIdx >= limit || srcIdx < 0 { + rg.array[regv+i] = LNil + } else { + rg.array[regv+i] = rg.array[srcIdx] + } + } + + // 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 + oldtop := rg.top + rg.top = regv + n + if rg.top < oldtop { + nilRange := rg.array[rg.top:oldtop] + for i := range nilRange { + nilRange[i] = nil + } + } +} // +inline-end + +// FillNil fills the registry with nil values from regm to regm+n and then sets the registry top to regm+n +func (rg *registry) FillNil(regm, n int) { // +inline-start + newSize := regm + n + // +inline-call rg.checkSize newSize + for i := 0; i < n; i++ { + rg.array[regm+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 + oldtop := rg.top + rg.top = regm + n + if rg.top < oldtop { + nilRange := rg.array[rg.top:oldtop] + for i := range nilRange { + nilRange[i] = nil + } + } +} // +inline-end + +func (rg *registry) Insert(value LValue, reg int) { + top := rg.Top() + if reg >= top { + 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)) + } + rg.Set(reg, value) +} + +func (rg *registry) Set(reg int, val LValue) { + newSize := reg + 1 + // +inline-call rg.checkSize newSize + rg.array[reg] = val + if reg >= rg.top { + rg.top = reg + 1 + } +} + +func (rg *registry) SetNumber(reg int, val LNumber) { + newSize := reg + 1 + // +inline-call rg.checkSize newSize + rg.array[reg] = rg.alloc.LNumber2I(val) + if reg >= rg.top { + rg.top = reg + 1 + } +} + +func (rg *registry) IsFull() bool { + return rg.top >= cap(rg.array) +} + +/* }}} */ + +/* Global {{{ */ + +func newGlobal() *Global { + return &Global{ + MainThread: nil, + Registry: newLTable(0, 32), + Global: newLTable(0, 64), + builtinMts: make(map[int]LValue), + tempFiles: make([]*os.File, 0, 10), + } +} + +/* }}} */ + +/* package local methods {{{ */ + +func panicWithTraceback(L *LState) { + err := newApiError(ApiErrorRun, L.Get(-1)) + err.StackTrace = L.stackTrace(0) + panic(err) +} + +func panicWithoutTraceback(L *LState) { + err := newApiError(ApiErrorRun, L.Get(-1)) + panic(err) +} + +func newLState(options Options) *LState { + al := newAllocator(32) + ls := &LState{ + G: newGlobal(), + Parent: nil, + Panic: panicWithTraceback, + Dead: false, + Options: options, + + stop: 0, + alloc: al, + currentFrame: nil, + wrapped: false, + uvcache: nil, + hasErrorFunc: false, + mainLoop: mainLoop, + ctx: nil, + } + if options.MinimizeStackMemory { + ls.stack = newAutoGrowingCallFrameStack(options.CallStackSize) + } else { + ls.stack = newFixedCallFrameStack(options.CallStackSize) + } + ls.reg = newRegistry(ls, options.RegistrySize, options.RegistryGrowStep, options.RegistryMaxSize, al) + ls.Env = ls.G.Global + return ls +} + +func (ls *LState) printReg() { + println("-------------------------") + println("thread:", ls) + println("top:", ls.reg.Top()) + if ls.currentFrame != nil { + println("function base:", ls.currentFrame.Base) + println("return base:", ls.currentFrame.ReturnBase) + } else { + println("(vm not started)") + } + println("local base:", ls.currentLocalBase()) + for i := 0; i < ls.reg.Top(); i++ { + println(i, ls.reg.Get(i).String()) + } + println("-------------------------") +} + +func (ls *LState) printCallStack() { + println("-------------------------") + for i := 0; i < ls.stack.Sp(); i++ { + print(i) + print(" ") + frame := ls.stack.At(i) + if frame == nil { + break + } + if frame.Fn.IsG { + println("IsG:", true, "Frame:", frame, "Fn:", frame.Fn) + } else { + println("IsG:", false, "Frame:", frame, "Fn:", frame.Fn, "pc:", frame.Pc) + } + } + println("-------------------------") +} + +func (ls *LState) closeAllUpvalues() { // +inline-start + for cf := ls.currentFrame; cf != nil; cf = cf.Parent { + if !cf.Fn.IsG { + ls.closeUpvalues(cf.LocalBase) + } + } +} // +inline-end + +func (ls *LState) raiseError(level int, format string, args ...interface{}) { + if !ls.hasErrorFunc { + ls.closeAllUpvalues() + } + message := format + if len(args) > 0 { + message = fmt.Sprintf(format, args...) + } + if level > 0 { + message = fmt.Sprintf("%v %v", ls.where(level-1, true), message) + } + if ls.reg.IsFull() { + // if the registry is full then it won't be possible to push a value, in this case, force a larger size + ls.reg.forceResize(ls.reg.Top() + 1) + } + ls.reg.Push(LString(message)) + ls.Panic(ls) +} + +func (ls *LState) findLocal(frame *callFrame, no int) string { + fn := frame.Fn + if !fn.IsG { + if name, ok := fn.LocalName(no, frame.Pc-1); ok { + return name + } + } + var top int + if ls.currentFrame == frame { + top = ls.reg.Top() + } else if frame.Idx+1 < ls.stack.Sp() { + top = ls.stack.At(frame.Idx + 1).Base + } else { + return "" + } + if top-frame.LocalBase >= no { + return "(*temporary)" + } + return "" +} + +func (ls *LState) where(level int, skipg bool) string { + dbg, ok := ls.GetStack(level) + if !ok { + return "" + } + cf := dbg.frame + proto := cf.Fn.Proto + sourcename := "[G]" + if proto != nil { + sourcename = proto.SourceName + } else if skipg { + return ls.where(level+1, skipg) + } + line := "" + if proto != nil { + line = fmt.Sprintf("%v:", proto.DbgSourcePositions[cf.Pc-1]) + } + return fmt.Sprintf("%v:%v", sourcename, line) +} + +func (ls *LState) stackTrace(level int) string { + buf := []string{} + header := "stack traceback:" + if ls.currentFrame != nil { + i := 0 + for dbg, ok := ls.GetStack(i); ok; dbg, ok = ls.GetStack(i) { + cf := dbg.frame + buf = append(buf, fmt.Sprintf("\t%v in %v", ls.Where(i), ls.formattedFrameFuncName(cf))) + if !cf.Fn.IsG && cf.TailCall > 0 { + for tc := cf.TailCall; tc > 0; tc-- { + buf = append(buf, "\t(tailcall): ?") + i++ + } + } + i++ + } + } + buf = append(buf, fmt.Sprintf("\t%v: %v", "[G]", "?")) + buf = buf[intMax(0, intMin(level, len(buf))):len(buf)] + if len(buf) > 20 { + newbuf := make([]string, 0, 20) + newbuf = append(newbuf, buf[0:7]...) + newbuf = append(newbuf, "\t...") + newbuf = append(newbuf, buf[len(buf)-7:len(buf)]...) + buf = newbuf + } + return fmt.Sprintf("%s\n%s", header, strings.Join(buf, "\n")) +} + +func (ls *LState) formattedFrameFuncName(fr *callFrame) string { + name, ischunk := ls.frameFuncName(fr) + if ischunk { + return name + } + if name[0] != '(' && name[0] != '<' { + return fmt.Sprintf("function '%s'", name) + } + return fmt.Sprintf("function %s", name) +} + +func (ls *LState) rawFrameFuncName(fr *callFrame) string { + name, _ := ls.frameFuncName(fr) + return name +} + +func (ls *LState) frameFuncName(fr *callFrame) (string, bool) { + frame := fr.Parent + if frame == nil { + if ls.Parent == nil { + return "main chunk", true + } else { + return "corountine", true + } + } + if !frame.Fn.IsG { + pc := frame.Pc - 1 + for _, call := range frame.Fn.Proto.DbgCalls { + if call.Pc == pc { + name := call.Name + if (name == "?" || fr.TailCall > 0) && !fr.Fn.IsG { + name = fmt.Sprintf("<%v:%v>", fr.Fn.Proto.SourceName, fr.Fn.Proto.LineDefined) + } + return name, false + } + } + } + if !fr.Fn.IsG { + return fmt.Sprintf("<%v:%v>", fr.Fn.Proto.SourceName, fr.Fn.Proto.LineDefined), false + } + return "(anonymous)", false +} + +func (ls *LState) isStarted() bool { + return ls.currentFrame != nil +} + +func (ls *LState) kill() { + ls.Dead = true +} + +func (ls *LState) indexToReg(idx int) int { + base := ls.currentLocalBase() + if idx > 0 { + return base + idx - 1 + } else if idx == 0 { + return -1 + } else { + tidx := ls.reg.Top() + idx + if tidx < base { + return -1 + } + return tidx + } +} + +func (ls *LState) currentLocalBase() int { + base := 0 + if ls.currentFrame != nil { + base = ls.currentFrame.LocalBase + } + return base +} + +func (ls *LState) currentEnv() *LTable { + return ls.Env + /* + if ls.currentFrame == nil { + return ls.Env + } + return ls.currentFrame.Fn.Env + */ +} + +func (ls *LState) rkValue(idx int) LValue { + /* + if OpIsK(idx) { + return ls.currentFrame.Fn.Proto.Constants[opIndexK(idx)] + } + return ls.reg.Get(ls.currentFrame.LocalBase + idx) + */ + if (idx & opBitRk) != 0 { + return ls.currentFrame.Fn.Proto.Constants[idx & ^opBitRk] + } + return ls.reg.array[ls.currentFrame.LocalBase+idx] +} + +func (ls *LState) rkString(idx int) string { + if (idx & opBitRk) != 0 { + return ls.currentFrame.Fn.Proto.stringConstants[idx & ^opBitRk] + } + return string(ls.reg.array[ls.currentFrame.LocalBase+idx].(LString)) +} + +func (ls *LState) closeUpvalues(idx int) { // +inline-start + if ls.uvcache != nil { + var prev *Upvalue + for uv := ls.uvcache; uv != nil; uv = uv.next { + if uv.index >= idx { + if prev != nil { + prev.next = nil + } else { + ls.uvcache = nil + } + uv.Close() + } + prev = uv + } + } +} // +inline-end + +func (ls *LState) findUpvalue(idx int) *Upvalue { + var prev *Upvalue + var next *Upvalue + if ls.uvcache != nil { + for uv := ls.uvcache; uv != nil; uv = uv.next { + if uv.index == idx { + return uv + } + if uv.index > idx { + next = uv + break + } + prev = uv + } + } + uv := &Upvalue{reg: ls.reg, index: idx, closed: false} + if prev != nil { + prev.next = uv + } else { + ls.uvcache = uv + } + if next != nil { + uv.next = next + } + return uv +} + +func (ls *LState) metatable(lvalue LValue, rawget bool) LValue { + var metatable LValue = LNil + switch obj := lvalue.(type) { + case *LTable: + metatable = obj.Metatable + case *LUserData: + metatable = obj.Metatable + default: + if table, ok := ls.G.builtinMts[int(obj.Type())]; ok { + metatable = table + } + } + + if !rawget && metatable != LNil { + oldmt := metatable + if tb, ok := metatable.(*LTable); ok { + metatable = tb.RawGetString("__metatable") + if metatable == LNil { + metatable = oldmt + } + } + } + + return metatable +} + +func (ls *LState) metaOp1(lvalue LValue, event string) LValue { + if mt := ls.metatable(lvalue, true); mt != LNil { + if tb, ok := mt.(*LTable); ok { + return tb.RawGetString(event) + } + } + return LNil +} + +func (ls *LState) metaOp2(value1, value2 LValue, event string) LValue { + if mt := ls.metatable(value1, true); mt != LNil { + if tb, ok := mt.(*LTable); ok { + if ret := tb.RawGetString(event); ret != LNil { + return ret + } + } + } + if mt := ls.metatable(value2, true); mt != LNil { + if tb, ok := mt.(*LTable); ok { + return tb.RawGetString(event) + } + } + return LNil +} + +func (ls *LState) metaCall(lvalue LValue) (*LFunction, bool) { + if fn, ok := lvalue.(*LFunction); ok { + return fn, false + } + if fn, ok := ls.metaOp1(lvalue, "__call").(*LFunction); ok { + return fn, true + } + return nil, false +} + +func (ls *LState) initCallFrame(cf *callFrame) { // +inline-start + if cf.Fn.IsG { + ls.reg.SetTop(cf.LocalBase + cf.NArgs) + } else { + proto := cf.Fn.Proto + nargs := cf.NArgs + np := int(proto.NumParameters) + newSize := cf.LocalBase + np + // +inline-call ls.reg.checkSize newSize + for i := nargs; i < np; i++ { + ls.reg.array[cf.LocalBase+i] = LNil + nargs = np + } + + if (proto.IsVarArg & VarArgIsVarArg) == 0 { + if nargs < int(proto.NumUsedRegisters) { + nargs = int(proto.NumUsedRegisters) + } + newSize = cf.LocalBase + nargs + // +inline-call ls.reg.checkSize newSize + for i := np; i < nargs; i++ { + ls.reg.array[cf.LocalBase+i] = LNil + } + ls.reg.top = cf.LocalBase + int(proto.NumUsedRegisters) + } else { + /* swap vararg positions: + closure + namedparam1 <- lbase + namedparam2 + vararg1 + vararg2 + + TO + + closure + nil + nil + vararg1 + vararg2 + namedparam1 <- lbase + namedparam2 + */ + nvarargs := nargs - np + if nvarargs < 0 { + nvarargs = 0 + } + + ls.reg.SetTop(cf.LocalBase + nargs + np) + for i := 0; i < np; i++ { + //ls.reg.Set(cf.LocalBase+nargs+i, ls.reg.Get(cf.LocalBase+i)) + ls.reg.array[cf.LocalBase+nargs+i] = ls.reg.array[cf.LocalBase+i] + //ls.reg.Set(cf.LocalBase+i, LNil) + ls.reg.array[cf.LocalBase+i] = LNil + } + + if CompatVarArg { + ls.reg.SetTop(cf.LocalBase + nargs + np + 1) + if (proto.IsVarArg & VarArgNeedsArg) != 0 { + argtb := newLTable(nvarargs, 0) + for i := 0; i < nvarargs; i++ { + argtb.RawSetInt(i+1, ls.reg.Get(cf.LocalBase+np+i)) + } + argtb.RawSetString("n", LNumber(nvarargs)) + //ls.reg.Set(cf.LocalBase+nargs+np, argtb) + ls.reg.array[cf.LocalBase+nargs+np] = argtb + } else { + ls.reg.array[cf.LocalBase+nargs+np] = LNil + } + } + cf.LocalBase += nargs + maxreg := cf.LocalBase + int(proto.NumUsedRegisters) + ls.reg.SetTop(maxreg) + } + } +} // +inline-end + +func (ls *LState) pushCallFrame(cf callFrame, fn LValue, meta bool) { // +inline-start + if meta { + cf.NArgs++ + ls.reg.Insert(fn, cf.LocalBase) + } + if cf.Fn == nil { + ls.RaiseError("attempt to call a non-function object") + } + if ls.stack.IsFull() { + ls.RaiseError("stack overflow") + } + ls.stack.Push(cf) + newcf := ls.stack.Last() + // +inline-call ls.initCallFrame newcf + ls.currentFrame = newcf +} // +inline-end + +func (ls *LState) callR(nargs, nret, rbase int) { + base := ls.reg.Top() - nargs - 1 + if rbase < 0 { + rbase = base + } + lv := ls.reg.Get(base) + fn, meta := ls.metaCall(lv) + ls.pushCallFrame(callFrame{ + Fn: fn, + Pc: 0, + Base: base, + LocalBase: base + 1, + ReturnBase: rbase, + NArgs: nargs, + NRet: nret, + Parent: ls.currentFrame, + TailCall: 0, + }, lv, meta) + if ls.G.MainThread == nil { + ls.G.MainThread = ls + ls.G.CurrentThread = ls + ls.mainLoop(ls, nil) + } else { + ls.mainLoop(ls, ls.currentFrame) + } + if nret != MultRet { + ls.reg.SetTop(rbase + nret) + } +} + +func (ls *LState) getField(obj LValue, key LValue) LValue { + curobj := obj + for i := 0; i < MaxTableGetLoop; i++ { + tb, istable := curobj.(*LTable) + if istable { + ret := tb.RawGet(key) + if ret != LNil { + return ret + } + } + metaindex := ls.metaOp1(curobj, "__index") + if metaindex == LNil { + if !istable { + ls.RaiseError("attempt to index a non-table object(%v) with key '%s'", curobj.Type().String(), key.String()) + } + return LNil + } + if metaindex.Type() == LTFunction { + ls.reg.Push(metaindex) + ls.reg.Push(curobj) + ls.reg.Push(key) + ls.Call(2, 1) + return ls.reg.Pop() + } else { + curobj = metaindex + } + } + ls.RaiseError("too many recursions in gettable") + return nil +} + +func (ls *LState) getFieldString(obj LValue, key string) LValue { + curobj := obj + for i := 0; i < MaxTableGetLoop; i++ { + tb, istable := curobj.(*LTable) + if istable { + ret := tb.RawGetString(key) + if ret != LNil { + return ret + } + } + metaindex := ls.metaOp1(curobj, "__index") + if metaindex == LNil { + if !istable { + ls.RaiseError("attempt to index a non-table object(%v) with key '%s'", curobj.Type().String(), key) + } + return LNil + } + if metaindex.Type() == LTFunction { + ls.reg.Push(metaindex) + ls.reg.Push(curobj) + ls.reg.Push(LString(key)) + ls.Call(2, 1) + return ls.reg.Pop() + } else { + curobj = metaindex + } + } + ls.RaiseError("too many recursions in gettable") + return nil +} + +func (ls *LState) setField(obj LValue, key LValue, value LValue) { + curobj := obj + for i := 0; i < MaxTableGetLoop; i++ { + tb, istable := curobj.(*LTable) + if istable { + if tb.RawGet(key) != LNil { + ls.RawSet(tb, key, value) + return + } + } + metaindex := ls.metaOp1(curobj, "__newindex") + if metaindex == LNil { + if !istable { + ls.RaiseError("attempt to index a non-table object(%v) with key '%s'", curobj.Type().String(), key.String()) + } + ls.RawSet(tb, key, value) + return + } + if metaindex.Type() == LTFunction { + ls.reg.Push(metaindex) + ls.reg.Push(curobj) + ls.reg.Push(key) + ls.reg.Push(value) + ls.Call(3, 0) + return + } else { + curobj = metaindex + } + } + ls.RaiseError("too many recursions in settable") +} + +func (ls *LState) setFieldString(obj LValue, key string, value LValue) { + curobj := obj + for i := 0; i < MaxTableGetLoop; i++ { + tb, istable := curobj.(*LTable) + if istable { + if tb.RawGetString(key) != LNil { + tb.RawSetString(key, value) + return + } + } + metaindex := ls.metaOp1(curobj, "__newindex") + if metaindex == LNil { + if !istable { + ls.RaiseError("attempt to index a non-table object(%v) with key '%s'", curobj.Type().String(), key) + } + tb.RawSetString(key, value) + return + } + if metaindex.Type() == LTFunction { + ls.reg.Push(metaindex) + ls.reg.Push(curobj) + ls.reg.Push(LString(key)) + ls.reg.Push(value) + ls.Call(3, 0) + return + } else { + curobj = metaindex + } + } + ls.RaiseError("too many recursions in settable") +} + +/* }}} */ + +/* api methods {{{ */ + +func NewState(opts ...Options) *LState { + var ls *LState + if len(opts) == 0 { + ls = newLState(Options{ + CallStackSize: CallStackSize, + RegistrySize: RegistrySize, + }) + ls.OpenLibs() + } else { + if opts[0].CallStackSize < 1 { + opts[0].CallStackSize = CallStackSize + } + if opts[0].RegistrySize < 128 { + opts[0].RegistrySize = RegistrySize + } + if opts[0].RegistryMaxSize < opts[0].RegistrySize { + opts[0].RegistryMaxSize = 0 // disable growth if max size is smaller than initial size + } else { + // if growth enabled, grow step is set + if opts[0].RegistryGrowStep < 1 { + opts[0].RegistryGrowStep = RegistryGrowStep + } + } + ls = newLState(opts[0]) + if !opts[0].SkipOpenLibs { + ls.OpenLibs() + } + } + return ls +} + +func (ls *LState) IsClosed() bool { + return ls.stack == nil +} + +func (ls *LState) Close() { + atomic.AddInt32(&ls.stop, 1) + for _, file := range ls.G.tempFiles { + // ignore errors in these operations + file.Close() + os.Remove(file.Name()) + } + ls.stack.FreeAll() + ls.stack = nil +} + +/* registry operations {{{ */ + +func (ls *LState) GetTop() int { + return ls.reg.Top() - ls.currentLocalBase() +} + +func (ls *LState) SetTop(idx int) { + base := ls.currentLocalBase() + newtop := ls.indexToReg(idx) + 1 + if newtop < base { + ls.reg.SetTop(base) + } else { + ls.reg.SetTop(newtop) + } +} + +func (ls *LState) Replace(idx int, value LValue) { + base := ls.currentLocalBase() + if idx > 0 { + reg := base + idx - 1 + if reg < ls.reg.Top() { + ls.reg.Set(reg, value) + } + } else if idx == 0 { + } else if idx > RegistryIndex { + if tidx := ls.reg.Top() + idx; tidx >= base { + ls.reg.Set(tidx, value) + } + } else { + switch idx { + case RegistryIndex: + if tb, ok := value.(*LTable); ok { + ls.G.Registry = tb + } else { + ls.RaiseError("registry must be a table(%v)", value.Type().String()) + } + case EnvironIndex: + if ls.currentFrame == nil { + ls.RaiseError("no calling environment") + } + if tb, ok := value.(*LTable); ok { + ls.currentFrame.Fn.Env = tb + } else { + ls.RaiseError("environment must be a table(%v)", value.Type().String()) + } + case GlobalsIndex: + if tb, ok := value.(*LTable); ok { + ls.G.Global = tb + } else { + ls.RaiseError("_G must be a table(%v)", value.Type().String()) + } + default: + fn := ls.currentFrame.Fn + index := GlobalsIndex - idx - 1 + if index < len(fn.Upvalues) { + fn.Upvalues[index].SetValue(value) + } + } + } +} + +func (ls *LState) Get(idx int) LValue { + base := ls.currentLocalBase() + if idx > 0 { + reg := base + idx - 1 + if reg < ls.reg.Top() { + return ls.reg.Get(reg) + } + return LNil + } else if idx == 0 { + return LNil + } else if idx > RegistryIndex { + tidx := ls.reg.Top() + idx + if tidx < base { + return LNil + } + return ls.reg.Get(tidx) + } else { + switch idx { + case RegistryIndex: + return ls.G.Registry + case EnvironIndex: + if ls.currentFrame == nil { + return ls.Env + } + return ls.currentFrame.Fn.Env + case GlobalsIndex: + return ls.G.Global + default: + fn := ls.currentFrame.Fn + index := GlobalsIndex - idx - 1 + if index < len(fn.Upvalues) { + return fn.Upvalues[index].Value() + } + return LNil + } + } + return LNil +} + +func (ls *LState) Push(value LValue) { + ls.reg.Push(value) +} + +func (ls *LState) Pop(n int) { + for i := 0; i < n; i++ { + if ls.GetTop() == 0 { + ls.RaiseError("register underflow") + } + ls.reg.Pop() + } +} + +func (ls *LState) Insert(value LValue, index int) { + reg := ls.indexToReg(index) + top := ls.reg.Top() + if reg >= top { + ls.reg.Set(reg, value) + return + } + if reg <= ls.currentLocalBase() { + reg = ls.currentLocalBase() + } + top-- + for ; top >= reg; top-- { + ls.reg.Set(top+1, ls.reg.Get(top)) + } + ls.reg.Set(reg, value) +} + +func (ls *LState) Remove(index int) { + reg := ls.indexToReg(index) + top := ls.reg.Top() + switch { + case reg >= top: + return + case reg < ls.currentLocalBase(): + return + case reg == top-1: + ls.Pop(1) + return + } + for i := reg; i < top-1; i++ { + ls.reg.Set(i, ls.reg.Get(i+1)) + } + ls.reg.SetTop(top - 1) +} + +/* }}} */ + +/* object allocation {{{ */ + +func (ls *LState) NewTable() *LTable { + return newLTable(defaultArrayCap, defaultHashCap) +} + +func (ls *LState) CreateTable(acap, hcap int) *LTable { + return newLTable(acap, hcap) +} + +// NewThread returns a new LState that shares with the original state all global objects. +// If the original state has context.Context, the new state has a new child context of the original state and this function returns its cancel function. +func (ls *LState) NewThread() (*LState, context.CancelFunc) { + thread := newLState(ls.Options) + thread.G = ls.G + thread.Env = ls.Env + var f context.CancelFunc = nil + if ls.ctx != nil { + thread.mainLoop = mainLoopWithContext + thread.ctx, f = context.WithCancel(ls.ctx) + } + return thread, f +} + +func (ls *LState) NewFunctionFromProto(proto *FunctionProto) *LFunction { + return newLFunctionL(proto, ls.Env, int(proto.NumUpvalues)) +} + +func (ls *LState) NewUserData() *LUserData { + return &LUserData{ + Env: ls.currentEnv(), + Metatable: LNil, + } +} + +func (ls *LState) NewFunction(fn LGFunction) *LFunction { + return newLFunctionG(fn, ls.currentEnv(), 0) +} + +func (ls *LState) NewClosure(fn LGFunction, upvalues ...LValue) *LFunction { + cl := newLFunctionG(fn, ls.currentEnv(), len(upvalues)) + for i, lv := range upvalues { + cl.Upvalues[i] = &Upvalue{} + cl.Upvalues[i].Close() + cl.Upvalues[i].SetValue(lv) + } + return cl +} + +/* }}} */ + +/* toType {{{ */ + +func (ls *LState) ToBool(n int) bool { + return LVAsBool(ls.Get(n)) +} + +func (ls *LState) ToInt(n int) int { + if lv, ok := ls.Get(n).(LNumber); ok { + return int(lv) + } + if lv, ok := ls.Get(n).(LString); ok { + if num, err := parseNumber(string(lv)); err == nil { + return int(num) + } + } + return 0 +} + +func (ls *LState) ToInt64(n int) int64 { + if lv, ok := ls.Get(n).(LNumber); ok { + return int64(lv) + } + if lv, ok := ls.Get(n).(LString); ok { + if num, err := parseNumber(string(lv)); err == nil { + return int64(num) + } + } + return 0 +} + +func (ls *LState) ToNumber(n int) LNumber { + return LVAsNumber(ls.Get(n)) +} + +func (ls *LState) ToString(n int) string { + return LVAsString(ls.Get(n)) +} + +func (ls *LState) ToTable(n int) *LTable { + if lv, ok := ls.Get(n).(*LTable); ok { + return lv + } + return nil +} + +func (ls *LState) ToFunction(n int) *LFunction { + if lv, ok := ls.Get(n).(*LFunction); ok { + return lv + } + return nil +} + +func (ls *LState) ToUserData(n int) *LUserData { + if lv, ok := ls.Get(n).(*LUserData); ok { + return lv + } + return nil +} + +func (ls *LState) ToThread(n int) *LState { + if lv, ok := ls.Get(n).(*LState); ok { + return lv + } + return nil +} + +/* }}} */ + +/* error & debug operations {{{ */ + +func (ls *LState) registryOverflow() { + ls.RaiseError("registry overflow") +} + +// This function is equivalent to luaL_error( http://www.lua.org/manual/5.1/manual.html#luaL_error ). +func (ls *LState) RaiseError(format string, args ...interface{}) { + ls.raiseError(1, format, args...) +} + +// This function is equivalent to lua_error( http://www.lua.org/manual/5.1/manual.html#lua_error ). +func (ls *LState) Error(lv LValue, level int) { + if str, ok := lv.(LString); ok { + ls.raiseError(level, string(str)) + } else { + if !ls.hasErrorFunc { + ls.closeAllUpvalues() + } + ls.Push(lv) + ls.Panic(ls) + } +} + +func (ls *LState) GetInfo(what string, dbg *Debug, fn LValue) (LValue, error) { + if !strings.HasPrefix(what, ">") { + fn = dbg.frame.Fn + } else { + what = what[1:] + } + f, ok := fn.(*LFunction) + if !ok { + return LNil, newApiErrorS(ApiErrorRun, "can not get debug info(an object in not a function)") + } + + retfn := false + for _, c := range what { + switch c { + case 'f': + retfn = true + case 'S': + if dbg.frame != nil && dbg.frame.Parent == nil { + dbg.What = "main" + } else if f.IsG { + dbg.What = "G" + } else if dbg.frame != nil && dbg.frame.TailCall > 0 { + dbg.What = "tail" + } else { + dbg.What = "Lua" + } + if !f.IsG { + dbg.Source = f.Proto.SourceName + dbg.LineDefined = f.Proto.LineDefined + dbg.LastLineDefined = f.Proto.LastLineDefined + } + case 'l': + if !f.IsG && dbg.frame != nil { + if dbg.frame.Pc > 0 { + dbg.CurrentLine = f.Proto.DbgSourcePositions[dbg.frame.Pc-1] + } + } else { + dbg.CurrentLine = -1 + } + case 'u': + dbg.NUpvalues = len(f.Upvalues) + case 'n': + if dbg.frame != nil { + dbg.Name = ls.rawFrameFuncName(dbg.frame) + } + default: + return LNil, newApiErrorS(ApiErrorRun, "invalid what: "+string(c)) + } + } + + if retfn { + return f, nil + } + return LNil, nil + +} + +func (ls *LState) GetStack(level int) (*Debug, bool) { + frame := ls.currentFrame + for ; level > 0 && frame != nil; frame = frame.Parent { + level-- + if !frame.Fn.IsG { + level -= frame.TailCall + } + } + + if level == 0 && frame != nil { + return &Debug{frame: frame}, true + } else if level < 0 && ls.stack.Sp() > 0 { + return &Debug{frame: ls.stack.At(0)}, true + } + return &Debug{}, false +} + +func (ls *LState) GetLocal(dbg *Debug, no int) (string, LValue) { + frame := dbg.frame + if name := ls.findLocal(frame, no); len(name) > 0 { + return name, ls.reg.Get(frame.LocalBase + no - 1) + } + return "", LNil +} + +func (ls *LState) SetLocal(dbg *Debug, no int, lv LValue) string { + frame := dbg.frame + if name := ls.findLocal(frame, no); len(name) > 0 { + ls.reg.Set(frame.LocalBase+no-1, lv) + return name + } + return "" +} + +func (ls *LState) GetUpvalue(fn *LFunction, no int) (string, LValue) { + if fn.IsG { + return "", LNil + } + + no-- + if no >= 0 && no < len(fn.Upvalues) { + return fn.Proto.DbgUpvalues[no], fn.Upvalues[no].Value() + } + return "", LNil +} + +func (ls *LState) SetUpvalue(fn *LFunction, no int, lv LValue) string { + if fn.IsG { + return "" + } + + no-- + if no >= 0 && no < len(fn.Upvalues) { + fn.Upvalues[no].SetValue(lv) + return fn.Proto.DbgUpvalues[no] + } + return "" +} + +/* }}} */ + +/* env operations {{{ */ + +func (ls *LState) GetFEnv(obj LValue) LValue { + switch lv := obj.(type) { + case *LFunction: + return lv.Env + case *LUserData: + return lv.Env + case *LState: + return lv.Env + } + return LNil +} + +func (ls *LState) SetFEnv(obj LValue, env LValue) { + tb, ok := env.(*LTable) + if !ok { + ls.RaiseError("cannot use %v as an environment", env.Type().String()) + } + + switch lv := obj.(type) { + case *LFunction: + lv.Env = tb + case *LUserData: + lv.Env = tb + case *LState: + lv.Env = tb + } + /* do nothing */ +} + +/* }}} */ + +/* table operations {{{ */ + +func (ls *LState) RawGet(tb *LTable, key LValue) LValue { + return tb.RawGet(key) +} + +func (ls *LState) RawGetInt(tb *LTable, key int) LValue { + return tb.RawGetInt(key) +} + +func (ls *LState) GetField(obj LValue, skey string) LValue { + return ls.getFieldString(obj, skey) +} + +func (ls *LState) GetTable(obj LValue, key LValue) LValue { + return ls.getField(obj, key) +} + +func (ls *LState) RawSet(tb *LTable, key LValue, value LValue) { + if n, ok := key.(LNumber); ok && math.IsNaN(float64(n)) { + ls.RaiseError("table index is NaN") + } else if key == LNil { + ls.RaiseError("table index is nil") + } + tb.RawSet(key, value) +} + +func (ls *LState) RawSetInt(tb *LTable, key int, value LValue) { + tb.RawSetInt(key, value) +} + +func (ls *LState) SetField(obj LValue, key string, value LValue) { + ls.setFieldString(obj, key, value) +} + +func (ls *LState) SetTable(obj LValue, key LValue, value LValue) { + ls.setField(obj, key, value) +} + +func (ls *LState) ForEach(tb *LTable, cb func(LValue, LValue)) { + tb.ForEach(cb) +} + +func (ls *LState) GetGlobal(name string) LValue { + return ls.GetField(ls.Get(GlobalsIndex), name) +} + +func (ls *LState) SetGlobal(name string, value LValue) { + ls.SetField(ls.Get(GlobalsIndex), name, value) +} + +func (ls *LState) Next(tb *LTable, key LValue) (LValue, LValue) { + return tb.Next(key) +} + +/* }}} */ + +/* unary operations {{{ */ + +func (ls *LState) ObjLen(v1 LValue) int { + if v1.Type() == LTString { + return len(string(v1.(LString))) + } + op := ls.metaOp1(v1, "__len") + if op.Type() == LTFunction { + ls.Push(op) + ls.Push(v1) + ls.Call(1, 1) + ret := ls.reg.Pop() + if ret.Type() == LTNumber { + return int(ret.(LNumber)) + } + } else if v1.Type() == LTTable { + return v1.(*LTable).Len() + } + return 0 +} + +/* }}} */ + +/* binary operations {{{ */ + +func (ls *LState) Concat(values ...LValue) string { + top := ls.reg.Top() + for _, value := range values { + ls.reg.Push(value) + } + ret := stringConcat(ls, len(values), ls.reg.Top()-1) + ls.reg.SetTop(top) + return LVAsString(ret) +} + +func (ls *LState) LessThan(lhs, rhs LValue) bool { + return lessThan(ls, lhs, rhs) +} + +func (ls *LState) Equal(lhs, rhs LValue) bool { + return equals(ls, lhs, rhs, false) +} + +func (ls *LState) RawEqual(lhs, rhs LValue) bool { + return equals(ls, lhs, rhs, true) +} + +/* }}} */ + +/* register operations {{{ */ + +func (ls *LState) Register(name string, fn LGFunction) { + ls.SetGlobal(name, ls.NewFunction(fn)) +} + +/* }}} */ + +/* load and function call operations {{{ */ + +func (ls *LState) Load(reader io.Reader, name string) (*LFunction, error) { + chunk, err := parse.Parse(reader, name) + if err != nil { + return nil, newApiErrorE(ApiErrorSyntax, err) + } + proto, err := Compile(chunk, name) + if err != nil { + return nil, newApiErrorE(ApiErrorSyntax, err) + } + return newLFunctionL(proto, ls.currentEnv(), 0), nil +} + +func (ls *LState) Call(nargs, nret int) { + ls.callR(nargs, nret, -1) +} + +func (ls *LState) PCall(nargs, nret int, errfunc *LFunction) (err error) { + err = nil + sp := ls.stack.Sp() + base := ls.reg.Top() - nargs - 1 + oldpanic := ls.Panic + ls.Panic = panicWithoutTraceback + if errfunc != nil { + ls.hasErrorFunc = true + } + defer func() { + ls.Panic = oldpanic + ls.hasErrorFunc = false + rcv := recover() + if rcv != nil { + if _, ok := rcv.(*ApiError); !ok { + err = newApiErrorS(ApiErrorPanic, fmt.Sprint(rcv)) + if ls.Options.IncludeGoStackTrace { + buf := make([]byte, 4096) + runtime.Stack(buf, false) + err.(*ApiError).StackTrace = strings.Trim(string(buf), "\000") + "\n" + ls.stackTrace(0) + } + } else { + err = rcv.(*ApiError) + } + if errfunc != nil { + ls.Push(errfunc) + ls.Push(err.(*ApiError).Object) + ls.Panic = panicWithoutTraceback + defer func() { + ls.Panic = oldpanic + rcv := recover() + if rcv != nil { + if _, ok := rcv.(*ApiError); !ok { + err = newApiErrorS(ApiErrorPanic, fmt.Sprint(rcv)) + if ls.Options.IncludeGoStackTrace { + buf := make([]byte, 4096) + runtime.Stack(buf, false) + err.(*ApiError).StackTrace = strings.Trim(string(buf), "\000") + ls.stackTrace(0) + } + } else { + err = rcv.(*ApiError) + err.(*ApiError).StackTrace = ls.stackTrace(0) + } + } + }() + ls.Call(1, 1) + err = newApiError(ApiErrorError, ls.Get(-1)) + } else if len(err.(*ApiError).StackTrace) == 0 { + err.(*ApiError).StackTrace = ls.stackTrace(0) + } + ls.stack.SetSp(sp) + ls.currentFrame = ls.stack.Last() + ls.reg.SetTop(base) + } + ls.stack.SetSp(sp) + if sp == 0 { + ls.currentFrame = nil + } + }() + + ls.Call(nargs, nret) + + return +} + +func (ls *LState) GPCall(fn LGFunction, data LValue) error { + ls.Push(newLFunctionG(fn, ls.currentEnv(), 0)) + ls.Push(data) + return ls.PCall(1, MultRet, nil) +} + +func (ls *LState) CallByParam(cp P, args ...LValue) error { + ls.Push(cp.Fn) + for _, arg := range args { + ls.Push(arg) + } + + if cp.Protect { + return ls.PCall(len(args), cp.NRet, cp.Handler) + } + ls.Call(len(args), cp.NRet) + return nil +} + +/* }}} */ + +/* metatable operations {{{ */ + +func (ls *LState) GetMetatable(obj LValue) LValue { + return ls.metatable(obj, false) +} + +func (ls *LState) SetMetatable(obj LValue, mt LValue) { + switch mt.(type) { + case *LNilType, *LTable: + default: + ls.RaiseError("metatable must be a table or nil, but got %v", mt.Type().String()) + } + + switch v := obj.(type) { + case *LTable: + v.Metatable = mt + case *LUserData: + v.Metatable = mt + default: + ls.G.builtinMts[int(obj.Type())] = mt + } +} + +/* }}} */ + +/* coroutine operations {{{ */ + +func (ls *LState) Status(th *LState) string { + status := "suspended" + if th.Dead { + status = "dead" + } else if ls.G.CurrentThread == th { + status = "running" + } else if ls.Parent == th { + status = "normal" + } + return status +} + +func (ls *LState) Resume(th *LState, fn *LFunction, args ...LValue) (ResumeState, error, []LValue) { + isstarted := th.isStarted() + if !isstarted { + base := 0 + th.stack.Push(callFrame{ + Fn: fn, + Pc: 0, + Base: base, + LocalBase: base + 1, + ReturnBase: base, + NArgs: 0, + NRet: MultRet, + Parent: nil, + TailCall: 0, + }) + } + + if ls.G.CurrentThread == th { + return ResumeError, newApiErrorS(ApiErrorRun, "can not resume a running thread"), nil + } + if th.Dead { + return ResumeError, newApiErrorS(ApiErrorRun, "can not resume a dead thread"), nil + } + th.Parent = ls + ls.G.CurrentThread = th + if !isstarted { + cf := th.stack.Last() + th.currentFrame = cf + th.SetTop(0) + for _, arg := range args { + th.Push(arg) + } + cf.NArgs = len(args) + th.initCallFrame(cf) + th.Panic = panicWithoutTraceback + } else { + for _, arg := range args { + th.Push(arg) + } + } + top := ls.GetTop() + threadRun(th) + haserror := LVIsFalse(ls.Get(top + 1)) + ret := make([]LValue, 0, ls.GetTop()) + for idx := top + 2; idx <= ls.GetTop(); idx++ { + ret = append(ret, ls.Get(idx)) + } + if len(ret) == 0 { + ret = append(ret, LNil) + } + ls.SetTop(top) + + if haserror { + return ResumeError, newApiError(ApiErrorRun, ret[0]), nil + } else if th.stack.IsEmpty() { + return ResumeOK, nil, ret + } + return ResumeYield, nil, ret +} + +func (ls *LState) Yield(values ...LValue) int { + ls.SetTop(0) + for _, lv := range values { + ls.Push(lv) + } + return -1 +} + +func (ls *LState) XMoveTo(other *LState, n int) { + if ls == other { + return + } + top := ls.GetTop() + n = intMin(n, top) + for i := n; i > 0; i-- { + other.Push(ls.Get(top - i + 1)) + } + ls.SetTop(top - n) +} + +/* }}} */ + +/* GopherLua original APIs {{{ */ + +// Set maximum memory size. This function can only be called from the main thread. +func (ls *LState) SetMx(mx int) { + if ls.Parent != nil { + ls.RaiseError("sub threads are not allowed to set a memory limit") + } + go func() { + limit := uint64(mx * 1024 * 1024) //MB + var s runtime.MemStats + for atomic.LoadInt32(&ls.stop) == 0 { + runtime.ReadMemStats(&s) + if s.Alloc >= limit { + fmt.Println("out of memory") + os.Exit(3) + } + time.Sleep(100 * time.Millisecond) + } + }() +} + +// SetContext set a context ctx to this LState. The provided ctx must be non-nil. +func (ls *LState) SetContext(ctx context.Context) { + ls.mainLoop = mainLoopWithContext + ls.ctx = ctx +} + +// Context returns the LState's context. To change the context, use WithContext. +func (ls *LState) Context() context.Context { + return ls.ctx +} + +// RemoveContext removes the context associated with this LState and returns this context. +func (ls *LState) RemoveContext() context.Context { + oldctx := ls.ctx + ls.mainLoop = mainLoop + ls.ctx = nil + return oldctx +} + +// Converts the Lua value at the given acceptable index to the chan LValue. +func (ls *LState) ToChannel(n int) chan LValue { + if lv, ok := ls.Get(n).(LChannel); ok { + return (chan LValue)(lv) + } + return nil +} + +// RemoveCallerFrame removes the stack frame above the current stack frame. This is useful in tail calls. It returns +// the new current frame. +func (ls *LState) RemoveCallerFrame() *callFrame { + cs := ls.stack + sp := cs.Sp() + parentFrame := cs.At(sp - 2) + currentFrame := cs.At(sp - 1) + parentsParentFrame := parentFrame.Parent + *parentFrame = *currentFrame + parentFrame.Parent = parentsParentFrame + parentFrame.Idx = sp - 2 + cs.Pop() + return parentFrame +} + +/* }}} */ + +/* }}} */ + +// diff --git a/vendor/github.com/yuin/gopher-lua/_vm.go b/vendor/github.com/yuin/gopher-lua/_vm.go new file mode 100644 index 00000000000..874ed9aa4a7 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/_vm.go @@ -0,0 +1,1033 @@ +package lua + +import ( + "fmt" + "math" + "strings" +) + +func mainLoop(L *LState, baseframe *callFrame) { + var inst uint32 + var cf *callFrame + + if L.stack.IsEmpty() { + return + } + + L.currentFrame = L.stack.Last() + if L.currentFrame.Fn.IsG { + callGFunction(L, false) + return + } + + for { + cf = L.currentFrame + inst = cf.Fn.Proto.Code[cf.Pc] + cf.Pc++ + if jumpTable[int(inst>>26)](L, inst, baseframe) == 1 { + return + } + } +} + +func mainLoopWithContext(L *LState, baseframe *callFrame) { + var inst uint32 + var cf *callFrame + + if L.stack.IsEmpty() { + return + } + + L.currentFrame = L.stack.Last() + if L.currentFrame.Fn.IsG { + callGFunction(L, false) + return + } + + for { + cf = L.currentFrame + inst = cf.Fn.Proto.Code[cf.Pc] + cf.Pc++ + select { + case <-L.ctx.Done(): + L.RaiseError(L.ctx.Err().Error()) + return + default: + if jumpTable[int(inst>>26)](L, inst, baseframe) == 1 { + return + } + } + } +} + +// regv is the first target register to copy the return values to. +// It can be reg.top, indicating that the copied values are going into new registers, or it can be below reg.top +// Indicating that the values should be within the existing registers. +// b is the available number of return values + 1. +// n is the desired number of return values. +// If n more than the available return values then the extra values are set to nil. +// When this function returns the top of the registry will be set to regv+n. +func copyReturnValues(L *LState, regv, start, n, b int) { // +inline-start + if b == 1 { + // +inline-call L.reg.FillNil regv n + } else { + // +inline-call L.reg.CopyRange regv start -1 n + if b > 1 && n > (b-1) { + // +inline-call L.reg.FillNil regv+b-1 n-(b-1) + } + } +} // +inline-end + +func switchToParentThread(L *LState, nargs int, haserror bool, kill bool) { + parent := L.Parent + if parent == nil { + L.RaiseError("can not yield from outside of a coroutine") + } + L.G.CurrentThread = parent + L.Parent = nil + if !L.wrapped { + if haserror { + parent.Push(LFalse) + } else { + parent.Push(LTrue) + } + } + L.XMoveTo(parent, nargs) + L.stack.Pop() + offset := L.currentFrame.LocalBase - L.currentFrame.ReturnBase + L.currentFrame = L.stack.Last() + L.reg.SetTop(L.reg.Top() - offset) // remove 'yield' function(including tailcalled functions) + if kill { + L.kill() + } +} + +func callGFunction(L *LState, tailcall bool) bool { + frame := L.currentFrame + gfnret := frame.Fn.GFunction(L) + if tailcall { + L.currentFrame = L.RemoveCallerFrame() + } + + if gfnret < 0 { + switchToParentThread(L, L.GetTop(), false, false) + return true + } + + wantret := frame.NRet + if wantret == MultRet { + wantret = gfnret + } + + if tailcall && L.Parent != nil && L.stack.Sp() == 1 { + switchToParentThread(L, wantret, false, true) + return true + } + + // +inline-call L.reg.CopyRange frame.ReturnBase L.reg.Top()-gfnret -1 wantret + L.stack.Pop() + L.currentFrame = L.stack.Last() + return false +} + +func threadRun(L *LState) { + if L.stack.IsEmpty() { + return + } + + defer func() { + if rcv := recover(); rcv != nil { + var lv LValue + if v, ok := rcv.(*ApiError); ok { + lv = v.Object + } else { + lv = LString(fmt.Sprint(rcv)) + } + if parent := L.Parent; parent != nil { + if L.wrapped { + L.Push(lv) + parent.Panic(L) + } else { + L.SetTop(0) + L.Push(lv) + switchToParentThread(L, 1, true, true) + } + } else { + panic(rcv) + } + } + }() + L.mainLoop(L, nil) +} + +type instFunc func(*LState, uint32, *callFrame) int + +var jumpTable [opCodeMax + 1]instFunc + +func init() { + jumpTable = [opCodeMax + 1]instFunc{ + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_MOVE + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + reg.Set(RA, reg.Get(lbase+B)) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_MOVEN + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + 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)) + code := cf.Fn.Proto.Code + pc := cf.Pc + for i := 0; i < C; i++ { + inst = code[pc] + pc++ + A = int(inst>>18) & 0xff //GETA + B = int(inst & 0x1ff) //GETB + reg.Set(lbase+A, reg.Get(lbase+B)) + } + cf.Pc = pc + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_LOADK + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + Bx := int(inst & 0x3ffff) //GETBX + reg.Set(RA, cf.Fn.Proto.Constants[Bx]) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_LOADBOOL + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + if B != 0 { + reg.Set(RA, LTrue) + } else { + reg.Set(RA, LFalse) + } + if C != 0 { + cf.Pc++ + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_LOADNIL + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + for i := RA; i <= lbase+B; i++ { + reg.Set(i, LNil) + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_GETUPVAL + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + reg.Set(RA, cf.Fn.Upvalues[B].Value()) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_GETGLOBAL + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + 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])) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_GETTABLE + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + 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))) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_GETTABLEKS + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + 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))) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_SETGLOBAL + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + Bx := int(inst & 0x3ffff) //GETBX + //L.setField(cf.Fn.Env, cf.Fn.Proto.Constants[Bx], reg.Get(RA)) + L.setFieldString(cf.Fn.Env, cf.Fn.Proto.stringConstants[Bx], reg.Get(RA)) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_SETUPVAL + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + cf.Fn.Upvalues[B].SetValue(reg.Get(RA)) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_SETTABLE + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + L.setField(reg.Get(RA), L.rkValue(B), L.rkValue(C)) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_SETTABLEKS + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + L.setFieldString(reg.Get(RA), L.rkString(B), L.rkValue(C)) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_NEWTABLE + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + reg.Set(RA, newLTable(B, C)) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_SELF + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + 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) + return 0 + }, + opArith, // OP_ADD + opArith, // OP_SUB + opArith, // OP_MUL + opArith, // OP_DIV + opArith, // OP_MOD + opArith, // OP_POW + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_UNM + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + unaryv := L.rkValue(B) + if nm, ok := unaryv.(LNumber); ok { + reg.SetNumber(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()) + } else if str, ok1 := unaryv.(LString); ok1 { + if num, err := parseNumber(string(str)); err == nil { + reg.Set(RA, -num) + } else { + L.RaiseError("__unm undefined") + } + } else { + L.RaiseError("__unm undefined") + } + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_NOT + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + if LVIsFalse(reg.Get(lbase + B)) { + reg.Set(RA, LTrue) + } else { + reg.Set(RA, LFalse) + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_LEN + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + switch lv := L.rkValue(B).(type) { + case LString: + reg.SetNumber(RA, LNumber(len(lv))) + default: + op := L.metaOp1(lv, "__len") + if op.Type() == LTFunction { + reg.Push(op) + reg.Push(lv) + L.Call(1, 1) + ret := reg.Pop() + if ret.Type() == LTNumber { + reg.SetNumber(RA, ret.(LNumber)) + } else { + reg.SetNumber(RA, LNumber(0)) + } + } else if lv.Type() == LTTable { + reg.SetNumber(RA, LNumber(lv.(*LTable).Len())) + } else { + L.RaiseError("__len undefined") + } + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_CONCAT + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + RC := lbase + C + RB := lbase + B + reg.Set(RA, stringConcat(L, RC-RB+1, RC)) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_JMP + cf := L.currentFrame + Sbx := int(inst&0x3ffff) - opMaxArgSbx //GETSBX + cf.Pc += Sbx + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_EQ + cf := L.currentFrame + A := int(inst>>18) & 0xff //GETA + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + ret := equals(L, L.rkValue(B), L.rkValue(C), false) + v := 1 + if ret { + v = 0 + } + if v == A { + cf.Pc++ + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_LT + cf := L.currentFrame + A := int(inst>>18) & 0xff //GETA + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + ret := lessThan(L, L.rkValue(B), L.rkValue(C)) + v := 1 + if ret { + v = 0 + } + if v == A { + cf.Pc++ + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_LE + cf := L.currentFrame + A := int(inst>>18) & 0xff //GETA + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + lhs := L.rkValue(B) + rhs := L.rkValue(C) + ret := false + + if v1, ok1 := lhs.assertFloat64(); ok1 { + if v2, ok2 := rhs.assertFloat64(); ok2 { + ret = v1 <= v2 + } else { + L.RaiseError("attempt to compare %v with %v", lhs.Type().String(), rhs.Type().String()) + } + } else { + if lhs.Type() != rhs.Type() { + L.RaiseError("attempt to compare %v with %v", lhs.Type().String(), rhs.Type().String()) + } + switch lhs.Type() { + case LTString: + ret = strCmp(string(lhs.(LString)), string(rhs.(LString))) <= 0 + default: + switch objectRational(L, lhs, rhs, "__le") { + case 1: + ret = true + case 0: + ret = false + default: + ret = !objectRationalWithError(L, rhs, lhs, "__lt") + } + } + } + + v := 1 + if ret { + v = 0 + } + if v == A { + cf.Pc++ + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_TEST + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + C := int(inst>>9) & 0x1ff //GETC + if LVAsBool(reg.Get(RA)) == (C == 0) { + cf.Pc++ + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_TESTSET + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + 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) + } else { + cf.Pc++ + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_CALL + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + nargs := B - 1 + if B == 0 { + nargs = reg.Top() - (RA + 1) + } + lv := reg.Get(RA) + nret := C - 1 + var callable *LFunction + var meta bool + if fn, ok := lv.assertFunction(); ok { + callable = fn + meta = false + } else { + callable, meta = L.metaCall(lv) + } + // +inline-call L.pushCallFrame callFrame{Fn:callable,Pc:0,Base:RA,LocalBase:RA+1,ReturnBase:RA,NArgs:nargs,NRet:nret,Parent:cf,TailCall:0} lv meta + if callable.IsG && callGFunction(L, false) { + return 1 + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_TAILCALL + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + nargs := B - 1 + if B == 0 { + nargs = reg.Top() - (RA + 1) + } + lv := reg.Get(RA) + var callable *LFunction + var meta bool + if fn, ok := lv.assertFunction(); ok { + callable = fn + meta = false + } else { + callable, meta = L.metaCall(lv) + } + if callable == nil { + L.RaiseError("attempt to call a non-function object") + } + // +inline-call L.closeUpvalues lbase + if callable.IsG { + luaframe := cf + L.pushCallFrame(callFrame{ + Fn: callable, + Pc: 0, + Base: RA, + LocalBase: RA + 1, + ReturnBase: cf.ReturnBase, + NArgs: nargs, + NRet: cf.NRet, + Parent: cf, + TailCall: 0, + }, lv, meta) + if callGFunction(L, true) { + return 1 + } + if L.currentFrame == nil || L.currentFrame.Fn.IsG || luaframe == baseframe { + return 1 + } + } else { + base := cf.Base + cf.Fn = callable + cf.Pc = 0 + cf.Base = RA + cf.LocalBase = RA + 1 + cf.ReturnBase = cf.ReturnBase + cf.NArgs = nargs + cf.NRet = cf.NRet + cf.TailCall++ + lbase := cf.LocalBase + if meta { + cf.NArgs++ + L.reg.Insert(lv, cf.LocalBase) + } + // +inline-call L.initCallFrame cf + // +inline-call L.reg.CopyRange base RA -1 reg.Top()-RA-1 + cf.Base = base + cf.LocalBase = base + (cf.LocalBase - lbase + 1) + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_RETURN + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + // +inline-call L.closeUpvalues lbase + nret := B - 1 + if B == 0 { + nret = reg.Top() - RA + } + n := cf.NRet + if cf.NRet == MultRet { + n = nret + } + + if L.Parent != nil && L.stack.Sp() == 1 { + // +inline-call copyReturnValues L reg.Top() RA n B + switchToParentThread(L, n, false, true) + return 1 + } + islast := baseframe == L.stack.Pop() || L.stack.IsEmpty() + // +inline-call copyReturnValues L cf.ReturnBase RA n B + L.currentFrame = L.stack.Last() + if islast || L.currentFrame == nil || L.currentFrame.Fn.IsG { + return 1 + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_FORLOOP + reg := L.reg + cf := L.currentFrame + 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 { + init += step + reg.SetNumber(RA, LNumber(init)) + if (step > 0 && init <= limit) || (step <= 0 && init >= limit) { + Sbx := int(inst&0x3ffff) - opMaxArgSbx //GETSBX + cf.Pc += Sbx + reg.SetNumber(RA+3, LNumber(init)) + } else { + reg.SetTop(RA + 1) + } + } else { + L.RaiseError("for statement step must be a number") + } + } else { + L.RaiseError("for statement limit must be a number") + } + } else { + L.RaiseError("for statement init must be a number") + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_FORPREP + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + 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)) + } else { + L.RaiseError("for statement step must be a number") + } + } else { + L.RaiseError("for statement init must be a number") + } + cf.Pc += Sbx + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_TFORLOOP + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + 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)) + L.callR(2, nret, RA+3) + if value := reg.Get(RA + 3); value != LNil { + reg.Set(RA+2, value) + pc := cf.Fn.Proto.Code[cf.Pc] + cf.Pc += int(pc&0x3ffff) - opMaxArgSbx + } + cf.Pc++ + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_SETLIST + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + if C == 0 { + C = int(cf.Fn.Proto.Code[cf.Pc]) + cf.Pc++ + } + offset := (C - 1) * FieldsPerFlush + table := reg.Get(RA).(*LTable) + nelem := B + if B == 0 { + nelem = reg.Top() - RA - 1 + } + for i := 1; i <= nelem; i++ { + table.RawSetInt(offset+i, reg.Get(RA+i)) + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_CLOSE + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + // +inline-call L.closeUpvalues RA + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_CLOSURE + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + Bx := int(inst & 0x3ffff) //GETBX + proto := cf.Fn.Proto.FunctionPrototypes[Bx] + closure := newLFunctionL(proto, cf.Fn.Env, int(proto.NumUpvalues)) + reg.Set(RA, closure) + for i := 0; i < int(proto.NumUpvalues); i++ { + inst = cf.Fn.Proto.Code[cf.Pc] + cf.Pc++ + B := opGetArgB(inst) + switch opGetOpCode(inst) { + case OP_MOVE: + closure.Upvalues[i] = L.findUpvalue(lbase + B) + case OP_GETUPVAL: + closure.Upvalues[i] = cf.Fn.Upvalues[B] + } + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_VARARG + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + nparams := int(cf.Fn.Proto.NumParameters) + nvarargs := cf.NArgs - nparams + if nvarargs < 0 { + nvarargs = 0 + } + nwant := B - 1 + if B == 0 { + nwant = nvarargs + } + // +inline-call reg.CopyRange RA cf.Base+nparams+1 cf.LocalBase nwant + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_NOP + return 0 + }, + } +} + +func opArith(L *LState, inst uint32, baseframe *callFrame) int { //OP_ADD, OP_SUB, OP_MUL, OP_DIV, OP_MOD, OP_POW + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + opcode := int(inst >> 26) //GETOPCODE + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + lhs := L.rkValue(B) + rhs := L.rkValue(C) + v1, ok1 := lhs.assertFloat64() + v2, ok2 := rhs.assertFloat64() + if ok1 && ok2 { + reg.SetNumber(RA, numberArith(L, opcode, LNumber(v1), LNumber(v2))) + } else { + reg.Set(RA, objectArith(L, opcode, lhs, rhs)) + } + return 0 +} + +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) { + v += frhs + } + return LNumber(v) +} + +func numberArith(L *LState, opcode int, lhs, rhs LNumber) LNumber { + switch opcode { + case OP_ADD: + return lhs + rhs + case OP_SUB: + return lhs - rhs + case OP_MUL: + return lhs * rhs + case OP_DIV: + return lhs / rhs + case OP_MOD: + return luaModulo(lhs, rhs) + case OP_POW: + flhs := float64(lhs) + frhs := float64(rhs) + return LNumber(math.Pow(flhs, frhs)) + } + panic("should not reach here") + return LNumber(0) +} + +func objectArith(L *LState, opcode int, lhs, rhs LValue) LValue { + event := "" + switch opcode { + case OP_ADD: + event = "__add" + case OP_SUB: + event = "__sub" + case OP_MUL: + event = "__mul" + case OP_DIV: + event = "__div" + case OP_MOD: + event = "__mod" + case OP_POW: + event = "__pow" + } + op := L.metaOp2(lhs, rhs, event) + if op.Type() == LTFunction { + L.reg.Push(op) + L.reg.Push(lhs) + L.reg.Push(rhs) + L.Call(2, 1) + return L.reg.Pop() + } + if str, ok := lhs.(LString); ok { + if lnum, err := parseNumber(string(str)); err == nil { + lhs = lnum + } + } + if str, ok := rhs.(LString); ok { + if rnum, err := parseNumber(string(str)); err == nil { + rhs = rnum + } + } + if v1, ok1 := lhs.assertFloat64(); ok1 { + if v2, ok2 := rhs.assertFloat64(); ok2 { + return numberArith(L, opcode, LNumber(v1), LNumber(v2)) + } + } + L.RaiseError(fmt.Sprintf("cannot perform %v operation between %v and %v", + strings.TrimLeft(event, "_"), lhs.Type().String(), rhs.Type().String())) + + return LNil +} + +func stringConcat(L *LState, total, last int) LValue { + rhs := L.reg.Get(last) + total-- + for i := last - 1; total > 0; { + lhs := L.reg.Get(i) + if !(LVCanConvToString(lhs) && LVCanConvToString(rhs)) { + op := L.metaOp2(lhs, rhs, "__concat") + if op.Type() == LTFunction { + L.reg.Push(op) + L.reg.Push(lhs) + L.reg.Push(rhs) + L.Call(2, 1) + rhs = L.reg.Pop() + total-- + i-- + } else { + L.RaiseError("cannot perform concat operation between %v and %v", lhs.Type().String(), rhs.Type().String()) + return LNil + } + } else { + buf := make([]string, total+1) + buf[total] = LVAsString(rhs) + for total > 0 { + lhs = L.reg.Get(i) + if !LVCanConvToString(lhs) { + break + } + buf[total-1] = LVAsString(lhs) + i-- + total-- + } + rhs = LString(strings.Join(buf, "")) + } + } + return rhs +} + +func lessThan(L *LState, lhs, rhs LValue) bool { + // optimization for numbers + if v1, ok1 := lhs.assertFloat64(); ok1 { + if v2, ok2 := rhs.assertFloat64(); ok2 { + return v1 < v2 + } + L.RaiseError("attempt to compare %v with %v", lhs.Type().String(), rhs.Type().String()) + } + if lhs.Type() != rhs.Type() { + L.RaiseError("attempt to compare %v with %v", lhs.Type().String(), rhs.Type().String()) + return false + } + ret := false + switch lhs.Type() { + case LTString: + ret = strCmp(string(lhs.(LString)), string(rhs.(LString))) < 0 + default: + ret = objectRationalWithError(L, lhs, rhs, "__lt") + } + return ret +} + +func equals(L *LState, lhs, rhs LValue, raw bool) bool { + if lhs.Type() != rhs.Type() { + return false + } + + ret := false + switch lhs.Type() { + case LTNil: + ret = true + case LTNumber: + v1, _ := lhs.assertFloat64() + v2, _ := rhs.assertFloat64() + ret = v1 == v2 + case LTBool: + ret = bool(lhs.(LBool)) == bool(rhs.(LBool)) + case LTString: + ret = string(lhs.(LString)) == string(rhs.(LString)) + case LTUserData, LTTable: + if lhs == rhs { + ret = true + } else if !raw { + switch objectRational(L, lhs, rhs, "__eq") { + case 1: + ret = true + default: + ret = false + } + } + default: + ret = lhs == rhs + } + return ret +} + +func objectRationalWithError(L *LState, lhs, rhs LValue, event string) bool { + switch objectRational(L, lhs, rhs, event) { + case 1: + return true + case 0: + return false + } + L.RaiseError("attempt to compare %v with %v", lhs.Type().String(), rhs.Type().String()) + return false +} + +func objectRational(L *LState, lhs, rhs LValue, event string) int { + m1 := L.metaOp1(lhs, event) + m2 := L.metaOp1(rhs, event) + if m1.Type() == LTFunction && m1 == m2 { + L.reg.Push(m1) + L.reg.Push(lhs) + L.reg.Push(rhs) + L.Call(2, 1) + if LVAsBool(L.reg.Pop()) { + return 1 + } + return 0 + } + return -1 +} diff --git a/vendor/github.com/yuin/gopher-lua/alloc.go b/vendor/github.com/yuin/gopher-lua/alloc.go new file mode 100644 index 00000000000..7a8cd63ac10 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/alloc.go @@ -0,0 +1,79 @@ +package lua + +import ( + "reflect" + "unsafe" +) + +// iface is an internal representation of the go-interface. +type iface struct { + itab unsafe.Pointer + word unsafe.Pointer +} + +const preloadLimit LNumber = 128 + +var _fv float64 +var _uv uintptr + +var preloads [int(preloadLimit)]LValue + +func init() { + for i := 0; i < int(preloadLimit); i++ { + preloads[i] = LNumber(i) + } +} + +// allocator is a fast bulk memory allocator for the LValue. +type allocator struct { + size int + fptrs []float64 + fheader *reflect.SliceHeader + + scratchValue LValue + scratchValueP *iface +} + +func newAllocator(size int) *allocator { + al := &allocator{ + size: size, + fptrs: make([]float64, 0, size), + fheader: nil, + } + al.fheader = (*reflect.SliceHeader)(unsafe.Pointer(&al.fptrs)) + al.scratchValue = LNumber(0) + al.scratchValueP = (*iface)(unsafe.Pointer(&al.scratchValue)) + + return al +} + +// LNumber2I takes a number value and returns an interface LValue representing the same number. +// Converting an LNumber to a LValue naively, by doing: +// `var val LValue = myLNumber` +// will result in an individual heap alloc of 8 bytes for the float value. LNumber2I amortizes the cost and memory +// overhead of these allocs by allocating blocks of floats instead. +// The downside of this is that all of the floats on a given block have to become eligible for gc before the block +// as a whole can be gc-ed. +func (al *allocator) LNumber2I(v LNumber) LValue { + // first check for shared preloaded numbers + if v >= 0 && v < preloadLimit && float64(v) == float64(int64(v)) { + return preloads[int(v)] + } + + // check if we need a new alloc page + if cap(al.fptrs) == len(al.fptrs) { + al.fptrs = make([]float64, 0, al.size) + al.fheader = (*reflect.SliceHeader)(unsafe.Pointer(&al.fptrs)) + } + + // alloc a new float, and store our value into it + al.fptrs = append(al.fptrs, float64(v)) + fptr := &al.fptrs[len(al.fptrs)-1] + + // hack our scratch LValue to point to our allocated value + // this scratch lvalue is copied when this function returns meaning the scratch value can be reused + // on the next call + al.scratchValueP.word = unsafe.Pointer(fptr) + + return al.scratchValue +} diff --git a/vendor/github.com/yuin/gopher-lua/ast/ast.go b/vendor/github.com/yuin/gopher-lua/ast/ast.go new file mode 100644 index 00000000000..f337a294732 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/ast/ast.go @@ -0,0 +1,29 @@ +package ast + +type PositionHolder interface { + Line() int + SetLine(int) + LastLine() int + SetLastLine(int) +} + +type Node struct { + line int + lastline int +} + +func (self *Node) Line() int { + return self.line +} + +func (self *Node) SetLine(line int) { + self.line = line +} + +func (self *Node) LastLine() int { + return self.lastline +} + +func (self *Node) SetLastLine(line int) { + self.lastline = line +} diff --git a/vendor/github.com/yuin/gopher-lua/ast/expr.go b/vendor/github.com/yuin/gopher-lua/ast/expr.go new file mode 100644 index 00000000000..ccda3279101 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/ast/expr.go @@ -0,0 +1,137 @@ +package ast + +type Expr interface { + PositionHolder + exprMarker() +} + +type ExprBase struct { + Node +} + +func (expr *ExprBase) exprMarker() {} + +/* ConstExprs {{{ */ + +type ConstExpr interface { + Expr + constExprMarker() +} + +type ConstExprBase struct { + ExprBase +} + +func (expr *ConstExprBase) constExprMarker() {} + +type TrueExpr struct { + ConstExprBase +} + +type FalseExpr struct { + ConstExprBase +} + +type NilExpr struct { + ConstExprBase +} + +type NumberExpr struct { + ConstExprBase + + Value string +} + +type StringExpr struct { + ConstExprBase + + Value string +} + +/* ConstExprs }}} */ + +type Comma3Expr struct { + ExprBase +} + +type IdentExpr struct { + ExprBase + + Value string +} + +type AttrGetExpr struct { + ExprBase + + Object Expr + Key Expr +} + +type TableExpr struct { + ExprBase + + Fields []*Field +} + +type FuncCallExpr struct { + ExprBase + + Func Expr + Receiver Expr + Method string + Args []Expr + AdjustRet bool +} + +type LogicalOpExpr struct { + ExprBase + + Operator string + Lhs Expr + Rhs Expr +} + +type RelationalOpExpr struct { + ExprBase + + Operator string + Lhs Expr + Rhs Expr +} + +type StringConcatOpExpr struct { + ExprBase + + Lhs Expr + Rhs Expr +} + +type ArithmeticOpExpr struct { + ExprBase + + Operator string + Lhs Expr + Rhs Expr +} + +type UnaryMinusOpExpr struct { + ExprBase + Expr Expr +} + +type UnaryNotOpExpr struct { + ExprBase + Expr Expr +} + +type UnaryLenOpExpr struct { + ExprBase + Expr Expr +} + +type FunctionExpr struct { + ExprBase + + ParList *ParList + Stmts []Stmt +} diff --git a/vendor/github.com/yuin/gopher-lua/ast/misc.go b/vendor/github.com/yuin/gopher-lua/ast/misc.go new file mode 100644 index 00000000000..d811c042aa0 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/ast/misc.go @@ -0,0 +1,17 @@ +package ast + +type Field struct { + Key Expr + Value Expr +} + +type ParList struct { + HasVargs bool + Names []string +} + +type FuncName struct { + Func Expr + Receiver Expr + Method string +} diff --git a/vendor/github.com/yuin/gopher-lua/ast/stmt.go b/vendor/github.com/yuin/gopher-lua/ast/stmt.go new file mode 100644 index 00000000000..56ea6d1a23a --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/ast/stmt.go @@ -0,0 +1,95 @@ +package ast + +type Stmt interface { + PositionHolder + stmtMarker() +} + +type StmtBase struct { + Node +} + +func (stmt *StmtBase) stmtMarker() {} + +type AssignStmt struct { + StmtBase + + Lhs []Expr + Rhs []Expr +} + +type LocalAssignStmt struct { + StmtBase + + Names []string + Exprs []Expr +} + +type FuncCallStmt struct { + StmtBase + + Expr Expr +} + +type DoBlockStmt struct { + StmtBase + + Stmts []Stmt +} + +type WhileStmt struct { + StmtBase + + Condition Expr + Stmts []Stmt +} + +type RepeatStmt struct { + StmtBase + + Condition Expr + Stmts []Stmt +} + +type IfStmt struct { + StmtBase + + Condition Expr + Then []Stmt + Else []Stmt +} + +type NumberForStmt struct { + StmtBase + + Name string + Init Expr + Limit Expr + Step Expr + Stmts []Stmt +} + +type GenericForStmt struct { + StmtBase + + Names []string + Exprs []Expr + Stmts []Stmt +} + +type FuncDefStmt struct { + StmtBase + + Name *FuncName + Func *FunctionExpr +} + +type ReturnStmt struct { + StmtBase + + Exprs []Expr +} + +type BreakStmt struct { + StmtBase +} diff --git a/vendor/github.com/yuin/gopher-lua/ast/token.go b/vendor/github.com/yuin/gopher-lua/ast/token.go new file mode 100644 index 00000000000..820467c9a86 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/ast/token.go @@ -0,0 +1,22 @@ +package ast + +import ( + "fmt" +) + +type Position struct { + Source string + Line int + Column int +} + +type Token struct { + Type int + Name string + Str string + Pos Position +} + +func (self *Token) String() string { + return fmt.Sprintf("", self.Name, self.Str) +} diff --git a/vendor/github.com/yuin/gopher-lua/auxlib.go b/vendor/github.com/yuin/gopher-lua/auxlib.go new file mode 100644 index 00000000000..61a3b8b6100 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/auxlib.go @@ -0,0 +1,460 @@ +package lua + +import ( + "bufio" + "fmt" + "io" + "os" + "strings" +) + +/* checkType {{{ */ + +func (ls *LState) CheckAny(n int) LValue { + if n > ls.GetTop() { + ls.ArgError(n, "value expected") + } + return ls.Get(n) +} + +func (ls *LState) CheckInt(n int) int { + v := ls.Get(n) + if intv, ok := v.(LNumber); ok { + return int(intv) + } + ls.TypeError(n, LTNumber) + return 0 +} + +func (ls *LState) CheckInt64(n int) int64 { + v := ls.Get(n) + if intv, ok := v.(LNumber); ok { + return int64(intv) + } + ls.TypeError(n, LTNumber) + return 0 +} + +func (ls *LState) CheckNumber(n int) LNumber { + v := ls.Get(n) + if lv, ok := v.(LNumber); ok { + return lv + } + ls.TypeError(n, LTNumber) + return 0 +} + +func (ls *LState) CheckString(n int) string { + v := ls.Get(n) + if lv, ok := v.(LString); ok { + return string(lv) + } else if LVCanConvToString(v) { + return ls.ToString(n) + } + ls.TypeError(n, LTString) + return "" +} + +func (ls *LState) CheckBool(n int) bool { + v := ls.Get(n) + if lv, ok := v.(LBool); ok { + return bool(lv) + } + ls.TypeError(n, LTBool) + return false +} + +func (ls *LState) CheckTable(n int) *LTable { + v := ls.Get(n) + if lv, ok := v.(*LTable); ok { + return lv + } + ls.TypeError(n, LTTable) + return nil +} + +func (ls *LState) CheckFunction(n int) *LFunction { + v := ls.Get(n) + if lv, ok := v.(*LFunction); ok { + return lv + } + ls.TypeError(n, LTFunction) + return nil +} + +func (ls *LState) CheckUserData(n int) *LUserData { + v := ls.Get(n) + if lv, ok := v.(*LUserData); ok { + return lv + } + ls.TypeError(n, LTUserData) + return nil +} + +func (ls *LState) CheckThread(n int) *LState { + v := ls.Get(n) + if lv, ok := v.(*LState); ok { + return lv + } + ls.TypeError(n, LTThread) + return nil +} + +func (ls *LState) CheckType(n int, typ LValueType) { + v := ls.Get(n) + if v.Type() != typ { + ls.TypeError(n, typ) + } +} + +func (ls *LState) CheckTypes(n int, typs ...LValueType) { + vt := ls.Get(n).Type() + for _, typ := range typs { + if vt == typ { + return + } + } + buf := []string{} + for _, typ := range typs { + buf = append(buf, typ.String()) + } + ls.ArgError(n, strings.Join(buf, " or ")+" expected, got "+ls.Get(n).Type().String()) +} + +func (ls *LState) CheckOption(n int, options []string) int { + str := ls.CheckString(n) + for i, v := range options { + if v == str { + return i + } + } + ls.ArgError(n, fmt.Sprintf("invalid option: %s (must be one of %s)", str, strings.Join(options, ","))) + return 0 +} + +/* }}} */ + +/* optType {{{ */ + +func (ls *LState) OptInt(n int, d int) int { + v := ls.Get(n) + if v == LNil { + return d + } + if intv, ok := v.(LNumber); ok { + return int(intv) + } + ls.TypeError(n, LTNumber) + return 0 +} + +func (ls *LState) OptInt64(n int, d int64) int64 { + v := ls.Get(n) + if v == LNil { + return d + } + if intv, ok := v.(LNumber); ok { + return int64(intv) + } + ls.TypeError(n, LTNumber) + return 0 +} + +func (ls *LState) OptNumber(n int, d LNumber) LNumber { + v := ls.Get(n) + if v == LNil { + return d + } + if lv, ok := v.(LNumber); ok { + return lv + } + ls.TypeError(n, LTNumber) + return 0 +} + +func (ls *LState) OptString(n int, d string) string { + v := ls.Get(n) + if v == LNil { + return d + } + if lv, ok := v.(LString); ok { + return string(lv) + } + ls.TypeError(n, LTString) + return "" +} + +func (ls *LState) OptBool(n int, d bool) bool { + v := ls.Get(n) + if v == LNil { + return d + } + if lv, ok := v.(LBool); ok { + return bool(lv) + } + ls.TypeError(n, LTBool) + return false +} + +func (ls *LState) OptTable(n int, d *LTable) *LTable { + v := ls.Get(n) + if v == LNil { + return d + } + if lv, ok := v.(*LTable); ok { + return lv + } + ls.TypeError(n, LTTable) + return nil +} + +func (ls *LState) OptFunction(n int, d *LFunction) *LFunction { + v := ls.Get(n) + if v == LNil { + return d + } + if lv, ok := v.(*LFunction); ok { + return lv + } + ls.TypeError(n, LTFunction) + return nil +} + +func (ls *LState) OptUserData(n int, d *LUserData) *LUserData { + v := ls.Get(n) + if v == LNil { + return d + } + if lv, ok := v.(*LUserData); ok { + return lv + } + ls.TypeError(n, LTUserData) + return nil +} + +/* }}} */ + +/* error operations {{{ */ + +func (ls *LState) ArgError(n int, message string) { + ls.RaiseError("bad argument #%v to %v (%v)", n, ls.rawFrameFuncName(ls.currentFrame), message) +} + +func (ls *LState) TypeError(n int, typ LValueType) { + ls.RaiseError("bad argument #%v to %v (%v expected, got %v)", n, ls.rawFrameFuncName(ls.currentFrame), typ.String(), ls.Get(n).Type().String()) +} + +/* }}} */ + +/* debug operations {{{ */ + +func (ls *LState) Where(level int) string { + return ls.where(level, false) +} + +/* }}} */ + +/* table operations {{{ */ + +func (ls *LState) FindTable(obj *LTable, n string, size int) LValue { + names := strings.Split(n, ".") + curobj := obj + for _, name := range names { + if curobj.Type() != LTTable { + return LNil + } + nextobj := ls.RawGet(curobj, LString(name)) + if nextobj == LNil { + tb := ls.CreateTable(0, size) + ls.RawSet(curobj, LString(name), tb) + curobj = tb + } else if nextobj.Type() != LTTable { + return LNil + } else { + curobj = nextobj.(*LTable) + } + } + return curobj +} + +/* }}} */ + +/* register operations {{{ */ + +func (ls *LState) RegisterModule(name string, funcs map[string]LGFunction) LValue { + tb := ls.FindTable(ls.Get(RegistryIndex).(*LTable), "_LOADED", 1) + mod := ls.GetField(tb, name) + if mod.Type() != LTTable { + newmod := ls.FindTable(ls.Get(GlobalsIndex).(*LTable), name, len(funcs)) + if newmodtb, ok := newmod.(*LTable); !ok { + ls.RaiseError("name conflict for module(%v)", name) + } else { + for fname, fn := range funcs { + newmodtb.RawSetString(fname, ls.NewFunction(fn)) + } + ls.SetField(tb, name, newmodtb) + return newmodtb + } + } + return mod +} + +func (ls *LState) SetFuncs(tb *LTable, funcs map[string]LGFunction, upvalues ...LValue) *LTable { + for fname, fn := range funcs { + tb.RawSetString(fname, ls.NewClosure(fn, upvalues...)) + } + return tb +} + +/* }}} */ + +/* metatable operations {{{ */ + +func (ls *LState) NewTypeMetatable(typ string) *LTable { + regtable := ls.Get(RegistryIndex) + mt := ls.GetField(regtable, typ) + if tb, ok := mt.(*LTable); ok { + return tb + } + mtnew := ls.NewTable() + ls.SetField(regtable, typ, mtnew) + return mtnew +} + +func (ls *LState) GetMetaField(obj LValue, event string) LValue { + return ls.metaOp1(obj, event) +} + +func (ls *LState) GetTypeMetatable(typ string) LValue { + return ls.GetField(ls.Get(RegistryIndex), typ) +} + +func (ls *LState) CallMeta(obj LValue, event string) LValue { + op := ls.metaOp1(obj, event) + if op.Type() == LTFunction { + ls.reg.Push(op) + ls.reg.Push(obj) + ls.Call(1, 1) + return ls.reg.Pop() + } + return LNil +} + +/* }}} */ + +/* load and function call operations {{{ */ + +func (ls *LState) LoadFile(path string) (*LFunction, error) { + var file *os.File + var err error + if len(path) == 0 { + file = os.Stdin + } else { + file, err = os.Open(path) + defer file.Close() + if err != nil { + return nil, newApiErrorE(ApiErrorFile, err) + } + } + + reader := bufio.NewReader(file) + // get the first character. + c, err := reader.ReadByte() + if err != nil && err != io.EOF { + return nil, newApiErrorE(ApiErrorFile, err) + } + if c == byte('#') { + // Unix exec. file? + // skip first line + _, err, _ = readBufioLine(reader) + if err != nil { + return nil, newApiErrorE(ApiErrorFile, err) + } + } + + if err != io.EOF { + // if the file is not empty, + // unread the first character of the file or newline character(readBufioLine's last byte). + err = reader.UnreadByte() + if err != nil { + return nil, newApiErrorE(ApiErrorFile, err) + } + } + + return ls.Load(reader, path) +} + +func (ls *LState) LoadString(source string) (*LFunction, error) { + return ls.Load(strings.NewReader(source), "") +} + +func (ls *LState) DoFile(path string) error { + if fn, err := ls.LoadFile(path); err != nil { + return err + } else { + ls.Push(fn) + return ls.PCall(0, MultRet, nil) + } +} + +func (ls *LState) DoString(source string) error { + if fn, err := ls.LoadString(source); err != nil { + return err + } else { + ls.Push(fn) + return ls.PCall(0, MultRet, nil) + } +} + +/* }}} */ + +/* GopherLua original APIs {{{ */ + +// 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 { + ls.Push(fn) + ls.Push(lv) + ls.Call(1, 1) + return ls.reg.Pop() + } else { + return LString(lv.String()) + } +} + +// Set a module loader to the package.preload table. +func (ls *LState) PreloadModule(name string, loader LGFunction) { + preload := ls.GetField(ls.GetField(ls.Get(EnvironIndex), "package"), "preload") + if _, ok := preload.(*LTable); !ok { + ls.RaiseError("package.preload must be a table") + } + ls.SetField(preload, name, ls.NewFunction(loader)) +} + +// Checks whether the given index is an LChannel and returns this channel. +func (ls *LState) CheckChannel(n int) chan LValue { + v := ls.Get(n) + if ch, ok := v.(LChannel); ok { + return (chan LValue)(ch) + } + ls.TypeError(n, LTChannel) + return nil +} + +// If the given index is a LChannel, returns this channel. If this argument is absent or is nil, returns ch. Otherwise, raises an error. +func (ls *LState) OptChannel(n int, ch chan LValue) chan LValue { + v := ls.Get(n) + if v == LNil { + return ch + } + if ch, ok := v.(LChannel); ok { + return (chan LValue)(ch) + } + ls.TypeError(n, LTChannel) + return nil +} + +/* }}} */ + +// diff --git a/vendor/github.com/yuin/gopher-lua/baselib.go b/vendor/github.com/yuin/gopher-lua/baselib.go new file mode 100644 index 00000000000..06c90619eef --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/baselib.go @@ -0,0 +1,592 @@ +package lua + +import ( + "fmt" + "io" + "os" + "runtime" + "strconv" + "strings" +) + +/* basic functions {{{ */ + +func OpenBase(L *LState) int { + global := L.Get(GlobalsIndex).(*LTable) + L.SetGlobal("_G", global) + L.SetGlobal("_VERSION", LString(LuaVersion)) + L.SetGlobal("_GOPHER_LUA_VERSION", LString(PackageName+" "+PackageVersion)) + basemod := L.RegisterModule("_G", baseFuncs) + global.RawSetString("ipairs", L.NewClosure(baseIpairs, L.NewFunction(ipairsaux))) + global.RawSetString("pairs", L.NewClosure(basePairs, L.NewFunction(pairsaux))) + L.Push(basemod) + return 1 +} + +var baseFuncs = map[string]LGFunction{ + "assert": baseAssert, + "collectgarbage": baseCollectGarbage, + "dofile": baseDoFile, + "error": baseError, + "getfenv": baseGetFEnv, + "getmetatable": baseGetMetatable, + "load": baseLoad, + "loadfile": baseLoadFile, + "loadstring": baseLoadString, + "next": baseNext, + "pcall": basePCall, + "print": basePrint, + "rawequal": baseRawEqual, + "rawget": baseRawGet, + "rawset": baseRawSet, + "select": baseSelect, + "_printregs": base_PrintRegs, + "setfenv": baseSetFEnv, + "setmetatable": baseSetMetatable, + "tonumber": baseToNumber, + "tostring": baseToString, + "type": baseType, + "unpack": baseUnpack, + "xpcall": baseXPCall, + // loadlib + "module": loModule, + "require": loRequire, + // hidden features + "newproxy": baseNewProxy, +} + +func baseAssert(L *LState) int { + if !L.ToBool(1) { + L.RaiseError(L.OptString(2, "assertion failed!")) + return 0 + } + return L.GetTop() +} + +func baseCollectGarbage(L *LState) int { + runtime.GC() + return 0 +} + +func baseDoFile(L *LState) int { + src := L.ToString(1) + top := L.GetTop() + fn, err := L.LoadFile(src) + if err != nil { + L.Push(LString(err.Error())) + L.Panic(L) + } + L.Push(fn) + L.Call(0, MultRet) + return L.GetTop() - top +} + +func baseError(L *LState) int { + obj := L.CheckAny(1) + level := L.OptInt(2, 1) + L.Error(obj, level) + return 0 +} + +func baseGetFEnv(L *LState) int { + var value LValue + if L.GetTop() == 0 { + value = LNumber(1) + } else { + value = L.Get(1) + } + + if fn, ok := value.(*LFunction); ok { + if !fn.IsG { + L.Push(fn.Env) + } else { + L.Push(L.G.Global) + } + return 1 + } + + if number, ok := value.(LNumber); ok { + level := int(float64(number)) + if level <= 0 { + L.Push(L.Env) + } else { + cf := L.currentFrame + for i := 0; i < level && cf != nil; i++ { + cf = cf.Parent + } + if cf == nil || cf.Fn.IsG { + L.Push(L.G.Global) + } else { + L.Push(cf.Fn.Env) + } + } + return 1 + } + + L.Push(L.G.Global) + return 1 +} + +func baseGetMetatable(L *LState) int { + L.Push(L.GetMetatable(L.CheckAny(1))) + return 1 +} + +func ipairsaux(L *LState) int { + tb := L.CheckTable(1) + i := L.CheckInt(2) + i++ + v := tb.RawGetInt(i) + if v == LNil { + return 0 + } else { + L.Pop(1) + L.Push(LNumber(i)) + L.Push(LNumber(i)) + L.Push(v) + return 2 + } +} + +func baseIpairs(L *LState) int { + tb := L.CheckTable(1) + L.Push(L.Get(UpvalueIndex(1))) + L.Push(tb) + L.Push(LNumber(0)) + return 3 +} + +func loadaux(L *LState, reader io.Reader, chunkname string) int { + if fn, err := L.Load(reader, chunkname); err != nil { + L.Push(LNil) + L.Push(LString(err.Error())) + return 2 + } else { + L.Push(fn) + return 1 + } +} + +func baseLoad(L *LState) int { + fn := L.CheckFunction(1) + chunkname := L.OptString(2, "?") + top := L.GetTop() + buf := []string{} + for { + L.SetTop(top) + L.Push(fn) + L.Call(0, 1) + ret := L.reg.Pop() + if ret == LNil { + break + } else if LVCanConvToString(ret) { + str := ret.String() + if len(str) > 0 { + buf = append(buf, string(str)) + } else { + break + } + } else { + L.Push(LNil) + L.Push(LString("reader function must return a string")) + return 2 + } + } + return loadaux(L, strings.NewReader(strings.Join(buf, "")), chunkname) +} + +func baseLoadFile(L *LState) int { + var reader io.Reader + var chunkname string + var err error + if L.GetTop() < 1 { + reader = os.Stdin + chunkname = "" + } else { + chunkname = L.CheckString(1) + reader, err = os.Open(chunkname) + if err != nil { + L.Push(LNil) + L.Push(LString(fmt.Sprintf("can not open file: %v", chunkname))) + return 2 + } + defer reader.(*os.File).Close() + } + return loadaux(L, reader, chunkname) +} + +func baseLoadString(L *LState) int { + return loadaux(L, strings.NewReader(L.CheckString(1)), L.OptString(2, "")) +} + +func baseNext(L *LState) int { + tb := L.CheckTable(1) + index := LNil + if L.GetTop() >= 2 { + index = L.Get(2) + } + key, value := tb.Next(index) + if key == LNil { + L.Push(LNil) + return 1 + } + L.Push(key) + L.Push(value) + return 2 +} + +func pairsaux(L *LState) int { + tb := L.CheckTable(1) + key, value := tb.Next(L.Get(2)) + if key == LNil { + return 0 + } else { + L.Pop(1) + L.Push(key) + L.Push(key) + L.Push(value) + return 2 + } +} + +func basePairs(L *LState) int { + tb := L.CheckTable(1) + L.Push(L.Get(UpvalueIndex(1))) + L.Push(tb) + L.Push(LNil) + return 3 +} + +func basePCall(L *LState) int { + L.CheckAny(1) + v := L.Get(1) + if v.Type() != LTFunction { + L.Push(LFalse) + L.Push(LString("attempt to call a " + v.Type().String() + " value")) + return 2 + } + nargs := L.GetTop() - 1 + if err := L.PCall(nargs, MultRet, nil); err != nil { + L.Push(LFalse) + if aerr, ok := err.(*ApiError); ok { + L.Push(aerr.Object) + } else { + L.Push(LString(err.Error())) + } + return 2 + } else { + L.Insert(LTrue, 1) + return L.GetTop() + } +} + +func basePrint(L *LState) int { + top := L.GetTop() + for i := 1; i <= top; i++ { + fmt.Print(L.ToStringMeta(L.Get(i)).String()) + if i != top { + fmt.Print("\t") + } + } + fmt.Println("") + return 0 +} + +func base_PrintRegs(L *LState) int { + L.printReg() + return 0 +} + +func baseRawEqual(L *LState) int { + if L.CheckAny(1) == L.CheckAny(2) { + L.Push(LTrue) + } else { + L.Push(LFalse) + } + return 1 +} + +func baseRawGet(L *LState) int { + L.Push(L.RawGet(L.CheckTable(1), L.CheckAny(2))) + return 1 +} + +func baseRawSet(L *LState) int { + L.RawSet(L.CheckTable(1), L.CheckAny(2), L.CheckAny(3)) + return 0 +} + +func baseSelect(L *LState) int { + L.CheckTypes(1, LTNumber, LTString) + switch lv := L.Get(1).(type) { + case LNumber: + idx := int(lv) + num := L.reg.Top() - L.indexToReg(int(lv)) - 1 + if idx < 0 { + num++ + } + return num + case LString: + if string(lv) != "#" { + L.ArgError(1, "invalid string '"+string(lv)+"'") + } + L.Push(LNumber(L.GetTop() - 1)) + return 1 + } + return 0 +} + +func baseSetFEnv(L *LState) int { + var value LValue + if L.GetTop() == 0 { + value = LNumber(1) + } else { + value = L.Get(1) + } + env := L.CheckTable(2) + + if fn, ok := value.(*LFunction); ok { + if fn.IsG { + L.RaiseError("cannot change the environment of given object") + } else { + fn.Env = env + L.Push(fn) + return 1 + } + } + + if number, ok := value.(LNumber); ok { + level := int(float64(number)) + if level <= 0 { + L.Env = env + return 0 + } + + cf := L.currentFrame + for i := 0; i < level && cf != nil; i++ { + cf = cf.Parent + } + if cf == nil || cf.Fn.IsG { + L.RaiseError("cannot change the environment of given object") + } else { + cf.Fn.Env = env + L.Push(cf.Fn) + return 1 + } + } + + L.RaiseError("cannot change the environment of given object") + return 0 +} + +func baseSetMetatable(L *LState) int { + L.CheckTypes(2, LTNil, LTTable) + obj := L.Get(1) + if obj == LNil { + L.RaiseError("cannot set metatable to a nil object.") + } + mt := L.Get(2) + if m := L.metatable(obj, true); m != LNil { + if tb, ok := m.(*LTable); ok && tb.RawGetString("__metatable") != LNil { + L.RaiseError("cannot change a protected metatable") + } + } + L.SetMetatable(obj, mt) + L.SetTop(1) + return 1 +} + +func baseToNumber(L *LState) int { + base := L.OptInt(2, 10) + noBase := L.Get(2) == LNil + + switch lv := L.CheckAny(1).(type) { + case LNumber: + L.Push(lv) + case LString: + str := strings.Trim(string(lv), " \n\t") + if strings.Index(str, ".") > -1 { + if v, err := strconv.ParseFloat(str, LNumberBit); err != nil { + L.Push(LNil) + } else { + L.Push(LNumber(v)) + } + } else { + if noBase && strings.HasPrefix(strings.ToLower(str), "0x") { + base, str = 16, str[2:] // Hex number + } + if v, err := strconv.ParseInt(str, base, LNumberBit); err != nil { + L.Push(LNil) + } else { + L.Push(LNumber(v)) + } + } + default: + L.Push(LNil) + } + return 1 +} + +func baseToString(L *LState) int { + v1 := L.CheckAny(1) + L.Push(L.ToStringMeta(v1)) + return 1 +} + +func baseType(L *LState) int { + L.Push(LString(L.CheckAny(1).Type().String())) + return 1 +} + +func baseUnpack(L *LState) int { + tb := L.CheckTable(1) + start := L.OptInt(2, 1) + end := L.OptInt(3, tb.Len()) + for i := start; i <= end; i++ { + L.Push(tb.RawGetInt(i)) + } + ret := end - start + 1 + if ret < 0 { + return 0 + } + return ret +} + +func baseXPCall(L *LState) int { + fn := L.CheckFunction(1) + errfunc := L.CheckFunction(2) + + top := L.GetTop() + L.Push(fn) + if err := L.PCall(0, MultRet, errfunc); err != nil { + L.Push(LFalse) + if aerr, ok := err.(*ApiError); ok { + L.Push(aerr.Object) + } else { + L.Push(LString(err.Error())) + } + return 2 + } else { + L.Insert(LTrue, top+1) + return L.GetTop() - top + } +} + +/* }}} */ + +/* load lib {{{ */ + +func loModule(L *LState) int { + name := L.CheckString(1) + loaded := L.GetField(L.Get(RegistryIndex), "_LOADED") + tb := L.GetField(loaded, name) + if _, ok := tb.(*LTable); !ok { + tb = L.FindTable(L.Get(GlobalsIndex).(*LTable), name, 1) + if tb == LNil { + L.RaiseError("name conflict for module: %v", name) + } + L.SetField(loaded, name, tb) + } + if L.GetField(tb, "_NAME") == LNil { + L.SetField(tb, "_M", tb) + L.SetField(tb, "_NAME", LString(name)) + names := strings.Split(name, ".") + pname := "" + if len(names) > 1 { + pname = strings.Join(names[:len(names)-1], ".") + "." + } + L.SetField(tb, "_PACKAGE", LString(pname)) + } + + caller := L.currentFrame.Parent + if caller == nil { + L.RaiseError("no calling stack.") + } else if caller.Fn.IsG { + L.RaiseError("module() can not be called from GFunctions.") + } + L.SetFEnv(caller.Fn, tb) + + top := L.GetTop() + for i := 2; i <= top; i++ { + L.Push(L.Get(i)) + L.Push(tb) + L.Call(1, 0) + } + L.Push(tb) + return 1 +} + +var loopdetection = &LUserData{} + +func loRequire(L *LState) int { + name := L.CheckString(1) + loaded := L.GetField(L.Get(RegistryIndex), "_LOADED") + lv := L.GetField(loaded, name) + if LVAsBool(lv) { + if lv == loopdetection { + L.RaiseError("loop or previous error loading module: %s", name) + } + L.Push(lv) + return 1 + } + loaders, ok := L.GetField(L.Get(RegistryIndex), "_LOADERS").(*LTable) + if !ok { + L.RaiseError("package.loaders must be a table") + } + messages := []string{} + var modasfunc LValue + for i := 1; ; i++ { + loader := L.RawGetInt(loaders, i) + if loader == LNil { + L.RaiseError("module %s not found:\n\t%s, ", name, strings.Join(messages, "\n\t")) + } + L.Push(loader) + L.Push(LString(name)) + L.Call(1, 1) + ret := L.reg.Pop() + switch retv := ret.(type) { + case *LFunction: + modasfunc = retv + goto loopbreak + case LString: + messages = append(messages, string(retv)) + } + } +loopbreak: + L.SetField(loaded, name, loopdetection) + L.Push(modasfunc) + L.Push(LString(name)) + L.Call(1, 1) + ret := L.reg.Pop() + modv := L.GetField(loaded, name) + if ret != LNil && modv == loopdetection { + L.SetField(loaded, name, ret) + L.Push(ret) + } else if modv == loopdetection { + L.SetField(loaded, name, LTrue) + L.Push(LTrue) + } else { + L.Push(modv) + } + return 1 +} + +/* }}} */ + +/* hidden features {{{ */ + +func baseNewProxy(L *LState) int { + ud := L.NewUserData() + L.SetTop(1) + if L.Get(1) == LTrue { + L.SetMetatable(ud, L.NewTable()) + } else if d, ok := L.Get(1).(*LUserData); ok { + L.SetMetatable(ud, L.GetMetatable(d)) + } + L.Push(ud) + return 1 +} + +/* }}} */ + +// diff --git a/vendor/github.com/yuin/gopher-lua/channellib.go b/vendor/github.com/yuin/gopher-lua/channellib.go new file mode 100644 index 00000000000..a92bf72cd0e --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/channellib.go @@ -0,0 +1,184 @@ +package lua + +import ( + "reflect" +) + +func checkChannel(L *LState, idx int) reflect.Value { + ch := L.CheckChannel(idx) + return reflect.ValueOf(ch) +} + +func checkGoroutineSafe(L *LState, idx int) LValue { + v := L.CheckAny(2) + if !isGoroutineSafe(v) { + L.ArgError(2, "can not send a function, userdata, thread or table that has a metatable") + } + return v +} + +func OpenChannel(L *LState) int { + var mod LValue + //_, ok := L.G.builtinMts[int(LTChannel)] + // if !ok { + mod = L.RegisterModule(ChannelLibName, channelFuncs) + mt := L.SetFuncs(L.NewTable(), channelMethods) + mt.RawSetString("__index", mt) + L.G.builtinMts[int(LTChannel)] = mt + // } + L.Push(mod) + return 1 +} + +var channelFuncs = map[string]LGFunction{ + "make": channelMake, + "select": channelSelect, +} + +func channelMake(L *LState) int { + buffer := L.OptInt(1, 0) + L.Push(LChannel(make(chan LValue, buffer))) + return 1 +} + +func channelSelect(L *LState) int { + //TODO check case table size + cases := make([]reflect.SelectCase, L.GetTop()) + top := L.GetTop() + for i := 0; i < top; i++ { + cas := reflect.SelectCase{ + Dir: reflect.SelectSend, + Chan: reflect.ValueOf(nil), + Send: reflect.ValueOf(nil), + } + tbl := L.CheckTable(i + 1) + dir, ok1 := tbl.RawGetInt(1).(LString) + if !ok1 { + L.ArgError(i+1, "invalid select case") + } + switch string(dir) { + case "<-|": + ch, ok := tbl.RawGetInt(2).(LChannel) + if !ok { + L.ArgError(i+1, "invalid select case") + } + cas.Chan = reflect.ValueOf((chan LValue)(ch)) + v := tbl.RawGetInt(3) + if !isGoroutineSafe(v) { + L.ArgError(i+1, "can not send a function, userdata, thread or table that has a metatable") + } + cas.Send = reflect.ValueOf(v) + case "|<-": + ch, ok := tbl.RawGetInt(2).(LChannel) + if !ok { + L.ArgError(i+1, "invalid select case") + } + cas.Chan = reflect.ValueOf((chan LValue)(ch)) + cas.Dir = reflect.SelectRecv + case "default": + cas.Dir = reflect.SelectDefault + default: + L.ArgError(i+1, "invalid channel direction:"+string(dir)) + } + cases[i] = cas + } + + if L.ctx != nil { + cases = append(cases, reflect.SelectCase{ + Dir: reflect.SelectRecv, + Chan: reflect.ValueOf(L.ctx.Done()), + Send: reflect.ValueOf(nil), + }) + } + + pos, recv, rok := reflect.Select(cases) + + if L.ctx != nil && pos == L.GetTop() { + return 0 + } + + lv := LNil + if recv.Kind() != 0 { + lv, _ = recv.Interface().(LValue) + if lv == nil { + lv = LNil + } + } + tbl := L.Get(pos + 1).(*LTable) + last := tbl.RawGetInt(tbl.Len()) + if last.Type() == LTFunction { + L.Push(last) + switch cases[pos].Dir { + case reflect.SelectRecv: + if rok { + L.Push(LTrue) + } else { + L.Push(LFalse) + } + L.Push(lv) + L.Call(2, 0) + case reflect.SelectSend: + L.Push(tbl.RawGetInt(3)) + L.Call(1, 0) + case reflect.SelectDefault: + L.Call(0, 0) + } + } + L.Push(LNumber(pos + 1)) + L.Push(lv) + if rok { + L.Push(LTrue) + } else { + L.Push(LFalse) + } + return 3 +} + +var channelMethods = map[string]LGFunction{ + "receive": channelReceive, + "send": channelSend, + "close": channelClose, +} + +func channelReceive(L *LState) int { + rch := checkChannel(L, 1) + var v reflect.Value + var ok bool + if L.ctx != nil { + cases := []reflect.SelectCase{{ + Dir: reflect.SelectRecv, + Chan: reflect.ValueOf(L.ctx.Done()), + Send: reflect.ValueOf(nil), + }, { + Dir: reflect.SelectRecv, + Chan: rch, + Send: reflect.ValueOf(nil), + }} + _, v, ok = reflect.Select(cases) + } else { + v, ok = rch.Recv() + } + if ok { + L.Push(LTrue) + L.Push(v.Interface().(LValue)) + } else { + L.Push(LFalse) + L.Push(LNil) + } + return 2 +} + +func channelSend(L *LState) int { + rch := checkChannel(L, 1) + v := checkGoroutineSafe(L, 2) + rch.Send(reflect.ValueOf(v)) + return 0 +} + +func channelClose(L *LState) int { + rch := checkChannel(L, 1) + rch.Close() + return 0 +} + +// diff --git a/vendor/github.com/yuin/gopher-lua/compile.go b/vendor/github.com/yuin/gopher-lua/compile.go new file mode 100644 index 00000000000..d3c665ae57c --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/compile.go @@ -0,0 +1,1672 @@ +package lua + +import ( + "fmt" + "github.com/yuin/gopher-lua/ast" + "math" + "reflect" +) + +/* internal constants & structs {{{ */ + +const maxRegisters = 200 + +type expContextType int + +const ( + ecGlobal expContextType = iota + ecUpvalue + ecLocal + ecTable + ecVararg + ecMethod + ecNone +) + +const regNotDefined = opMaxArgsA + 1 +const labelNoJump = 0 + +type expcontext struct { + ctype expContextType + reg int + // varargopt >= 0: wants varargopt+1 results, i.e a = func() + // varargopt = -1: ignore results i.e func() + // varargopt = -2: receive all results i.e a = {func()} + varargopt int +} + +type assigncontext struct { + ec *expcontext + keyrk int + valuerk int + keyks bool + needmove bool +} + +type lblabels struct { + t int + f int + e int + b bool +} + +type constLValueExpr struct { + ast.ExprBase + + Value LValue +} + +// }}} + +/* utilities {{{ */ +var _ecnone0 = &expcontext{ecNone, regNotDefined, 0} +var _ecnonem1 = &expcontext{ecNone, regNotDefined, -1} +var _ecnonem2 = &expcontext{ecNone, regNotDefined, -2} +var ecfuncdef = &expcontext{ecMethod, regNotDefined, 0} + +func ecupdate(ec *expcontext, ctype expContextType, reg, varargopt int) { + if ec == _ecnone0 || ec == _ecnonem1 || ec == _ecnonem2 { + panic("can not update ec cache") + } + ec.ctype = ctype + ec.reg = reg + ec.varargopt = varargopt +} + +func ecnone(varargopt int) *expcontext { + switch varargopt { + case 0: + return _ecnone0 + case -1: + return _ecnonem1 + case -2: + return _ecnonem2 + } + return &expcontext{ecNone, regNotDefined, varargopt} +} + +func shouldmove(ec *expcontext, reg int) bool { + return ec.ctype == ecLocal && ec.reg != regNotDefined && ec.reg != reg +} + +func sline(pos ast.PositionHolder) int { + return pos.Line() +} + +func eline(pos ast.PositionHolder) int { + return pos.LastLine() +} + +func savereg(ec *expcontext, reg int) int { + if ec.ctype != ecLocal || ec.reg == regNotDefined { + return reg + } + return ec.reg +} + +func raiseCompileError(context *funcContext, line int, format string, args ...interface{}) { + msg := fmt.Sprintf(format, args...) + panic(&CompileError{context: context, Line: line, Message: msg}) +} + +func isVarArgReturnExpr(expr ast.Expr) bool { + switch ex := expr.(type) { + case *ast.FuncCallExpr: + return !ex.AdjustRet + case *ast.Comma3Expr: + return true + } + return false +} + +func lnumberValue(expr ast.Expr) (LNumber, bool) { + if ex, ok := expr.(*ast.NumberExpr); ok { + lv, err := parseNumber(ex.Value) + if err != nil { + lv = LNumber(math.NaN()) + } + return lv, true + } else if ex, ok := expr.(*constLValueExpr); ok { + return ex.Value.(LNumber), true + } + return 0, false +} + +/* utilities }}} */ + +type CompileError struct { // {{{ + context *funcContext + Line int + Message string +} + +func (e *CompileError) Error() string { + return fmt.Sprintf("compile error near line(%v) %v: %v", e.Line, e.context.Proto.SourceName, e.Message) +} // }}} + +type codeStore struct { // {{{ + codes []uint32 + lines []int + pc int +} + +func (cd *codeStore) Add(inst uint32, line int) { + if l := len(cd.codes); l <= 0 || cd.pc == l { + cd.codes = append(cd.codes, inst) + cd.lines = append(cd.lines, line) + } else { + cd.codes[cd.pc] = inst + cd.lines[cd.pc] = line + } + cd.pc++ +} + +func (cd *codeStore) AddABC(op int, a int, b int, c int, line int) { + cd.Add(opCreateABC(op, a, b, c), line) +} + +func (cd *codeStore) AddABx(op int, a int, bx int, line int) { + cd.Add(opCreateABx(op, a, bx), line) +} + +func (cd *codeStore) AddASbx(op int, a int, sbx int, line int) { + cd.Add(opCreateASbx(op, a, sbx), line) +} + +func (cd *codeStore) PropagateKMV(top int, save *int, reg *int, inc int) { + lastinst := cd.Last() + if opGetArgA(lastinst) >= top { + switch opGetOpCode(lastinst) { + case OP_LOADK: + cindex := opGetArgBx(lastinst) + if cindex <= opMaxIndexRk { + cd.Pop() + *save = opRkAsk(cindex) + return + } + case OP_MOVE: + cd.Pop() + *save = opGetArgB(lastinst) + return + } + } + *save = *reg + *reg = *reg + inc +} + +func (cd *codeStore) PropagateMV(top int, save *int, reg *int, inc int) { + lastinst := cd.Last() + if opGetArgA(lastinst) >= top { + switch opGetOpCode(lastinst) { + case OP_MOVE: + cd.Pop() + *save = opGetArgB(lastinst) + return + } + } + *save = *reg + *reg = *reg + inc +} + +func (cd *codeStore) AddLoadNil(a, b, line int) { + last := cd.Last() + if opGetOpCode(last) == OP_LOADNIL && (opGetArgA(last)+opGetArgB(last)) == a { + cd.SetB(cd.LastPC(), b) + } else { + cd.AddABC(OP_LOADNIL, a, b, 0, line) + } +} + +func (cd *codeStore) SetOpCode(pc int, v int) { + opSetOpCode(&cd.codes[pc], v) +} + +func (cd *codeStore) SetA(pc int, v int) { + opSetArgA(&cd.codes[pc], v) +} + +func (cd *codeStore) SetB(pc int, v int) { + opSetArgB(&cd.codes[pc], v) +} + +func (cd *codeStore) SetC(pc int, v int) { + opSetArgC(&cd.codes[pc], v) +} + +func (cd *codeStore) SetBx(pc int, v int) { + opSetArgBx(&cd.codes[pc], v) +} + +func (cd *codeStore) SetSbx(pc int, v int) { + opSetArgSbx(&cd.codes[pc], v) +} + +func (cd *codeStore) At(pc int) uint32 { + return cd.codes[pc] +} + +func (cd *codeStore) List() []uint32 { + return cd.codes[:cd.pc] +} + +func (cd *codeStore) PosList() []int { + return cd.lines[:cd.pc] +} + +func (cd *codeStore) LastPC() int { + return cd.pc - 1 +} + +func (cd *codeStore) Last() uint32 { + if cd.pc == 0 { + return opInvalidInstruction + } + return cd.codes[cd.pc-1] +} + +func (cd *codeStore) Pop() { + cd.pc-- +} /* }}} Code */ + +/* {{{ VarNamePool */ + +type varNamePoolValue struct { + Index int + Name string +} + +type varNamePool struct { + names []string + offset int +} + +func newVarNamePool(offset int) *varNamePool { + return &varNamePool{make([]string, 0, 16), offset} +} + +func (vp *varNamePool) Names() []string { + return vp.names +} + +func (vp *varNamePool) List() []varNamePoolValue { + result := make([]varNamePoolValue, len(vp.names), len(vp.names)) + for i, name := range vp.names { + result[i].Index = i + vp.offset + result[i].Name = name + } + return result +} + +func (vp *varNamePool) LastIndex() int { + return vp.offset + len(vp.names) +} + +func (vp *varNamePool) Find(name string) int { + for i := len(vp.names) - 1; i >= 0; i-- { + if vp.names[i] == name { + return i + vp.offset + } + } + return -1 +} + +func (vp *varNamePool) RegisterUnique(name string) int { + index := vp.Find(name) + if index < 0 { + return vp.Register(name) + } + return index +} + +func (vp *varNamePool) Register(name string) int { + vp.names = append(vp.names, name) + return len(vp.names) - 1 + vp.offset +} + +/* }}} VarNamePool */ + +/* FuncContext {{{ */ + +type codeBlock struct { + LocalVars *varNamePool + BreakLabel int + Parent *codeBlock + RefUpvalue bool + LineStart int + LastLine int +} + +func newCodeBlock(localvars *varNamePool, blabel int, parent *codeBlock, pos ast.PositionHolder) *codeBlock { + bl := &codeBlock{localvars, blabel, parent, false, 0, 0} + if pos != nil { + bl.LineStart = pos.Line() + bl.LastLine = pos.LastLine() + } + return bl +} + +type funcContext struct { + Proto *FunctionProto + Code *codeStore + Parent *funcContext + Upvalues *varNamePool + Block *codeBlock + Blocks []*codeBlock + regTop int + labelId int + labelPc map[int]int +} + +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{}, + } + fc.Blocks = []*codeBlock{fc.Block} + return fc +} + +func (fc *funcContext) NewLabel() int { + ret := fc.labelId + fc.labelId++ + return ret +} + +func (fc *funcContext) SetLabelPc(label int, pc int) { + fc.labelPc[label] = pc +} + +func (fc *funcContext) GetLabelPc(label int) int { + return fc.labelPc[label] +} + +func (fc *funcContext) ConstIndex(value LValue) int { + ctype := value.Type() + for i, lv := range fc.Proto.Constants { + if lv.Type() == ctype && lv == value { + return i + } + } + fc.Proto.Constants = append(fc.Proto.Constants, value) + v := len(fc.Proto.Constants) - 1 + if v > opMaxArgBx { + raiseCompileError(fc, fc.Proto.LineDefined, "too many constants") + } + return v +} + +func (fc *funcContext) RegisterLocalVar(name string) int { + ret := fc.Block.LocalVars.Register(name) + fc.Proto.DbgLocals = append(fc.Proto.DbgLocals, &DbgLocalInfo{Name: name, StartPc: fc.Code.LastPC() + 1}) + fc.SetRegTop(fc.RegTop() + 1) + return ret +} + +func (fc *funcContext) FindLocalVarAndBlock(name string) (int, *codeBlock) { + for block := fc.Block; block != nil; block = block.Parent { + if index := block.LocalVars.Find(name); index > -1 { + return index, block + } + } + return -1, nil +} + +func (fc *funcContext) FindLocalVar(name string) int { + idx, _ := fc.FindLocalVarAndBlock(name) + return idx +} + +func (fc *funcContext) LocalVars() []varNamePoolValue { + result := make([]varNamePoolValue, 0, 32) + for _, block := range fc.Blocks { + result = append(result, block.LocalVars.List()...) + } + return result +} + +func (fc *funcContext) EnterBlock(blabel int, pos ast.PositionHolder) { + fc.Block = newCodeBlock(newVarNamePool(fc.RegTop()), blabel, fc.Block, pos) + fc.Blocks = append(fc.Blocks, fc.Block) +} + +func (fc *funcContext) CloseUpvalues() int { + n := -1 + if fc.Block.RefUpvalue { + n = fc.Block.Parent.LocalVars.LastIndex() + fc.Code.AddABC(OP_CLOSE, n, 0, 0, fc.Block.LastLine) + } + return n +} + +func (fc *funcContext) LeaveBlock() int { + closed := fc.CloseUpvalues() + fc.EndScope() + fc.Block = fc.Block.Parent + fc.SetRegTop(fc.Block.LocalVars.LastIndex()) + return closed +} + +func (fc *funcContext) EndScope() { + for _, vr := range fc.Block.LocalVars.List() { + fc.Proto.DbgLocals[vr.Index].EndPc = fc.Code.LastPC() + } +} + +func (fc *funcContext) SetRegTop(top int) { + if top > maxRegisters { + raiseCompileError(fc, fc.Proto.LineDefined, "too many local variables") + } + fc.regTop = top +} + +func (fc *funcContext) RegTop() int { + return fc.regTop +} + +/* FuncContext }}} */ + +func compileChunk(context *funcContext, chunk []ast.Stmt) { // {{{ + for _, stmt := range chunk { + compileStmt(context, stmt) + } +} // }}} + +func compileBlock(context *funcContext, chunk []ast.Stmt) { // {{{ + if len(chunk) == 0 { + return + } + ph := &ast.Node{} + ph.SetLine(sline(chunk[0])) + ph.SetLastLine(eline(chunk[len(chunk)-1])) + context.EnterBlock(labelNoJump, ph) + for _, stmt := range chunk { + compileStmt(context, stmt) + } + context.LeaveBlock() +} // }}} + +func compileStmt(context *funcContext, stmt ast.Stmt) { // {{{ + switch st := stmt.(type) { + case *ast.AssignStmt: + compileAssignStmt(context, st) + case *ast.LocalAssignStmt: + compileLocalAssignStmt(context, st) + case *ast.FuncCallStmt: + compileFuncCallExpr(context, context.RegTop(), st.Expr.(*ast.FuncCallExpr), ecnone(-1)) + case *ast.DoBlockStmt: + context.EnterBlock(labelNoJump, st) + compileChunk(context, st.Stmts) + context.LeaveBlock() + case *ast.WhileStmt: + compileWhileStmt(context, st) + case *ast.RepeatStmt: + compileRepeatStmt(context, st) + case *ast.FuncDefStmt: + compileFuncDefStmt(context, st) + case *ast.ReturnStmt: + compileReturnStmt(context, st) + case *ast.IfStmt: + compileIfStmt(context, st) + case *ast.BreakStmt: + compileBreakStmt(context, st) + case *ast.NumberForStmt: + compileNumberForStmt(context, st) + case *ast.GenericForStmt: + compileGenericForStmt(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 + switch st := lhs.(type) { + case *ast.IdentExpr: + identtype := getIdentRefType(context, context, st) + ec := &expcontext{identtype, regNotDefined, 0} + switch identtype { + case ecGlobal: + context.ConstIndex(LString(st.Value)) + case ecUpvalue: + context.Upvalues.RegisterUnique(st.Value) + case ecLocal: + if islast { + ec.reg = context.FindLocalVar(st.Value) + } + } + acs = append(acs, &assigncontext{ec, 0, 0, false, false}) + case *ast.AttrGetExpr: + ac := &assigncontext{&expcontext{ecTable, regNotDefined, 0}, 0, 0, false, false} + compileExprWithKMVPropagation(context, st.Object, ®, &ac.ec.reg) + ac.keyrk = reg + reg += compileExpr(context, reg, st.Key, ecnone(0)) + if _, ok := st.Key.(*ast.StringExpr); ok { + ac.keyks = true + } + acs = append(acs, ac) + + default: + panic("invalid left expression.") + } + } + return reg, acs +} // }}} + +func compileAssignStmtRight(context *funcContext, stmt *ast.AssignStmt, reg int, acs []*assigncontext) (int, []*assigncontext) { // {{{ + lennames := len(stmt.Lhs) + lenexprs := len(stmt.Rhs) + namesassigned := 0 + + for namesassigned < lennames { + ac := acs[namesassigned] + ec := ac.ec + var expr ast.Expr = nil + if namesassigned >= lenexprs { + expr = &ast.NilExpr{} + expr.SetLine(sline(stmt.Lhs[namesassigned])) + expr.SetLastLine(eline(stmt.Lhs[namesassigned])) + } else if isVarArgReturnExpr(stmt.Rhs[namesassigned]) && (lenexprs-namesassigned-1) <= 0 { + varargopt := lennames - namesassigned - 1 + regstart := reg + reginc := compileExpr(context, reg, stmt.Rhs[namesassigned], ecnone(varargopt)) + reg += reginc + for i := namesassigned; i < namesassigned+int(reginc); i++ { + acs[i].needmove = true + if acs[i].ec.ctype == ecTable { + acs[i].valuerk = regstart + (i - namesassigned) + } + } + namesassigned = lennames + continue + } + + if expr == nil { + expr = stmt.Rhs[namesassigned] + } + idx := reg + reginc := compileExpr(context, reg, expr, ec) + if ec.ctype == ecTable { + if _, ok := expr.(*ast.LogicalOpExpr); !ok { + context.Code.PropagateKMV(context.RegTop(), &ac.valuerk, ®, reginc) + } else { + ac.valuerk = idx + reg += reginc + } + } else { + ac.needmove = reginc != 0 + reg += reginc + } + namesassigned += 1 + } + + rightreg := reg - 1 + + // extra right exprs + for i := namesassigned; i < lenexprs; i++ { + varargopt := -1 + if i != lenexprs-1 { + varargopt = 0 + } + reg += compileExpr(context, reg, stmt.Rhs[i], ecnone(varargopt)) + } + return rightreg, acs +} // }}} + +func compileAssignStmt(context *funcContext, stmt *ast.AssignStmt) { // {{{ + code := context.Code + lennames := len(stmt.Lhs) + reg, acs := compileAssignStmtLeft(context, stmt) + reg, acs = compileAssignStmtRight(context, stmt, reg, acs) + + for i := lennames - 1; i >= 0; i-- { + ex := stmt.Lhs[i] + switch acs[i].ec.ctype { + case ecLocal: + if acs[i].needmove { + code.AddABC(OP_MOVE, context.FindLocalVar(ex.(*ast.IdentExpr).Value), reg, 0, sline(ex)) + reg -= 1 + } + case ecGlobal: + code.AddABx(OP_SETGLOBAL, reg, context.ConstIndex(LString(ex.(*ast.IdentExpr).Value)), sline(ex)) + reg -= 1 + case ecUpvalue: + code.AddABC(OP_SETUPVAL, reg, context.Upvalues.RegisterUnique(ex.(*ast.IdentExpr).Value), 0, sline(ex)) + reg -= 1 + case ecTable: + opcode := OP_SETTABLE + if acs[i].keyks { + opcode = OP_SETTABLEKS + } + code.AddABC(opcode, acs[i].ec.reg, acs[i].keyrk, acs[i].valuerk, sline(ex)) + if !opIsK(acs[i].valuerk) { + reg -= 1 + } + } + } +} // }}} + +func compileRegAssignment(context *funcContext, names []string, exprs []ast.Expr, reg int, nvars int, line int) { // {{{ + lennames := len(names) + lenexprs := len(exprs) + namesassigned := 0 + ec := &expcontext{} + + for namesassigned < lennames && namesassigned < lenexprs { + if isVarArgReturnExpr(exprs[namesassigned]) && (lenexprs-namesassigned-1) <= 0 { + + varargopt := nvars - namesassigned + ecupdate(ec, ecVararg, reg, varargopt-1) + compileExpr(context, reg, exprs[namesassigned], ec) + reg += varargopt + namesassigned = lennames + } else { + ecupdate(ec, ecLocal, reg, 0) + compileExpr(context, reg, exprs[namesassigned], ec) + reg += 1 + namesassigned += 1 + } + } + + // extra left names + if lennames > namesassigned { + restleft := lennames - namesassigned - 1 + context.Code.AddLoadNil(reg, reg+restleft, line) + reg += restleft + } + + // extra right exprs + for i := namesassigned; i < lenexprs; i++ { + varargopt := -1 + if i != lenexprs-1 { + varargopt = 0 + } + ecupdate(ec, ecNone, reg, varargopt) + reg += compileExpr(context, reg, exprs[i], ec) + } +} // }}} + +func compileLocalAssignStmt(context *funcContext, stmt *ast.LocalAssignStmt) { // {{{ + reg := context.RegTop() + if len(stmt.Names) == 1 && len(stmt.Exprs) == 1 { + if _, ok := stmt.Exprs[0].(*ast.FunctionExpr); ok { + context.RegisterLocalVar(stmt.Names[0]) + compileRegAssignment(context, stmt.Names, stmt.Exprs, reg, len(stmt.Names), sline(stmt)) + return + } + } + + compileRegAssignment(context, stmt.Names, stmt.Exprs, reg, len(stmt.Names), sline(stmt)) + for _, name := range stmt.Names { + context.RegisterLocalVar(name) + } +} // }}} + +func compileReturnStmt(context *funcContext, stmt *ast.ReturnStmt) { // {{{ + lenexprs := len(stmt.Exprs) + code := context.Code + reg := context.RegTop() + a := reg + lastisvaarg := false + + if lenexprs == 1 { + switch ex := stmt.Exprs[0].(type) { + case *ast.IdentExpr: + if idx := context.FindLocalVar(ex.Value); idx > -1 { + code.AddABC(OP_RETURN, idx, 2, 0, sline(stmt)) + return + } + case *ast.FuncCallExpr: + reg += compileExpr(context, reg, ex, ecnone(-2)) + code.SetOpCode(code.LastPC(), OP_TAILCALL) + code.AddABC(OP_RETURN, a, 0, 0, sline(stmt)) + return + } + } + + for i, expr := range stmt.Exprs { + if i == lenexprs-1 && isVarArgReturnExpr(expr) { + compileExpr(context, reg, expr, ecnone(-2)) + lastisvaarg = true + } else { + reg += compileExpr(context, reg, expr, ecnone(0)) + } + } + count := reg - a + 1 + if lastisvaarg { + count = 0 + } + context.Code.AddABC(OP_RETURN, a, count, 0, sline(stmt)) +} // }}} + +func compileIfStmt(context *funcContext, stmt *ast.IfStmt) { // {{{ + thenlabel := context.NewLabel() + elselabel := context.NewLabel() + endlabel := context.NewLabel() + + compileBranchCondition(context, context.RegTop(), stmt.Condition, thenlabel, elselabel, false) + context.SetLabelPc(thenlabel, context.Code.LastPC()) + compileBlock(context, stmt.Then) + if len(stmt.Else) > 0 { + context.Code.AddASbx(OP_JMP, 0, endlabel, sline(stmt)) + } + context.SetLabelPc(elselabel, context.Code.LastPC()) + if len(stmt.Else) > 0 { + compileBlock(context, stmt.Else) + context.SetLabelPc(endlabel, context.Code.LastPC()) + } + +} // }}} + +func compileBranchCondition(context *funcContext, reg int, expr ast.Expr, thenlabel, elselabel int, hasnextcond bool) { // {{{ + // TODO folding constants? + code := context.Code + flip := 0 + jumplabel := elselabel + if hasnextcond { + flip = 1 + jumplabel = thenlabel + } + + switch ex := expr.(type) { + case *ast.FalseExpr, *ast.NilExpr: + if !hasnextcond { + code.AddASbx(OP_JMP, 0, elselabel, sline(expr)) + return + } + case *ast.TrueExpr, *ast.NumberExpr, *ast.StringExpr: + if !hasnextcond { + return + } + case *ast.UnaryNotOpExpr: + compileBranchCondition(context, reg, ex.Expr, elselabel, thenlabel, !hasnextcond) + return + case *ast.LogicalOpExpr: + switch ex.Operator { + case "and": + nextcondlabel := context.NewLabel() + compileBranchCondition(context, reg, ex.Lhs, nextcondlabel, elselabel, false) + context.SetLabelPc(nextcondlabel, context.Code.LastPC()) + compileBranchCondition(context, reg, ex.Rhs, thenlabel, elselabel, hasnextcond) + case "or": + nextcondlabel := context.NewLabel() + compileBranchCondition(context, reg, ex.Lhs, thenlabel, nextcondlabel, true) + context.SetLabelPc(nextcondlabel, context.Code.LastPC()) + compileBranchCondition(context, reg, ex.Rhs, thenlabel, elselabel, hasnextcond) + } + return + case *ast.RelationalOpExpr: + compileRelationalOpExprAux(context, reg, ex, flip, jumplabel) + return + } + + a := reg + compileExprWithMVPropagation(context, expr, ®, &a) + code.AddABC(OP_TEST, a, 0, 0^flip, sline(expr)) + code.AddASbx(OP_JMP, 0, jumplabel, sline(expr)) +} // }}} + +func compileWhileStmt(context *funcContext, stmt *ast.WhileStmt) { // {{{ + thenlabel := context.NewLabel() + elselabel := context.NewLabel() + condlabel := context.NewLabel() + + context.SetLabelPc(condlabel, context.Code.LastPC()) + compileBranchCondition(context, context.RegTop(), stmt.Condition, thenlabel, elselabel, false) + context.SetLabelPc(thenlabel, context.Code.LastPC()) + context.EnterBlock(elselabel, stmt) + compileChunk(context, stmt.Stmts) + context.CloseUpvalues() + context.Code.AddASbx(OP_JMP, 0, condlabel, eline(stmt)) + context.LeaveBlock() + context.SetLabelPc(elselabel, context.Code.LastPC()) +} // }}} + +func compileRepeatStmt(context *funcContext, stmt *ast.RepeatStmt) { // {{{ + initlabel := context.NewLabel() + thenlabel := context.NewLabel() + elselabel := context.NewLabel() + + context.SetLabelPc(initlabel, context.Code.LastPC()) + context.SetLabelPc(elselabel, context.Code.LastPC()) + context.EnterBlock(thenlabel, stmt) + compileChunk(context, stmt.Stmts) + compileBranchCondition(context, context.RegTop(), stmt.Condition, thenlabel, elselabel, false) + + context.SetLabelPc(thenlabel, context.Code.LastPC()) + n := context.LeaveBlock() + + if n > -1 { + label := context.NewLabel() + context.Code.AddASbx(OP_JMP, 0, label, eline(stmt)) + context.SetLabelPc(elselabel, context.Code.LastPC()) + context.Code.AddABC(OP_CLOSE, n, 0, 0, eline(stmt)) + context.Code.AddASbx(OP_JMP, 0, initlabel, eline(stmt)) + context.SetLabelPc(label, context.Code.LastPC()) + } + +} // }}} + +func compileBreakStmt(context *funcContext, stmt *ast.BreakStmt) { // {{{ + for block := context.Block; block != nil; block = block.Parent { + if label := block.BreakLabel; label != labelNoJump { + if block.RefUpvalue { + context.Code.AddABC(OP_CLOSE, block.Parent.LocalVars.LastIndex(), 0, 0, sline(stmt)) + } + context.Code.AddASbx(OP_JMP, 0, label, sline(stmt)) + return + } + } + raiseCompileError(context, sline(stmt), "no loop to break") +} // }}} + +func compileFuncDefStmt(context *funcContext, stmt *ast.FuncDefStmt) { // {{{ + if stmt.Name.Func == nil { + reg := context.RegTop() + var treg, kreg int + compileExprWithKMVPropagation(context, stmt.Name.Receiver, ®, &treg) + kreg = loadRk(context, ®, stmt.Func, LString(stmt.Name.Method)) + compileExpr(context, reg, stmt.Func, ecfuncdef) + context.Code.AddABC(OP_SETTABLE, treg, kreg, reg, sline(stmt.Name.Receiver)) + } else { + astmt := &ast.AssignStmt{Lhs: []ast.Expr{stmt.Name.Func}, Rhs: []ast.Expr{stmt.Func}} + astmt.SetLine(sline(stmt.Func)) + astmt.SetLastLine(eline(stmt.Func)) + compileAssignStmt(context, astmt) + } +} // }}} + +func compileNumberForStmt(context *funcContext, stmt *ast.NumberForStmt) { // {{{ + code := context.Code + endlabel := context.NewLabel() + ec := &expcontext{} + + context.EnterBlock(endlabel, stmt) + reg := context.RegTop() + rindex := context.RegisterLocalVar("(for index)") + ecupdate(ec, ecLocal, rindex, 0) + compileExpr(context, reg, stmt.Init, ec) + + reg = context.RegTop() + rlimit := context.RegisterLocalVar("(for limit)") + ecupdate(ec, ecLocal, rlimit, 0) + compileExpr(context, reg, stmt.Limit, ec) + + reg = context.RegTop() + rstep := context.RegisterLocalVar("(for step)") + if stmt.Step == nil { + stmt.Step = &ast.NumberExpr{Value: "1"} + stmt.Step.SetLine(sline(stmt.Init)) + } + ecupdate(ec, ecLocal, rstep, 0) + compileExpr(context, reg, stmt.Step, ec) + + code.AddASbx(OP_FORPREP, rindex, 0, sline(stmt)) + + context.RegisterLocalVar(stmt.Name) + + bodypc := code.LastPC() + compileChunk(context, stmt.Stmts) + + context.LeaveBlock() + + flpc := code.LastPC() + code.AddASbx(OP_FORLOOP, rindex, bodypc-(flpc+1), sline(stmt)) + + context.SetLabelPc(endlabel, code.LastPC()) + code.SetSbx(bodypc, flpc-bodypc) + +} // }}} + +func compileGenericForStmt(context *funcContext, stmt *ast.GenericForStmt) { // {{{ + code := context.Code + endlabel := context.NewLabel() + bodylabel := context.NewLabel() + fllabel := context.NewLabel() + nnames := len(stmt.Names) + + context.EnterBlock(endlabel, stmt) + rgen := context.RegisterLocalVar("(for generator)") + context.RegisterLocalVar("(for state)") + context.RegisterLocalVar("(for control)") + + compileRegAssignment(context, stmt.Names, stmt.Exprs, context.RegTop()-3, 3, sline(stmt)) + + code.AddASbx(OP_JMP, 0, fllabel, sline(stmt)) + + for _, name := range stmt.Names { + context.RegisterLocalVar(name) + } + + context.SetLabelPc(bodylabel, code.LastPC()) + compileChunk(context, stmt.Stmts) + + context.LeaveBlock() + + context.SetLabelPc(fllabel, code.LastPC()) + code.AddABC(OP_TFORLOOP, rgen, 0, nnames, sline(stmt)) + code.AddASbx(OP_JMP, 0, bodylabel, sline(stmt)) + + context.SetLabelPc(endlabel, code.LastPC()) +} // }}} + +func compileExpr(context *funcContext, reg int, expr ast.Expr, ec *expcontext) int { // {{{ + code := context.Code + sreg := savereg(ec, reg) + sused := 1 + if sreg < reg { + sused = 0 + } + + switch ex := expr.(type) { + case *ast.StringExpr: + code.AddABx(OP_LOADK, sreg, context.ConstIndex(LString(ex.Value)), sline(ex)) + return sused + case *ast.NumberExpr: + num, err := parseNumber(ex.Value) + if err != nil { + num = LNumber(math.NaN()) + } + code.AddABx(OP_LOADK, sreg, context.ConstIndex(num), sline(ex)) + return sused + case *constLValueExpr: + code.AddABx(OP_LOADK, sreg, context.ConstIndex(ex.Value), sline(ex)) + return sused + case *ast.NilExpr: + code.AddLoadNil(sreg, sreg, sline(ex)) + return sused + case *ast.FalseExpr: + code.AddABC(OP_LOADBOOL, sreg, 0, 0, sline(ex)) + return sused + case *ast.TrueExpr: + code.AddABC(OP_LOADBOOL, sreg, 1, 0, sline(ex)) + return sused + case *ast.IdentExpr: + switch getIdentRefType(context, context, ex) { + case ecGlobal: + code.AddABx(OP_GETGLOBAL, sreg, context.ConstIndex(LString(ex.Value)), sline(ex)) + case ecUpvalue: + code.AddABC(OP_GETUPVAL, sreg, context.Upvalues.RegisterUnique(ex.Value), 0, sline(ex)) + case ecLocal: + b := context.FindLocalVar(ex.Value) + code.AddABC(OP_MOVE, sreg, b, 0, sline(ex)) + } + return sused + case *ast.Comma3Expr: + if context.Proto.IsVarArg == 0 { + raiseCompileError(context, sline(ex), "cannot use '...' outside a vararg function") + } + context.Proto.IsVarArg &= ^VarArgNeedsArg + code.AddABC(OP_VARARG, sreg, 2+ec.varargopt, 0, sline(ex)) + if context.RegTop() > (sreg+2+ec.varargopt) || ec.varargopt < -1 { + return 0 + } + return (sreg + 1 + ec.varargopt) - reg + case *ast.AttrGetExpr: + a := sreg + b := reg + compileExprWithMVPropagation(context, ex.Object, ®, &b) + c := reg + compileExprWithKMVPropagation(context, ex.Key, ®, &c) + opcode := OP_GETTABLE + if _, ok := ex.Key.(*ast.StringExpr); ok { + opcode = OP_GETTABLEKS + } + code.AddABC(opcode, a, b, c, sline(ex)) + return sused + case *ast.TableExpr: + compileTableExpr(context, reg, ex, ec) + return 1 + case *ast.ArithmeticOpExpr: + compileArithmeticOpExpr(context, reg, ex, ec) + return sused + case *ast.StringConcatOpExpr: + compileStringConcatOpExpr(context, reg, ex, ec) + return sused + case *ast.UnaryMinusOpExpr, *ast.UnaryNotOpExpr, *ast.UnaryLenOpExpr: + compileUnaryOpExpr(context, reg, ex, ec) + return sused + case *ast.RelationalOpExpr: + compileRelationalOpExpr(context, reg, ex, ec) + return sused + case *ast.LogicalOpExpr: + compileLogicalOpExpr(context, reg, ex, ec) + return sused + case *ast.FuncCallExpr: + return compileFuncCallExpr(context, reg, ex, ec) + case *ast.FunctionExpr: + childcontext := newFuncContext(context.Proto.SourceName, context) + compileFunctionExpr(childcontext, ex, ec) + protono := len(context.Proto.FunctionPrototypes) + context.Proto.FunctionPrototypes = append(context.Proto.FunctionPrototypes, childcontext.Proto) + code.AddABx(OP_CLOSURE, sreg, protono, sline(ex)) + for _, upvalue := range childcontext.Upvalues.List() { + localidx, block := context.FindLocalVarAndBlock(upvalue.Name) + if localidx > -1 { + code.AddABC(OP_MOVE, 0, localidx, 0, sline(ex)) + block.RefUpvalue = true + } else { + upvalueidx := context.Upvalues.Find(upvalue.Name) + if upvalueidx < 0 { + upvalueidx = context.Upvalues.RegisterUnique(upvalue.Name) + } + code.AddABC(OP_GETUPVAL, 0, upvalueidx, 0, sline(ex)) + } + } + return sused + default: + panic(fmt.Sprintf("expr %v not implemented.", reflect.TypeOf(ex).Elem().Name())) + } + +} // }}} + +func compileExprWithPropagation(context *funcContext, expr ast.Expr, reg *int, save *int, propergator func(int, *int, *int, int)) { // {{{ + reginc := compileExpr(context, *reg, expr, ecnone(0)) + if _, ok := expr.(*ast.LogicalOpExpr); ok { + *save = *reg + *reg = *reg + reginc + } else { + propergator(context.RegTop(), save, reg, reginc) + } +} // }}} + +func compileExprWithKMVPropagation(context *funcContext, expr ast.Expr, reg *int, save *int) { // {{{ + compileExprWithPropagation(context, expr, reg, save, context.Code.PropagateKMV) +} // }}} + +func compileExprWithMVPropagation(context *funcContext, expr ast.Expr, reg *int, save *int) { // {{{ + compileExprWithPropagation(context, expr, reg, save, context.Code.PropagateMV) +} // }}} + +func constFold(exp ast.Expr) ast.Expr { // {{{ + switch expr := exp.(type) { + case *ast.ArithmeticOpExpr: + lvalue, lisconst := lnumberValue(constFold(expr.Lhs)) + rvalue, risconst := lnumberValue(constFold(expr.Rhs)) + if lisconst && risconst { + switch expr.Operator { + case "+": + return &constLValueExpr{Value: lvalue + rvalue} + case "-": + return &constLValueExpr{Value: lvalue - rvalue} + case "*": + return &constLValueExpr{Value: lvalue * rvalue} + case "/": + return &constLValueExpr{Value: lvalue / rvalue} + case "%": + return &constLValueExpr{Value: luaModulo(lvalue, rvalue)} + case "^": + return &constLValueExpr{Value: LNumber(math.Pow(float64(lvalue), float64(rvalue)))} + default: + panic(fmt.Sprintf("unknown binop: %v", expr.Operator)) + } + } else { + return expr + } + case *ast.UnaryMinusOpExpr: + expr.Expr = constFold(expr.Expr) + if value, ok := lnumberValue(expr.Expr); ok { + return &constLValueExpr{Value: LNumber(-value)} + } + return expr + default: + + return exp + } +} // }}} + +func compileFunctionExpr(context *funcContext, funcexpr *ast.FunctionExpr, ec *expcontext) { // {{{ + context.Proto.LineDefined = sline(funcexpr) + context.Proto.LastLineDefined = eline(funcexpr) + if len(funcexpr.ParList.Names) > maxRegisters { + raiseCompileError(context, context.Proto.LineDefined, "register overflow") + } + context.Proto.NumParameters = uint8(len(funcexpr.ParList.Names)) + if ec.ctype == ecMethod { + context.Proto.NumParameters += 1 + context.RegisterLocalVar("self") + } + for _, name := range funcexpr.ParList.Names { + context.RegisterLocalVar(name) + } + if funcexpr.ParList.HasVargs { + if CompatVarArg { + context.Proto.IsVarArg = VarArgHasArg | VarArgNeedsArg + if context.Parent != nil { + context.RegisterLocalVar("arg") + } + } + context.Proto.IsVarArg |= VarArgIsVarArg + } + + compileChunk(context, funcexpr.Stmts) + + context.Code.AddABC(OP_RETURN, 0, 1, 0, eline(funcexpr)) + context.EndScope() + context.Proto.Code = context.Code.List() + context.Proto.DbgSourcePositions = context.Code.PosList() + context.Proto.DbgUpvalues = context.Upvalues.Names() + context.Proto.NumUpvalues = uint8(len(context.Proto.DbgUpvalues)) + for _, clv := range context.Proto.Constants { + sv := "" + if slv, ok := clv.(LString); ok { + sv = string(slv) + } + context.Proto.stringConstants = append(context.Proto.stringConstants, sv) + } + patchCode(context) +} // }}} + +func compileTableExpr(context *funcContext, reg int, ex *ast.TableExpr, ec *expcontext) { // {{{ + code := context.Code + /* + tablereg := savereg(ec, reg) + if tablereg == reg { + reg += 1 + } + */ + tablereg := reg + reg++ + code.AddABC(OP_NEWTABLE, tablereg, 0, 0, sline(ex)) + tablepc := code.LastPC() + regbase := reg + + arraycount := 0 + lastvararg := false + for i, field := range ex.Fields { + islast := i == len(ex.Fields)-1 + if field.Key == nil { + if islast && isVarArgReturnExpr(field.Value) { + reg += compileExpr(context, reg, field.Value, ecnone(-2)) + lastvararg = true + } else { + reg += compileExpr(context, reg, field.Value, ecnone(0)) + arraycount += 1 + } + } else { + regorg := reg + b := reg + compileExprWithKMVPropagation(context, field.Key, ®, &b) + c := reg + compileExprWithKMVPropagation(context, field.Value, ®, &c) + opcode := OP_SETTABLE + if _, ok := field.Key.(*ast.StringExpr); ok { + opcode = OP_SETTABLEKS + } + code.AddABC(opcode, tablereg, b, c, sline(ex)) + reg = regorg + } + flush := arraycount % FieldsPerFlush + if (arraycount != 0 && (flush == 0 || islast)) || lastvararg { + reg = regbase + num := flush + if num == 0 { + num = FieldsPerFlush + } + c := (arraycount-1)/FieldsPerFlush + 1 + b := num + if islast && isVarArgReturnExpr(field.Value) { + b = 0 + } + line := field.Value + if field.Key != nil { + line = field.Key + } + if c > 511 { + c = 0 + } + code.AddABC(OP_SETLIST, tablereg, b, c, sline(line)) + if c == 0 { + code.Add(uint32(c), sline(line)) + } + } + } + code.SetB(tablepc, int2Fb(arraycount)) + code.SetC(tablepc, int2Fb(len(ex.Fields)-arraycount)) + if shouldmove(ec, tablereg) { + code.AddABC(OP_MOVE, ec.reg, tablereg, 0, sline(ex)) + } +} // }}} + +func compileArithmeticOpExpr(context *funcContext, reg int, expr *ast.ArithmeticOpExpr, ec *expcontext) { // {{{ + exp := constFold(expr) + if ex, ok := exp.(*constLValueExpr); ok { + exp.SetLine(sline(expr)) + compileExpr(context, reg, ex, ec) + return + } + expr, _ = exp.(*ast.ArithmeticOpExpr) + a := savereg(ec, reg) + b := reg + compileExprWithKMVPropagation(context, expr.Lhs, ®, &b) + c := reg + compileExprWithKMVPropagation(context, expr.Rhs, ®, &c) + + op := 0 + switch expr.Operator { + case "+": + op = OP_ADD + case "-": + op = OP_SUB + case "*": + op = OP_MUL + case "/": + op = OP_DIV + case "%": + op = OP_MOD + case "^": + op = OP_POW + } + context.Code.AddABC(op, a, b, c, sline(expr)) +} // }}} + +func compileStringConcatOpExpr(context *funcContext, reg int, expr *ast.StringConcatOpExpr, ec *expcontext) { // {{{ + code := context.Code + crange := 1 + for current := expr.Rhs; current != nil; { + if ex, ok := current.(*ast.StringConcatOpExpr); ok { + crange += 1 + current = ex.Rhs + } else { + current = nil + } + } + a := savereg(ec, reg) + basereg := reg + reg += compileExpr(context, reg, expr.Lhs, ecnone(0)) + reg += compileExpr(context, reg, expr.Rhs, ecnone(0)) + for pc := code.LastPC(); pc != 0 && opGetOpCode(code.At(pc)) == OP_CONCAT; pc-- { + code.Pop() + } + code.AddABC(OP_CONCAT, a, basereg, basereg+crange, sline(expr)) +} // }}} + +func compileUnaryOpExpr(context *funcContext, reg int, expr ast.Expr, ec *expcontext) { // {{{ + opcode := 0 + code := context.Code + var operandexpr ast.Expr + switch ex := expr.(type) { + case *ast.UnaryMinusOpExpr: + exp := constFold(ex) + if lvexpr, ok := exp.(*constLValueExpr); ok { + exp.SetLine(sline(expr)) + compileExpr(context, reg, lvexpr, ec) + return + } + ex, _ = exp.(*ast.UnaryMinusOpExpr) + operandexpr = ex.Expr + opcode = OP_UNM + case *ast.UnaryNotOpExpr: + switch ex.Expr.(type) { + case *ast.TrueExpr: + code.AddABC(OP_LOADBOOL, savereg(ec, reg), 0, 0, sline(expr)) + return + case *ast.FalseExpr, *ast.NilExpr: + code.AddABC(OP_LOADBOOL, savereg(ec, reg), 1, 0, sline(expr)) + return + default: + opcode = OP_NOT + operandexpr = ex.Expr + } + case *ast.UnaryLenOpExpr: + opcode = OP_LEN + operandexpr = ex.Expr + } + + a := savereg(ec, reg) + b := reg + compileExprWithMVPropagation(context, operandexpr, ®, &b) + code.AddABC(opcode, a, b, 0, sline(expr)) +} // }}} + +func compileRelationalOpExprAux(context *funcContext, reg int, expr *ast.RelationalOpExpr, flip int, label int) { // {{{ + code := context.Code + b := reg + compileExprWithKMVPropagation(context, expr.Lhs, ®, &b) + c := reg + compileExprWithKMVPropagation(context, expr.Rhs, ®, &c) + switch expr.Operator { + case "<": + code.AddABC(OP_LT, 0^flip, b, c, sline(expr)) + case ">": + code.AddABC(OP_LT, 0^flip, c, b, sline(expr)) + case "<=": + code.AddABC(OP_LE, 0^flip, b, c, sline(expr)) + case ">=": + code.AddABC(OP_LE, 0^flip, c, b, sline(expr)) + case "==": + code.AddABC(OP_EQ, 0^flip, b, c, sline(expr)) + case "~=": + code.AddABC(OP_EQ, 1^flip, b, c, sline(expr)) + } + code.AddASbx(OP_JMP, 0, label, sline(expr)) +} // }}} + +func compileRelationalOpExpr(context *funcContext, reg int, expr *ast.RelationalOpExpr, ec *expcontext) { // {{{ + a := savereg(ec, reg) + code := context.Code + jumplabel := context.NewLabel() + compileRelationalOpExprAux(context, reg, expr, 1, jumplabel) + code.AddABC(OP_LOADBOOL, a, 0, 1, sline(expr)) + context.SetLabelPc(jumplabel, code.LastPC()) + code.AddABC(OP_LOADBOOL, a, 1, 0, sline(expr)) +} // }}} + +func compileLogicalOpExpr(context *funcContext, reg int, expr *ast.LogicalOpExpr, ec *expcontext) { // {{{ + a := savereg(ec, reg) + code := context.Code + endlabel := context.NewLabel() + lb := &lblabels{context.NewLabel(), context.NewLabel(), endlabel, false} + nextcondlabel := context.NewLabel() + if expr.Operator == "and" { + compileLogicalOpExprAux(context, reg, expr.Lhs, ec, nextcondlabel, endlabel, false, lb) + context.SetLabelPc(nextcondlabel, code.LastPC()) + compileLogicalOpExprAux(context, reg, expr.Rhs, ec, endlabel, endlabel, false, lb) + } else { + compileLogicalOpExprAux(context, reg, expr.Lhs, ec, endlabel, nextcondlabel, true, lb) + context.SetLabelPc(nextcondlabel, code.LastPC()) + compileLogicalOpExprAux(context, reg, expr.Rhs, ec, endlabel, endlabel, false, lb) + } + + if lb.b { + context.SetLabelPc(lb.f, code.LastPC()) + code.AddABC(OP_LOADBOOL, a, 0, 1, sline(expr)) + context.SetLabelPc(lb.t, code.LastPC()) + code.AddABC(OP_LOADBOOL, a, 1, 0, sline(expr)) + } + + lastinst := code.Last() + if opGetOpCode(lastinst) == OP_JMP && opGetArgSbx(lastinst) == endlabel { + code.Pop() + } + + context.SetLabelPc(endlabel, code.LastPC()) +} // }}} + +func compileLogicalOpExprAux(context *funcContext, reg int, expr ast.Expr, ec *expcontext, thenlabel, elselabel int, hasnextcond bool, lb *lblabels) { // {{{ + // TODO folding constants? + code := context.Code + flip := 0 + jumplabel := elselabel + if hasnextcond { + flip = 1 + jumplabel = thenlabel + } + + switch ex := expr.(type) { + case *ast.FalseExpr: + if elselabel == lb.e { + code.AddASbx(OP_JMP, 0, lb.f, sline(expr)) + lb.b = true + } else { + code.AddASbx(OP_JMP, 0, elselabel, sline(expr)) + } + return + case *ast.NilExpr: + if elselabel == lb.e { + compileExpr(context, reg, expr, ec) + code.AddASbx(OP_JMP, 0, lb.e, sline(expr)) + } else { + code.AddASbx(OP_JMP, 0, elselabel, sline(expr)) + } + return + case *ast.TrueExpr: + if thenlabel == lb.e { + code.AddASbx(OP_JMP, 0, lb.t, sline(expr)) + lb.b = true + } else { + code.AddASbx(OP_JMP, 0, thenlabel, sline(expr)) + } + return + case *ast.NumberExpr, *ast.StringExpr: + if thenlabel == lb.e { + compileExpr(context, reg, expr, ec) + code.AddASbx(OP_JMP, 0, lb.e, sline(expr)) + } else { + code.AddASbx(OP_JMP, 0, thenlabel, sline(expr)) + } + return + case *ast.LogicalOpExpr: + switch ex.Operator { + case "and": + nextcondlabel := context.NewLabel() + compileLogicalOpExprAux(context, reg, ex.Lhs, ec, nextcondlabel, elselabel, false, lb) + context.SetLabelPc(nextcondlabel, context.Code.LastPC()) + compileLogicalOpExprAux(context, reg, ex.Rhs, ec, thenlabel, elselabel, hasnextcond, lb) + case "or": + nextcondlabel := context.NewLabel() + compileLogicalOpExprAux(context, reg, ex.Lhs, ec, thenlabel, nextcondlabel, true, lb) + context.SetLabelPc(nextcondlabel, context.Code.LastPC()) + compileLogicalOpExprAux(context, reg, ex.Rhs, ec, thenlabel, elselabel, hasnextcond, lb) + } + return + case *ast.RelationalOpExpr: + if thenlabel == elselabel { + flip ^= 1 + jumplabel = lb.t + lb.b = true + } else if thenlabel == lb.e { + jumplabel = lb.t + lb.b = true + } else if elselabel == lb.e { + jumplabel = lb.f + lb.b = true + } + compileRelationalOpExprAux(context, reg, ex, flip, jumplabel) + return + } + + a := reg + sreg := savereg(ec, a) + 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 { + context.Code.SetA(context.Code.LastPC(), sreg) + } else { + context.Code.AddABC(OP_MOVE, sreg, a, 0, sline(expr)) + } + } else { + reg += compileExpr(context, reg, expr, ecnone(0)) + if sreg == a { + code.AddABC(OP_TEST, a, 0, 0^flip, sline(expr)) + } else { + code.AddABC(OP_TESTSET, sreg, a, 0^flip, sline(expr)) + } + } + code.AddASbx(OP_JMP, 0, jumplabel, sline(expr)) +} // }}} + +func compileFuncCallExpr(context *funcContext, reg int, expr *ast.FuncCallExpr, ec *expcontext) int { // {{{ + funcreg := reg + if ec.ctype == ecLocal && ec.reg == (int(context.Proto.NumParameters)-1) { + funcreg = ec.reg + reg = ec.reg + } + argc := len(expr.Args) + islastvararg := false + name := "(anonymous)" + + if expr.Func != nil { // hoge.func() + reg += compileExpr(context, reg, expr.Func, ecnone(0)) + name = getExprName(context, expr.Func) + } else { // hoge:method() + b := reg + compileExprWithMVPropagation(context, expr.Receiver, ®, &b) + c := loadRk(context, ®, expr, LString(expr.Method)) + context.Code.AddABC(OP_SELF, funcreg, b, c, sline(expr)) + // increments a register for an implicit "self" + reg = b + 1 + reg2 := funcreg + 2 + if reg2 > reg { + reg = reg2 + } + argc += 1 + name = string(expr.Method) + } + + for i, ar := range expr.Args { + islastvararg = (i == len(expr.Args)-1) && isVarArgReturnExpr(ar) + if islastvararg { + compileExpr(context, reg, ar, ecnone(-2)) + } else { + reg += compileExpr(context, reg, ar, ecnone(0)) + } + } + b := argc + 1 + if islastvararg { + b = 0 + } + context.Code.AddABC(OP_CALL, funcreg, b, ec.varargopt+2, sline(expr)) + context.Proto.DbgCalls = append(context.Proto.DbgCalls, DbgCall{Pc: context.Code.LastPC(), Name: name}) + + if ec.varargopt == 0 && shouldmove(ec, funcreg) { + context.Code.AddABC(OP_MOVE, ec.reg, funcreg, 0, sline(expr)) + return 1 + } + if context.RegTop() > (funcreg+2+ec.varargopt) || ec.varargopt < -1 { + return 0 + } + return ec.varargopt + 1 +} // }}} + +func loadRk(context *funcContext, reg *int, expr ast.Expr, cnst LValue) int { // {{{ + cindex := context.ConstIndex(cnst) + if cindex <= opMaxIndexRk { + return opRkAsk(cindex) + } else { + ret := *reg + *reg++ + context.Code.AddABx(OP_LOADK, ret, cindex, sline(expr)) + return ret + } +} // }}} + +func getIdentRefType(context *funcContext, current *funcContext, expr *ast.IdentExpr) expContextType { // {{{ + if current == nil { + return ecGlobal + } else if current.FindLocalVar(expr.Value) > -1 { + if current == context { + return ecLocal + } + return ecUpvalue + } + return getIdentRefType(context, current.Parent, expr) +} // }}} + +func getExprName(context *funcContext, expr ast.Expr) string { // {{{ + switch ex := expr.(type) { + case *ast.IdentExpr: + return ex.Value + case *ast.AttrGetExpr: + switch kex := ex.Key.(type) { + case *ast.StringExpr: + return kex.Value + } + return "?" + } + return "?" +} // }}} + +func patchCode(context *funcContext) { // {{{ + maxreg := 1 + if np := int(context.Proto.NumParameters); np > 1 { + maxreg = np + } + moven := 0 + code := context.Code.List() + for pc := 0; pc < len(code); pc++ { + inst := code[pc] + curop := opGetOpCode(inst) + switch curop { + case OP_CLOSURE: + pc += int(context.Proto.FunctionPrototypes[opGetArgBx(inst)].NumUpvalues) + moven = 0 + continue + case OP_SETGLOBAL, OP_SETUPVAL, OP_EQ, OP_LT, OP_LE, OP_TEST, + OP_TAILCALL, OP_RETURN, OP_FORPREP, OP_FORLOOP, OP_TFORLOOP, + OP_SETLIST, OP_CLOSE: + /* nothing to do */ + case OP_CALL: + if reg := opGetArgA(inst) + opGetArgC(inst) - 2; reg > maxreg { + maxreg = reg + } + case OP_VARARG: + if reg := opGetArgA(inst) + opGetArgB(inst) - 1; reg > maxreg { + maxreg = reg + } + case OP_SELF: + if reg := opGetArgA(inst) + 1; reg > maxreg { + maxreg = reg + } + case OP_LOADNIL: + if reg := opGetArgB(inst); reg > maxreg { + maxreg = reg + } + case OP_JMP: // jump to jump optimization + distance := 0 + count := 0 // avoiding infinite loops + for jmp := inst; opGetOpCode(jmp) == OP_JMP && count < 5; jmp = context.Code.At(pc + distance + 1) { + d := context.GetLabelPc(opGetArgSbx(jmp)) - pc + if d > opMaxArgSbx { + if distance == 0 { + raiseCompileError(context, context.Proto.LineDefined, "too long to jump.") + } + break + } + distance = d + count++ + } + if distance == 0 { + context.Code.SetOpCode(pc, OP_NOP) + } else { + context.Code.SetSbx(pc, distance) + } + default: + if reg := opGetArgA(inst); reg > maxreg { + maxreg = reg + } + } + + // bulk move optimization(reducing op dipatch costs) + if curop == OP_MOVE { + moven++ + } else { + if moven > 1 { + context.Code.SetOpCode(pc-moven, OP_MOVEN) + context.Code.SetC(pc-moven, intMin(moven-1, opMaxArgsC)) + } + moven = 0 + } + } + maxreg++ + if maxreg > maxRegisters { + raiseCompileError(context, context.Proto.LineDefined, "register overflow(too many local variables)") + } + context.Proto.NumUsedRegisters = uint8(maxreg) +} // }}} + +func Compile(chunk []ast.Stmt, name string) (proto *FunctionProto, err error) { // {{{ + defer func() { + if rcv := recover(); rcv != nil { + if _, ok := rcv.(*CompileError); ok { + err = rcv.(error) + } else { + panic(rcv) + } + } + }() + err = nil + parlist := &ast.ParList{HasVargs: true, Names: []string{}} + funcexpr := &ast.FunctionExpr{ParList: parlist, Stmts: chunk} + context := newFuncContext(name, nil) + compileFunctionExpr(context, funcexpr, ecnone(0)) + proto = context.Proto + return +} // }}} diff --git a/vendor/github.com/yuin/gopher-lua/config.go b/vendor/github.com/yuin/gopher-lua/config.go new file mode 100644 index 00000000000..f58b59393a5 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/config.go @@ -0,0 +1,36 @@ +package lua + +import ( + "os" +) + +var CompatVarArg = true +var FieldsPerFlush = 50 +var RegistrySize = 256 * 20 +var RegistryGrowStep = 32 +var CallStackSize = 256 +var MaxTableGetLoop = 100 +var MaxArrayIndex = 67108864 + +type LNumber float64 + +const LNumberBit = 64 +const LNumberScanFormat = "%f" +const LuaVersion = "Lua 5.1" + +var LuaPath = "LUA_PATH" +var LuaLDir string +var LuaPathDefault string +var LuaOS string + +func init() { + if os.PathSeparator == '/' { // unix-like + LuaOS = "unix" + LuaLDir = "/usr/local/share/lua/5.1" + LuaPathDefault = "./?.lua;" + LuaLDir + "/?.lua;" + LuaLDir + "/?/init.lua" + } else { // windows + LuaOS = "windows" + LuaLDir = "!\\lua" + LuaPathDefault = ".\\?.lua;" + LuaLDir + "\\?.lua;" + LuaLDir + "\\?\\init.lua" + } +} diff --git a/vendor/github.com/yuin/gopher-lua/coroutinelib.go b/vendor/github.com/yuin/gopher-lua/coroutinelib.go new file mode 100644 index 00000000000..d42c41a1dfc --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/coroutinelib.go @@ -0,0 +1,112 @@ +package lua + +func OpenCoroutine(L *LState) int { + // TODO: Tie module name to contents of linit.go? + mod := L.RegisterModule(CoroutineLibName, coFuncs) + L.Push(mod) + return 1 +} + +var coFuncs = map[string]LGFunction{ + "create": coCreate, + "yield": coYield, + "resume": coResume, + "running": coRunning, + "status": coStatus, + "wrap": coWrap, +} + +func coCreate(L *LState) int { + fn := L.CheckFunction(1) + newthread, _ := L.NewThread() + base := 0 + newthread.stack.Push(callFrame{ + Fn: fn, + Pc: 0, + Base: base, + LocalBase: base + 1, + ReturnBase: base, + NArgs: 0, + NRet: MultRet, + Parent: nil, + TailCall: 0, + }) + L.Push(newthread) + return 1 +} + +func coYield(L *LState) int { + return -1 +} + +func coResume(L *LState) int { + th := L.CheckThread(1) + if L.G.CurrentThread == th { + msg := "can not resume a running thread" + if th.wrapped { + L.RaiseError(msg) + return 0 + } + L.Push(LFalse) + L.Push(LString(msg)) + return 2 + } + if th.Dead { + msg := "can not resume a dead thread" + if th.wrapped { + L.RaiseError(msg) + return 0 + } + L.Push(LFalse) + L.Push(LString(msg)) + return 2 + } + th.Parent = L + L.G.CurrentThread = th + if !th.isStarted() { + cf := th.stack.Last() + th.currentFrame = cf + th.SetTop(0) + nargs := L.GetTop() - 1 + L.XMoveTo(th, nargs) + cf.NArgs = nargs + th.initCallFrame(cf) + th.Panic = panicWithoutTraceback + } else { + nargs := L.GetTop() - 1 + L.XMoveTo(th, nargs) + } + top := L.GetTop() + threadRun(th) + return L.GetTop() - top +} + +func coRunning(L *LState) int { + if L.G.MainThread == L { + L.Push(LNil) + return 1 + } + L.Push(L.G.CurrentThread) + return 1 +} + +func coStatus(L *LState) int { + L.Push(LString(L.Status(L.CheckThread(1)))) + return 1 +} + +func wrapaux(L *LState) int { + L.Insert(L.ToThread(UpvalueIndex(1)), 1) + return coResume(L) +} + +func coWrap(L *LState) int { + coCreate(L) + L.CheckThread(L.GetTop()).wrapped = true + v := L.Get(L.GetTop()) + L.Pop(1) + L.Push(L.NewClosure(wrapaux, v)) + return 1 +} + +// diff --git a/vendor/github.com/yuin/gopher-lua/debuglib.go b/vendor/github.com/yuin/gopher-lua/debuglib.go new file mode 100644 index 00000000000..41f883f1d06 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/debuglib.go @@ -0,0 +1,173 @@ +package lua + +import ( + "fmt" + "strings" +) + +func OpenDebug(L *LState) int { + dbgmod := L.RegisterModule(DebugLibName, debugFuncs) + L.Push(dbgmod) + return 1 +} + +var debugFuncs = map[string]LGFunction{ + "getfenv": debugGetFEnv, + "getinfo": debugGetInfo, + "getlocal": debugGetLocal, + "getmetatable": debugGetMetatable, + "getupvalue": debugGetUpvalue, + "setfenv": debugSetFEnv, + "setlocal": debugSetLocal, + "setmetatable": debugSetMetatable, + "setupvalue": debugSetUpvalue, + "traceback": debugTraceback, +} + +func debugGetFEnv(L *LState) int { + L.Push(L.GetFEnv(L.CheckAny(1))) + return 1 +} + +func debugGetInfo(L *LState) int { + L.CheckTypes(1, LTFunction, LTNumber) + arg1 := L.Get(1) + what := L.OptString(2, "Slunf") + var dbg *Debug + var fn LValue + var err error + var ok bool + switch lv := arg1.(type) { + case *LFunction: + dbg = &Debug{} + fn, err = L.GetInfo(">"+what, dbg, lv) + case LNumber: + dbg, ok = L.GetStack(int(lv)) + if !ok { + L.Push(LNil) + return 1 + } + fn, err = L.GetInfo(what, dbg, LNil) + } + + if err != nil { + L.Push(LNil) + return 1 + } + tbl := L.NewTable() + if len(dbg.Name) > 0 { + tbl.RawSetString("name", LString(dbg.Name)) + } else { + tbl.RawSetString("name", LNil) + } + tbl.RawSetString("what", LString(dbg.What)) + tbl.RawSetString("source", LString(dbg.Source)) + tbl.RawSetString("currentline", LNumber(dbg.CurrentLine)) + tbl.RawSetString("nups", LNumber(dbg.NUpvalues)) + tbl.RawSetString("linedefined", LNumber(dbg.LineDefined)) + tbl.RawSetString("lastlinedefined", LNumber(dbg.LastLineDefined)) + tbl.RawSetString("func", fn) + L.Push(tbl) + return 1 +} + +func debugGetLocal(L *LState) int { + level := L.CheckInt(1) + idx := L.CheckInt(2) + dbg, ok := L.GetStack(level) + if !ok { + L.ArgError(1, "level out of range") + } + name, value := L.GetLocal(dbg, idx) + if len(name) > 0 { + L.Push(LString(name)) + L.Push(value) + return 2 + } + L.Push(LNil) + return 1 +} + +func debugGetMetatable(L *LState) int { + L.Push(L.GetMetatable(L.CheckAny(1))) + return 1 +} + +func debugGetUpvalue(L *LState) int { + fn := L.CheckFunction(1) + idx := L.CheckInt(2) + name, value := L.GetUpvalue(fn, idx) + if len(name) > 0 { + L.Push(LString(name)) + L.Push(value) + return 2 + } + L.Push(LNil) + return 1 +} + +func debugSetFEnv(L *LState) int { + L.SetFEnv(L.CheckAny(1), L.CheckAny(2)) + return 0 +} + +func debugSetLocal(L *LState) int { + level := L.CheckInt(1) + idx := L.CheckInt(2) + value := L.CheckAny(3) + dbg, ok := L.GetStack(level) + if !ok { + L.ArgError(1, "level out of range") + } + name := L.SetLocal(dbg, idx, value) + if len(name) > 0 { + L.Push(LString(name)) + } else { + L.Push(LNil) + } + return 1 +} + +func debugSetMetatable(L *LState) int { + L.CheckTypes(2, LTNil, LTTable) + obj := L.Get(1) + mt := L.Get(2) + L.SetMetatable(obj, mt) + L.SetTop(1) + return 1 +} + +func debugSetUpvalue(L *LState) int { + fn := L.CheckFunction(1) + idx := L.CheckInt(2) + value := L.CheckAny(3) + name := L.SetUpvalue(fn, idx, value) + if len(name) > 0 { + L.Push(LString(name)) + } else { + L.Push(LNil) + } + return 1 +} + +func debugTraceback(L *LState) int { + msg := "" + level := L.OptInt(2, 1) + ls := L + if L.GetTop() > 0 { + if s, ok := L.Get(1).assertString(); ok { + msg = s + } + if l, ok := L.Get(1).(*LState); ok { + ls = l + msg = "" + } + } + + traceback := strings.TrimSpace(ls.stackTrace(level)) + if len(msg) > 0 { + traceback = fmt.Sprintf("%s\n%s", msg, traceback) + } + L.Push(LString(traceback)) + return 1 +} diff --git a/vendor/github.com/yuin/gopher-lua/function.go b/vendor/github.com/yuin/gopher-lua/function.go new file mode 100644 index 00000000000..169e5407cac --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/function.go @@ -0,0 +1,193 @@ +package lua + +import ( + "fmt" + "strings" +) + +const ( + VarArgHasArg uint8 = 1 + VarArgIsVarArg uint8 = 2 + VarArgNeedsArg uint8 = 4 +) + +type DbgLocalInfo struct { + Name string + StartPc int + EndPc int +} + +type DbgCall struct { + Name string + Pc int +} + +type FunctionProto struct { + SourceName string + LineDefined int + LastLineDefined int + NumUpvalues uint8 + NumParameters uint8 + IsVarArg uint8 + NumUsedRegisters uint8 + Code []uint32 + Constants []LValue + FunctionPrototypes []*FunctionProto + + DbgSourcePositions []int + DbgLocals []*DbgLocalInfo + DbgCalls []DbgCall + DbgUpvalues []string + + stringConstants []string +} + +/* Upvalue {{{ */ + +type Upvalue struct { + next *Upvalue + reg *registry + index int + value LValue + closed bool +} + +func (uv *Upvalue) Value() LValue { + //if uv.IsClosed() { + if uv.closed || uv.reg == nil { + return uv.value + } + //return uv.reg.Get(uv.index) + return uv.reg.array[uv.index] +} + +func (uv *Upvalue) SetValue(value LValue) { + if uv.IsClosed() { + uv.value = value + } else { + uv.reg.Set(uv.index, value) + } +} + +func (uv *Upvalue) Close() { + value := uv.Value() + uv.closed = true + uv.value = value +} + +func (uv *Upvalue) IsClosed() bool { + return uv.closed || uv.reg == nil +} + +func UpvalueIndex(i int) int { + return GlobalsIndex - i +} + +/* }}} */ + +/* FunctionProto {{{ */ + +func newFunctionProto(name string) *FunctionProto { + return &FunctionProto{ + SourceName: name, + LineDefined: 0, + LastLineDefined: 0, + NumUpvalues: 0, + NumParameters: 0, + IsVarArg: 0, + NumUsedRegisters: 2, + Code: make([]uint32, 0, 128), + Constants: make([]LValue, 0, 32), + FunctionPrototypes: make([]*FunctionProto, 0, 16), + + DbgSourcePositions: make([]int, 0, 128), + DbgLocals: make([]*DbgLocalInfo, 0, 16), + DbgCalls: make([]DbgCall, 0, 128), + DbgUpvalues: make([]string, 0, 16), + + stringConstants: make([]string, 0, 32), + } +} + +func (fp *FunctionProto) String() string { + return fp.str(1, 0) +} + +func (fp *FunctionProto) str(level int, count int) string { + indent := strings.Repeat(" ", level-1) + buf := []string{} + buf = append(buf, fmt.Sprintf("%v; function [%v] definition (level %v)\n", + indent, count, level)) + buf = append(buf, fmt.Sprintf("%v; %v upvalues, %v params, %v stacks\n", + indent, fp.NumUpvalues, fp.NumParameters, fp.NumUsedRegisters)) + for reg, linfo := range fp.DbgLocals { + buf = append(buf, fmt.Sprintf("%v.local %v ; %v\n", indent, linfo.Name, reg)) + } + for reg, upvalue := range fp.DbgUpvalues { + buf = append(buf, fmt.Sprintf("%v.upvalue %v ; %v\n", indent, upvalue, reg)) + } + for reg, conzt := range fp.Constants { + buf = append(buf, fmt.Sprintf("%v.const %v ; %v\n", indent, conzt.String(), reg)) + } + buf = append(buf, "\n") + + protono := 0 + for no, code := range fp.Code { + inst := opGetOpCode(code) + if inst == OP_CLOSURE { + buf = append(buf, "\n") + buf = append(buf, fp.FunctionPrototypes[protono].str(level+1, protono)) + buf = append(buf, "\n") + protono++ + } + buf = append(buf, fmt.Sprintf("%v[%03d] %v (line:%v)\n", + indent, no+1, opToString(code), fp.DbgSourcePositions[no])) + + } + buf = append(buf, fmt.Sprintf("%v; end of function\n", indent)) + return strings.Join(buf, "") +} + +/* }}} */ + +/* LFunction {{{ */ + +func newLFunctionL(proto *FunctionProto, env *LTable, nupvalue int) *LFunction { + return &LFunction{ + IsG: false, + Env: env, + + Proto: proto, + GFunction: nil, + Upvalues: make([]*Upvalue, nupvalue), + } +} + +func newLFunctionG(gfunc LGFunction, env *LTable, nupvalue int) *LFunction { + return &LFunction{ + IsG: true, + Env: env, + + Proto: nil, + GFunction: gfunc, + Upvalues: make([]*Upvalue, nupvalue), + } +} + +func (fn *LFunction) LocalName(regno, pc int) (string, bool) { + if fn.IsG { + return "", false + } + p := fn.Proto + for i := 0; i < len(p.DbgLocals) && p.DbgLocals[i].StartPc < pc; i++ { + if pc < p.DbgLocals[i].EndPc { + regno-- + if regno == 0 { + return p.DbgLocals[i].Name, true + } + } + } + return "", false +} + +/* }}} */ diff --git a/vendor/github.com/yuin/gopher-lua/go.mod b/vendor/github.com/yuin/gopher-lua/go.mod new file mode 100644 index 00000000000..a94de6e2036 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/go.mod @@ -0,0 +1,10 @@ +module github.com/yuin/gopher-lua + +go 1.14 + +require ( + github.com/chzyer/logex v1.1.10 // indirect + github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e + github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 // indirect + golang.org/x/sys v0.0.0-20190204203706-41f3e6584952 // indirect +) diff --git a/vendor/github.com/yuin/gopher-lua/go.sum b/vendor/github.com/yuin/gopher-lua/go.sum new file mode 100644 index 00000000000..ca60bd9c3df --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/go.sum @@ -0,0 +1,8 @@ +github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +golang.org/x/sys v0.0.0-20190204203706-41f3e6584952 h1:FDfvYgoVsA7TTZSbgiqjAbfPbK47CNHdWl3h/PJtii0= +golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= diff --git a/vendor/github.com/yuin/gopher-lua/iolib.go b/vendor/github.com/yuin/gopher-lua/iolib.go new file mode 100644 index 00000000000..4a86f89362d --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/iolib.go @@ -0,0 +1,746 @@ +package lua + +import ( + "bufio" + "errors" + "fmt" + "io" + "io/ioutil" + "os" + "os/exec" + "syscall" +) + +var ioFuncs = map[string]LGFunction{ + "close": ioClose, + "flush": ioFlush, + "lines": ioLines, + "input": ioInput, + "output": ioOutput, + "open": ioOpenFile, + "popen": ioPopen, + "read": ioRead, + "type": ioType, + "tmpfile": ioTmpFile, + "write": ioWrite, +} + +const lFileClass = "FILE*" + +type lFile struct { + fp *os.File + pp *exec.Cmd + writer io.Writer + reader *bufio.Reader + stdout io.ReadCloser + closed bool +} + +type lFileType int + +const ( + lFileFile lFileType = iota + lFileProcess +) + +const fileDefOutIndex = 1 +const fileDefInIndex = 2 +const fileDefaultWriteBuffer = 4096 +const fileDefaultReadBuffer = 4096 + +func checkFile(L *LState) *lFile { + ud := L.CheckUserData(1) + if file, ok := ud.Value.(*lFile); ok { + return file + } + L.ArgError(1, "file expected") + return nil +} + +func errorIfFileIsClosed(L *LState, file *lFile) { + if file.closed { + L.ArgError(1, "file is closed") + } +} + +func newFile(L *LState, file *os.File, path string, flag int, perm os.FileMode, writable, readable bool) (*LUserData, error) { + ud := L.NewUserData() + var err error + if file == nil { + file, err = os.OpenFile(path, flag, perm) + if err != nil { + return nil, err + } + } + lfile := &lFile{fp: file, pp: nil, writer: nil, reader: nil, stdout: nil, closed: false} + ud.Value = lfile + if writable { + lfile.writer = file + } + if readable { + lfile.reader = bufio.NewReaderSize(file, fileDefaultReadBuffer) + } + L.SetMetatable(ud, L.GetTypeMetatable(lFileClass)) + return ud, nil +} + +func newProcess(L *LState, cmd string, writable, readable bool) (*LUserData, error) { + ud := L.NewUserData() + c, args := popenArgs(cmd) + pp := exec.Command(c, args...) + lfile := &lFile{fp: nil, pp: pp, writer: nil, reader: nil, stdout: nil, closed: false} + ud.Value = lfile + + var err error + if writable { + lfile.writer, err = pp.StdinPipe() + } + if readable { + lfile.stdout, err = pp.StdoutPipe() + lfile.reader = bufio.NewReaderSize(lfile.stdout, fileDefaultReadBuffer) + } + if err != nil { + return nil, err + } + err = pp.Start() + if err != nil { + return nil, err + } + + L.SetMetatable(ud, L.GetTypeMetatable(lFileClass)) + return ud, nil +} + +func (file *lFile) Type() lFileType { + if file.fp == nil { + return lFileProcess + } + return lFileFile +} + +func (file *lFile) Name() string { + switch file.Type() { + case lFileFile: + return fmt.Sprintf("file %s", file.fp.Name()) + case lFileProcess: + return fmt.Sprintf("process %s", file.pp.Path) + } + return "" +} + +func (file *lFile) AbandonReadBuffer() error { + if file.Type() == lFileFile && file.reader != nil { + _, err := file.fp.Seek(-int64(file.reader.Buffered()), 1) + if err != nil { + return err + } + file.reader = bufio.NewReaderSize(file.fp, fileDefaultReadBuffer) + } + return nil +} + +func fileDefOut(L *LState) *LUserData { + return L.Get(UpvalueIndex(1)).(*LTable).RawGetInt(fileDefOutIndex).(*LUserData) +} + +func fileDefIn(L *LState) *LUserData { + return L.Get(UpvalueIndex(1)).(*LTable).RawGetInt(fileDefInIndex).(*LUserData) +} + +func fileIsWritable(L *LState, file *lFile) int { + if file.writer == nil { + L.Push(LNil) + L.Push(LString(fmt.Sprintf("%s is opened for only reading.", file.Name()))) + L.Push(LNumber(1)) // C-Lua compatibility: Original Lua pushes errno to the stack + return 3 + } + return 0 +} + +func fileIsReadable(L *LState, file *lFile) int { + if file.reader == nil { + L.Push(LNil) + L.Push(LString(fmt.Sprintf("%s is opened for only writing.", file.Name()))) + L.Push(LNumber(1)) // C-Lua compatibility: Original Lua pushes errno to the stack + return 3 + } + return 0 +} + +var stdFiles = []struct { + name string + file *os.File + writable bool + readable bool +}{ + {"stdout", os.Stdout, true, false}, + {"stdin", os.Stdin, false, true}, + {"stderr", os.Stderr, true, false}, +} + +func OpenIo(L *LState) int { + mod := L.RegisterModule(IoLibName, map[string]LGFunction{}).(*LTable) + mt := L.NewTypeMetatable(lFileClass) + mt.RawSetString("__index", mt) + L.SetFuncs(mt, fileMethods) + mt.RawSetString("lines", L.NewClosure(fileLines, L.NewFunction(fileLinesIter))) + + for _, finfo := range stdFiles { + file, _ := newFile(L, finfo.file, "", 0, os.FileMode(0), finfo.writable, finfo.readable) + mod.RawSetString(finfo.name, file) + } + uv := L.CreateTable(2, 0) + uv.RawSetInt(fileDefOutIndex, mod.RawGetString("stdout")) + uv.RawSetInt(fileDefInIndex, mod.RawGetString("stdin")) + for name, fn := range ioFuncs { + mod.RawSetString(name, L.NewClosure(fn, uv)) + } + mod.RawSetString("lines", L.NewClosure(ioLines, uv, L.NewClosure(ioLinesIter, uv))) + // Modifications are being made in-place rather than returned? + L.Push(mod) + return 1 +} + +var fileMethods = map[string]LGFunction{ + "__tostring": fileToString, + "write": fileWrite, + "close": fileClose, + "flush": fileFlush, + "lines": fileLines, + "read": fileRead, + "seek": fileSeek, + "setvbuf": fileSetVBuf, +} + +func fileToString(L *LState) int { + file := checkFile(L) + if file.Type() == lFileFile { + if file.closed { + L.Push(LString("file (closed)")) + } else { + L.Push(LString("file")) + } + } else { + if file.closed { + L.Push(LString("process (closed)")) + } else { + L.Push(LString("process")) + } + } + return 1 +} + +func fileWriteAux(L *LState, file *lFile, idx int) int { + if n := fileIsWritable(L, file); n != 0 { + return n + } + errorIfFileIsClosed(L, file) + top := L.GetTop() + out := file.writer + var err error + for i := idx; i <= top; i++ { + L.CheckTypes(i, LTNumber, LTString) + s := LVAsString(L.Get(i)) + if _, err = out.Write(unsafeFastStringToReadOnlyBytes(s)); err != nil { + goto errreturn + } + } + + file.AbandonReadBuffer() + L.Push(LTrue) + return 1 +errreturn: + + file.AbandonReadBuffer() + L.Push(LNil) + L.Push(LString(err.Error())) + L.Push(LNumber(1)) // C-Lua compatibility: Original Lua pushes errno to the stack + return 3 +} + +func fileCloseAux(L *LState, file *lFile) int { + file.closed = true + var err error + if file.writer != nil { + if bwriter, ok := file.writer.(*bufio.Writer); ok { + if err = bwriter.Flush(); err != nil { + goto errreturn + } + } + } + file.AbandonReadBuffer() + + switch file.Type() { + case lFileFile: + if err = file.fp.Close(); err != nil { + goto errreturn + } + L.Push(LTrue) + return 1 + case lFileProcess: + if file.stdout != nil { + file.stdout.Close() // ignore errors + } + err = file.pp.Wait() + var exitStatus int // Initialised to zero value = 0 + if err != nil { + if e2, ok := err.(*exec.ExitError); ok { + if s, ok := e2.Sys().(syscall.WaitStatus); ok { + exitStatus = s.ExitStatus() + } else { + err = errors.New("Unimplemented for system where exec.ExitError.Sys() is not syscall.WaitStatus.") + } + } + } else { + exitStatus = 0 + } + L.Push(LNumber(exitStatus)) + return 1 + } + +errreturn: + L.RaiseError(err.Error()) + return 0 +} + +func fileFlushAux(L *LState, file *lFile) int { + if n := fileIsWritable(L, file); n != 0 { + return n + } + errorIfFileIsClosed(L, file) + + if bwriter, ok := file.writer.(*bufio.Writer); ok { + if err := bwriter.Flush(); err != nil { + L.Push(LNil) + L.Push(LString(err.Error())) + return 2 + } + } + L.Push(LTrue) + return 1 +} + +func fileReadAux(L *LState, file *lFile, idx int) int { + if n := fileIsReadable(L, file); n != 0 { + return n + } + errorIfFileIsClosed(L, file) + if L.GetTop() == idx-1 { + L.Push(LString("*l")) + } + var err error + top := L.GetTop() + for i := idx; i <= top; i++ { + switch lv := L.Get(i).(type) { + case LNumber: + size := int64(lv) + if size == 0 { + _, err = file.reader.ReadByte() + if err == io.EOF { + L.Push(LNil) + goto normalreturn + } + file.reader.UnreadByte() + } + var buf []byte + var iseof bool + buf, err, iseof = readBufioSize(file.reader, size) + if iseof { + L.Push(LNil) + goto normalreturn + } + if err != nil { + goto errreturn + } + L.Push(LString(string(buf))) + case LString: + options := L.CheckString(i) + if len(options) > 0 && options[0] != '*' { + L.ArgError(2, "invalid options:"+options) + } + for _, opt := range options[1:] { + switch opt { + case 'n': + var v LNumber + _, err = fmt.Fscanf(file.reader, LNumberScanFormat, &v) + if err == io.EOF { + L.Push(LNil) + goto normalreturn + } + if err != nil { + goto errreturn + } + L.Push(v) + case 'a': + var buf []byte + buf, err = ioutil.ReadAll(file.reader) + if err == io.EOF { + L.Push(emptyLString) + goto normalreturn + } + if err != nil { + goto errreturn + } + L.Push(LString(string(buf))) + case 'l': + var buf []byte + var iseof bool + buf, err, iseof = readBufioLine(file.reader) + if iseof { + L.Push(LNil) + goto normalreturn + } + if err != nil { + goto errreturn + } + L.Push(LString(string(buf))) + default: + L.ArgError(2, "invalid options:"+string(opt)) + } + } + } + } +normalreturn: + return L.GetTop() - top + +errreturn: + L.RaiseError(err.Error()) + //L.Push(LNil) + //L.Push(LString(err.Error())) + return 2 +} + +var fileSeekOptions = []string{"set", "cur", "end"} + +func fileSeek(L *LState) int { + file := checkFile(L) + if file.Type() != lFileFile { + L.Push(LNil) + L.Push(LString("can not seek a process.")) + return 2 + } + + top := L.GetTop() + if top == 1 { + L.Push(LString("cur")) + L.Push(LNumber(0)) + } else if top == 2 { + L.Push(LNumber(0)) + } + + var pos int64 + var err error + + err = file.AbandonReadBuffer() + if err != nil { + goto errreturn + } + + pos, err = file.fp.Seek(L.CheckInt64(3), L.CheckOption(2, fileSeekOptions)) + if err != nil { + goto errreturn + } + + L.Push(LNumber(pos)) + return 1 + +errreturn: + L.Push(LNil) + L.Push(LString(err.Error())) + return 2 +} + +func fileWrite(L *LState) int { + return fileWriteAux(L, checkFile(L), 2) +} + +func fileClose(L *LState) int { + return fileCloseAux(L, checkFile(L)) +} + +func fileFlush(L *LState) int { + return fileFlushAux(L, checkFile(L)) +} + +func fileLinesIter(L *LState) int { + var file *lFile + if ud, ok := L.Get(1).(*LUserData); ok { + file = ud.Value.(*lFile) + } else { + file = L.Get(UpvalueIndex(2)).(*LUserData).Value.(*lFile) + } + buf, _, err := file.reader.ReadLine() + if err != nil { + if err == io.EOF { + L.Push(LNil) + return 1 + } + L.RaiseError(err.Error()) + } + L.Push(LString(string(buf))) + return 1 +} + +func fileLines(L *LState) int { + file := checkFile(L) + ud := L.CheckUserData(1) + if n := fileIsReadable(L, file); n != 0 { + return 0 + } + L.Push(L.NewClosure(fileLinesIter, L.Get(UpvalueIndex(1)), ud)) + return 1 +} + +func fileRead(L *LState) int { + return fileReadAux(L, checkFile(L), 2) +} + +var filebufOptions = []string{"no", "full"} + +func fileSetVBuf(L *LState) int { + var err error + var writer io.Writer + file := checkFile(L) + if n := fileIsWritable(L, file); n != 0 { + return n + } + switch filebufOptions[L.CheckOption(2, filebufOptions)] { + case "no": + switch file.Type() { + case lFileFile: + file.writer = file.fp + case lFileProcess: + file.writer, err = file.pp.StdinPipe() + if err != nil { + goto errreturn + } + } + case "full", "line": // TODO line buffer not supported + bufsize := L.OptInt(3, fileDefaultWriteBuffer) + switch file.Type() { + case lFileFile: + file.writer = bufio.NewWriterSize(file.fp, bufsize) + case lFileProcess: + writer, err = file.pp.StdinPipe() + if err != nil { + goto errreturn + } + file.writer = bufio.NewWriterSize(writer, bufsize) + } + } + L.Push(LTrue) + return 1 +errreturn: + L.Push(LNil) + L.Push(LString(err.Error())) + return 2 +} + +func ioInput(L *LState) int { + if L.GetTop() == 0 { + L.Push(fileDefIn(L)) + return 1 + } + switch lv := L.Get(1).(type) { + case LString: + file, err := newFile(L, nil, string(lv), os.O_RDONLY, 0600, false, true) + if err != nil { + L.RaiseError(err.Error()) + } + L.Get(UpvalueIndex(1)).(*LTable).RawSetInt(fileDefInIndex, file) + L.Push(file) + return 1 + case *LUserData: + if _, ok := lv.Value.(*lFile); ok { + L.Get(UpvalueIndex(1)).(*LTable).RawSetInt(fileDefInIndex, lv) + L.Push(lv) + return 1 + } + + } + L.ArgError(1, "string or file expedted, but got "+L.Get(1).Type().String()) + return 0 +} + +func ioClose(L *LState) int { + if L.GetTop() == 0 { + return fileCloseAux(L, fileDefOut(L).Value.(*lFile)) + } + return fileClose(L) +} + +func ioFlush(L *LState) int { + return fileFlushAux(L, fileDefOut(L).Value.(*lFile)) +} + +func ioLinesIter(L *LState) int { + var file *lFile + toclose := false + if ud, ok := L.Get(1).(*LUserData); ok { + file = ud.Value.(*lFile) + } else { + file = L.Get(UpvalueIndex(2)).(*LUserData).Value.(*lFile) + toclose = true + } + buf, _, err := file.reader.ReadLine() + if err != nil { + if err == io.EOF { + if toclose { + fileCloseAux(L, file) + } + L.Push(LNil) + return 1 + } + L.RaiseError(err.Error()) + } + L.Push(LString(string(buf))) + return 1 +} + +func ioLines(L *LState) int { + if L.GetTop() == 0 { + L.Push(L.Get(UpvalueIndex(2))) + L.Push(fileDefIn(L)) + return 2 + } + + path := L.CheckString(1) + ud, err := newFile(L, nil, path, os.O_RDONLY, os.FileMode(0600), false, true) + if err != nil { + return 0 + } + L.Push(L.NewClosure(ioLinesIter, L.Get(UpvalueIndex(1)), ud)) + return 1 +} + +var ioOpenOpions = []string{"r", "rb", "w", "wb", "a", "ab", "r+", "rb+", "w+", "wb+", "a+", "ab+"} + +func ioOpenFile(L *LState) int { + path := L.CheckString(1) + if L.GetTop() == 1 { + L.Push(LString("r")) + } + mode := os.O_RDONLY + perm := 0600 + writable := true + readable := true + switch ioOpenOpions[L.CheckOption(2, ioOpenOpions)] { + case "r", "rb": + mode = os.O_RDONLY + writable = false + case "w", "wb": + mode = os.O_WRONLY | os.O_CREATE + readable = false + case "a", "ab": + mode = os.O_WRONLY | os.O_APPEND | os.O_CREATE + case "r+", "rb+": + mode = os.O_RDWR + case "w+", "wb+": + mode = os.O_RDWR | os.O_TRUNC | os.O_CREATE + case "a+", "ab+": + mode = os.O_APPEND | os.O_RDWR | os.O_CREATE + } + file, err := newFile(L, nil, path, mode, os.FileMode(perm), writable, readable) + if err != nil { + L.Push(LNil) + L.Push(LString(err.Error())) + L.Push(LNumber(1)) // C-Lua compatibility: Original Lua pushes errno to the stack + return 3 + } + L.Push(file) + return 1 + +} + +var ioPopenOptions = []string{"r", "w"} + +func ioPopen(L *LState) int { + cmd := L.CheckString(1) + if L.GetTop() == 1 { + L.Push(LString("r")) + } + var file *LUserData + var err error + + switch ioPopenOptions[L.CheckOption(2, ioPopenOptions)] { + case "r": + file, err = newProcess(L, cmd, false, true) + case "w": + file, err = newProcess(L, cmd, true, false) + } + if err != nil { + L.Push(LNil) + L.Push(LString(err.Error())) + return 2 + } + L.Push(file) + return 1 +} + +func ioRead(L *LState) int { + return fileReadAux(L, fileDefIn(L).Value.(*lFile), 1) +} + +func ioType(L *LState) int { + ud, udok := L.Get(1).(*LUserData) + if !udok { + L.Push(LNil) + return 1 + } + file, ok := ud.Value.(*lFile) + if !ok { + L.Push(LNil) + return 1 + } + if file.closed { + L.Push(LString("closed file")) + return 1 + } + L.Push(LString("file")) + return 1 +} + +func ioTmpFile(L *LState) int { + file, err := ioutil.TempFile("", "") + if err != nil { + L.Push(LNil) + L.Push(LString(err.Error())) + return 2 + } + L.G.tempFiles = append(L.G.tempFiles, file) + ud, _ := newFile(L, file, "", 0, os.FileMode(0), true, true) + L.Push(ud) + return 1 +} + +func ioOutput(L *LState) int { + if L.GetTop() == 0 { + L.Push(fileDefOut(L)) + return 1 + } + switch lv := L.Get(1).(type) { + case LString: + file, err := newFile(L, nil, string(lv), os.O_WRONLY|os.O_CREATE, 0600, true, false) + if err != nil { + L.RaiseError(err.Error()) + } + L.Get(UpvalueIndex(1)).(*LTable).RawSetInt(fileDefOutIndex, file) + L.Push(file) + return 1 + case *LUserData: + if _, ok := lv.Value.(*lFile); ok { + L.Get(UpvalueIndex(1)).(*LTable).RawSetInt(fileDefOutIndex, lv) + L.Push(lv) + return 1 + } + + } + L.ArgError(1, "string or file expedted, but got "+L.Get(1).Type().String()) + return 0 +} + +func ioWrite(L *LState) int { + return fileWriteAux(L, fileDefOut(L).Value.(*lFile), 1) +} + +// diff --git a/vendor/github.com/yuin/gopher-lua/linit.go b/vendor/github.com/yuin/gopher-lua/linit.go new file mode 100644 index 00000000000..cd96d660151 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/linit.go @@ -0,0 +1,54 @@ +package lua + +const ( + // BaseLibName is here for consistency; the base functions have no namespace/library. + BaseLibName = "" + // LoadLibName is here for consistency; the loading system has no namespace/library. + LoadLibName = "package" + // TabLibName is the name of the table Library. + TabLibName = "table" + // IoLibName is the name of the io Library. + IoLibName = "io" + // OsLibName is the name of the os Library. + OsLibName = "os" + // StringLibName is the name of the string Library. + StringLibName = "string" + // MathLibName is the name of the math Library. + MathLibName = "math" + // DebugLibName is the name of the debug Library. + DebugLibName = "debug" + // ChannelLibName is the name of the channel Library. + ChannelLibName = "channel" + // CoroutineLibName is the name of the coroutine Library. + CoroutineLibName = "coroutine" +) + +type luaLib struct { + libName string + libFunc LGFunction +} + +var luaLibs = []luaLib{ + luaLib{LoadLibName, OpenPackage}, + luaLib{BaseLibName, OpenBase}, + luaLib{TabLibName, OpenTable}, + luaLib{IoLibName, OpenIo}, + luaLib{OsLibName, OpenOs}, + luaLib{StringLibName, OpenString}, + luaLib{MathLibName, OpenMath}, + luaLib{DebugLibName, OpenDebug}, + luaLib{ChannelLibName, OpenChannel}, + luaLib{CoroutineLibName, OpenCoroutine}, +} + +// OpenLibs loads the built-in libraries. It is equivalent to running OpenLoad, +// then OpenBase, then iterating over the other OpenXXX functions in any order. +func (ls *LState) OpenLibs() { + // NB: Map iteration order in Go is deliberately randomised, so must open Load/Base + // prior to iterating. + for _, lib := range luaLibs { + ls.Push(ls.NewFunction(lib.libFunc)) + ls.Push(LString(lib.libName)) + ls.Call(1, 0) + } +} diff --git a/vendor/github.com/yuin/gopher-lua/loadlib.go b/vendor/github.com/yuin/gopher-lua/loadlib.go new file mode 100644 index 00000000000..772bb04ad88 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/loadlib.go @@ -0,0 +1,125 @@ +package lua + +import ( + "fmt" + "os" + "path/filepath" + "strings" +) + +/* load lib {{{ */ + +var loLoaders = []LGFunction{loLoaderPreload, loLoaderLua} + +func loGetPath(env string, defpath string) string { + path := os.Getenv(env) + if len(path) == 0 { + path = defpath + } + path = strings.Replace(path, ";;", ";"+defpath+";", -1) + if os.PathSeparator != '/' { + dir, err := filepath.Abs(filepath.Dir(os.Args[0])) + if err != nil { + panic(err) + } + path = strings.Replace(path, "!", dir, -1) + } + return path +} + +func loFindFile(L *LState, name, pname string) (string, string) { + name = strings.Replace(name, ".", string(os.PathSeparator), -1) + lv := L.GetField(L.GetField(L.Get(EnvironIndex), "package"), pname) + path, ok := lv.(LString) + if !ok { + L.RaiseError("package.%s must be a string", pname) + } + messages := []string{} + for _, pattern := range strings.Split(string(path), ";") { + luapath := strings.Replace(pattern, "?", name, -1) + if _, err := os.Stat(luapath); err == nil { + return luapath, "" + } else { + messages = append(messages, err.Error()) + } + } + return "", strings.Join(messages, "\n\t") +} + +func OpenPackage(L *LState) int { + packagemod := L.RegisterModule(LoadLibName, loFuncs) + + L.SetField(packagemod, "preload", L.NewTable()) + + loaders := L.CreateTable(len(loLoaders), 0) + for i, loader := range loLoaders { + L.RawSetInt(loaders, i+1, L.NewFunction(loader)) + } + L.SetField(packagemod, "loaders", loaders) + L.SetField(L.Get(RegistryIndex), "_LOADERS", loaders) + + loaded := L.NewTable() + L.SetField(packagemod, "loaded", loaded) + L.SetField(L.Get(RegistryIndex), "_LOADED", loaded) + + L.SetField(packagemod, "path", LString(loGetPath(LuaPath, LuaPathDefault))) + L.SetField(packagemod, "cpath", emptyLString) + + L.Push(packagemod) + return 1 +} + +var loFuncs = map[string]LGFunction{ + "loadlib": loLoadLib, + "seeall": loSeeAll, +} + +func loLoaderPreload(L *LState) int { + name := L.CheckString(1) + preload := L.GetField(L.GetField(L.Get(EnvironIndex), "package"), "preload") + if _, ok := preload.(*LTable); !ok { + L.RaiseError("package.preload must be a table") + } + lv := L.GetField(preload, name) + if lv == LNil { + L.Push(LString(fmt.Sprintf("no field package.preload['%s']", name))) + return 1 + } + L.Push(lv) + return 1 +} + +func loLoaderLua(L *LState) int { + name := L.CheckString(1) + path, msg := loFindFile(L, name, "path") + if len(path) == 0 { + L.Push(LString(msg)) + return 1 + } + fn, err1 := L.LoadFile(path) + if err1 != nil { + L.RaiseError(err1.Error()) + } + L.Push(fn) + return 1 +} + +func loLoadLib(L *LState) int { + L.RaiseError("loadlib is not supported") + return 0 +} + +func loSeeAll(L *LState) int { + mod := L.CheckTable(1) + mt := L.GetMetatable(mod) + if mt == LNil { + mt = L.CreateTable(0, 1) + L.SetMetatable(mod, mt) + } + L.SetField(mt, "__index", L.Get(GlobalsIndex)) + return 0 +} + +/* }}} */ + +// diff --git a/vendor/github.com/yuin/gopher-lua/mathlib.go b/vendor/github.com/yuin/gopher-lua/mathlib.go new file mode 100644 index 00000000000..e612f2f0bf8 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/mathlib.go @@ -0,0 +1,231 @@ +package lua + +import ( + "math" + "math/rand" +) + +func OpenMath(L *LState) int { + mod := L.RegisterModule(MathLibName, mathFuncs).(*LTable) + mod.RawSetString("pi", LNumber(math.Pi)) + mod.RawSetString("huge", LNumber(math.MaxFloat64)) + L.Push(mod) + return 1 +} + +var mathFuncs = map[string]LGFunction{ + "abs": mathAbs, + "acos": mathAcos, + "asin": mathAsin, + "atan": mathAtan, + "atan2": mathAtan2, + "ceil": mathCeil, + "cos": mathCos, + "cosh": mathCosh, + "deg": mathDeg, + "exp": mathExp, + "floor": mathFloor, + "fmod": mathFmod, + "frexp": mathFrexp, + "ldexp": mathLdexp, + "log": mathLog, + "log10": mathLog10, + "max": mathMax, + "min": mathMin, + "mod": mathMod, + "modf": mathModf, + "pow": mathPow, + "rad": mathRad, + "random": mathRandom, + "randomseed": mathRandomseed, + "sin": mathSin, + "sinh": mathSinh, + "sqrt": mathSqrt, + "tan": mathTan, + "tanh": mathTanh, +} + +func mathAbs(L *LState) int { + L.Push(LNumber(math.Abs(float64(L.CheckNumber(1))))) + return 1 +} + +func mathAcos(L *LState) int { + L.Push(LNumber(math.Acos(float64(L.CheckNumber(1))))) + return 1 +} + +func mathAsin(L *LState) int { + L.Push(LNumber(math.Asin(float64(L.CheckNumber(1))))) + return 1 +} + +func mathAtan(L *LState) int { + L.Push(LNumber(math.Atan(float64(L.CheckNumber(1))))) + return 1 +} + +func mathAtan2(L *LState) int { + L.Push(LNumber(math.Atan2(float64(L.CheckNumber(1)), float64(L.CheckNumber(2))))) + return 1 +} + +func mathCeil(L *LState) int { + L.Push(LNumber(math.Ceil(float64(L.CheckNumber(1))))) + return 1 +} + +func mathCos(L *LState) int { + L.Push(LNumber(math.Cos(float64(L.CheckNumber(1))))) + return 1 +} + +func mathCosh(L *LState) int { + L.Push(LNumber(math.Cosh(float64(L.CheckNumber(1))))) + return 1 +} + +func mathDeg(L *LState) int { + L.Push(LNumber(float64(L.CheckNumber(1)) * 180 / math.Pi)) + return 1 +} + +func mathExp(L *LState) int { + L.Push(LNumber(math.Exp(float64(L.CheckNumber(1))))) + return 1 +} + +func mathFloor(L *LState) int { + L.Push(LNumber(math.Floor(float64(L.CheckNumber(1))))) + return 1 +} + +func mathFmod(L *LState) int { + L.Push(LNumber(math.Mod(float64(L.CheckNumber(1)), float64(L.CheckNumber(2))))) + return 1 +} + +func mathFrexp(L *LState) int { + v1, v2 := math.Frexp(float64(L.CheckNumber(1))) + L.Push(LNumber(v1)) + L.Push(LNumber(v2)) + return 2 +} + +func mathLdexp(L *LState) int { + L.Push(LNumber(math.Ldexp(float64(L.CheckNumber(1)), L.CheckInt(2)))) + return 1 +} + +func mathLog(L *LState) int { + L.Push(LNumber(math.Log(float64(L.CheckNumber(1))))) + return 1 +} + +func mathLog10(L *LState) int { + L.Push(LNumber(math.Log10(float64(L.CheckNumber(1))))) + return 1 +} + +func mathMax(L *LState) int { + if L.GetTop() == 0 { + L.RaiseError("wrong number of arguments") + } + max := L.CheckNumber(1) + top := L.GetTop() + for i := 2; i <= top; i++ { + v := L.CheckNumber(i) + if v > max { + max = v + } + } + L.Push(max) + return 1 +} + +func mathMin(L *LState) int { + if L.GetTop() == 0 { + L.RaiseError("wrong number of arguments") + } + min := L.CheckNumber(1) + top := L.GetTop() + for i := 2; i <= top; i++ { + v := L.CheckNumber(i) + if v < min { + min = v + } + } + L.Push(min) + return 1 +} + +func mathMod(L *LState) int { + lhs := L.CheckNumber(1) + rhs := L.CheckNumber(2) + L.Push(luaModulo(lhs, rhs)) + return 1 +} + +func mathModf(L *LState) int { + v1, v2 := math.Modf(float64(L.CheckNumber(1))) + L.Push(LNumber(v1)) + L.Push(LNumber(v2)) + return 2 +} + +func mathPow(L *LState) int { + L.Push(LNumber(math.Pow(float64(L.CheckNumber(1)), float64(L.CheckNumber(2))))) + return 1 +} + +func mathRad(L *LState) int { + L.Push(LNumber(float64(L.CheckNumber(1)) * math.Pi / 180)) + return 1 +} + +func mathRandom(L *LState) int { + switch L.GetTop() { + case 0: + L.Push(LNumber(rand.Float64())) + case 1: + n := L.CheckInt(1) + L.Push(LNumber(rand.Intn(n) + 1)) + default: + min := L.CheckInt(1) + max := L.CheckInt(2) + 1 + L.Push(LNumber(rand.Intn(max-min) + min)) + } + return 1 +} + +func mathRandomseed(L *LState) int { + rand.Seed(L.CheckInt64(1)) + return 0 +} + +func mathSin(L *LState) int { + L.Push(LNumber(math.Sin(float64(L.CheckNumber(1))))) + return 1 +} + +func mathSinh(L *LState) int { + L.Push(LNumber(math.Sinh(float64(L.CheckNumber(1))))) + return 1 +} + +func mathSqrt(L *LState) int { + L.Push(LNumber(math.Sqrt(float64(L.CheckNumber(1))))) + return 1 +} + +func mathTan(L *LState) int { + L.Push(LNumber(math.Tan(float64(L.CheckNumber(1))))) + return 1 +} + +func mathTanh(L *LState) int { + L.Push(LNumber(math.Tanh(float64(L.CheckNumber(1))))) + return 1 +} + +// diff --git a/vendor/github.com/yuin/gopher-lua/opcode.go b/vendor/github.com/yuin/gopher-lua/opcode.go new file mode 100644 index 00000000000..91fff1c9b41 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/opcode.go @@ -0,0 +1,371 @@ +package lua + +import ( + "fmt" +) + +/* + gopherlua uses Lua 5.1.4's opcodes. + Lua 5.1.4 opcodes layout: + + instruction = 32bit(fixed length) + + +---------------------------------------------+ + |0-5(6bits)|6-13(8bit)|14-22(9bit)|23-31(9bit)| + |==========+==========+===========+===========| + | opcode | A | C | B | + |----------+----------+-----------+-----------| + | opcode | A | Bx(unsigned) | + |----------+----------+-----------+-----------| + | opcode | A | sBx(signed) | + +---------------------------------------------+ +*/ + +const opInvalidInstruction = ^uint32(0) + +const opSizeCode = 6 +const opSizeA = 8 +const opSizeB = 9 +const opSizeC = 9 +const opSizeBx = 18 +const opSizesBx = 18 + +const opMaxArgsA = (1 << opSizeA) - 1 +const opMaxArgsB = (1 << opSizeB) - 1 +const opMaxArgsC = (1 << opSizeC) - 1 +const opMaxArgBx = (1 << opSizeBx) - 1 +const opMaxArgSbx = opMaxArgBx >> 1 + +const ( + OP_MOVE int = iota /* A B R(A) := R(B) */ + OP_MOVEN /* A B R(A) := R(B); followed by R(C) MOVE ops */ + OP_LOADK /* A Bx R(A) := Kst(Bx) */ + OP_LOADBOOL /* A B C R(A) := (Bool)B; if (C) pc++ */ + OP_LOADNIL /* A B R(A) := ... := R(B) := nil */ + OP_GETUPVAL /* A B R(A) := UpValue[B] */ + + OP_GETGLOBAL /* A Bx R(A) := Gbl[Kst(Bx)] */ + OP_GETTABLE /* A B C R(A) := R(B)[RK(C)] */ + OP_GETTABLEKS /* A B C R(A) := R(B)[RK(C)] ; RK(C) is constant string */ + + OP_SETGLOBAL /* A Bx Gbl[Kst(Bx)] := R(A) */ + OP_SETUPVAL /* A B UpValue[B] := R(A) */ + OP_SETTABLE /* A B C R(A)[RK(B)] := RK(C) */ + OP_SETTABLEKS /* A B C R(A)[RK(B)] := RK(C) ; RK(B) is constant string */ + + OP_NEWTABLE /* A B C R(A) := {} (size = BC) */ + + OP_SELF /* A B C R(A+1) := R(B); R(A) := R(B)[RK(C)] */ + + OP_ADD /* A B C R(A) := RK(B) + RK(C) */ + OP_SUB /* A B C R(A) := RK(B) - RK(C) */ + OP_MUL /* A B C R(A) := RK(B) * RK(C) */ + OP_DIV /* A B C R(A) := RK(B) / RK(C) */ + OP_MOD /* A B C R(A) := RK(B) % RK(C) */ + OP_POW /* A B C R(A) := RK(B) ^ RK(C) */ + OP_UNM /* A B R(A) := -R(B) */ + OP_NOT /* A B R(A) := not R(B) */ + OP_LEN /* A B R(A) := length of R(B) */ + + OP_CONCAT /* A B C R(A) := R(B).. ... ..R(C) */ + + OP_JMP /* sBx pc+=sBx */ + + OP_EQ /* A B C if ((RK(B) == RK(C)) ~= A) then pc++ */ + OP_LT /* A B C if ((RK(B) < RK(C)) ~= A) then pc++ */ + OP_LE /* A B C if ((RK(B) <= RK(C)) ~= A) then pc++ */ + + OP_TEST /* A C if not (R(A) <=> C) then pc++ */ + OP_TESTSET /* A B C if (R(B) <=> C) then R(A) := R(B) else pc++ */ + + OP_CALL /* A B C R(A) ... R(A+C-2) := R(A)(R(A+1) ... R(A+B-1)) */ + OP_TAILCALL /* A B C return R(A)(R(A+1) ... R(A+B-1)) */ + OP_RETURN /* A B return R(A) ... R(A+B-2) (see note) */ + + OP_FORLOOP /* A sBx R(A)+=R(A+2); + if R(A) =) R(A)*/ + OP_CLOSURE /* A Bx R(A) := closure(KPROTO[Bx] R(A) ... R(A+n)) */ + + OP_VARARG /* A B R(A) R(A+1) ... R(A+B-1) = vararg */ + + OP_NOP /* NOP */ +) +const opCodeMax = OP_NOP + +type opArgMode int + +const ( + opArgModeN opArgMode = iota + opArgModeU + opArgModeR + opArgModeK +) + +type opType int + +const ( + opTypeABC = iota + opTypeABx + opTypeASbx +) + +type opProp struct { + Name string + IsTest bool + SetRegA bool + ModeArgB opArgMode + ModeArgC opArgMode + Type opType +} + +var opProps = []opProp{ + opProp{"MOVE", false, true, opArgModeR, opArgModeN, opTypeABC}, + opProp{"MOVEN", false, true, opArgModeR, opArgModeN, opTypeABC}, + opProp{"LOADK", false, true, opArgModeK, opArgModeN, opTypeABx}, + opProp{"LOADBOOL", false, true, opArgModeU, opArgModeU, opTypeABC}, + opProp{"LOADNIL", false, true, opArgModeR, opArgModeN, opTypeABC}, + opProp{"GETUPVAL", false, true, opArgModeU, opArgModeN, opTypeABC}, + opProp{"GETGLOBAL", false, true, opArgModeK, opArgModeN, opTypeABx}, + opProp{"GETTABLE", false, true, opArgModeR, opArgModeK, opTypeABC}, + opProp{"GETTABLEKS", false, true, opArgModeR, opArgModeK, opTypeABC}, + opProp{"SETGLOBAL", false, false, opArgModeK, opArgModeN, opTypeABx}, + opProp{"SETUPVAL", false, false, opArgModeU, opArgModeN, opTypeABC}, + opProp{"SETTABLE", false, false, opArgModeK, opArgModeK, opTypeABC}, + opProp{"SETTABLEKS", false, false, opArgModeK, opArgModeK, opTypeABC}, + opProp{"NEWTABLE", false, true, opArgModeU, opArgModeU, opTypeABC}, + opProp{"SELF", false, true, opArgModeR, opArgModeK, opTypeABC}, + opProp{"ADD", false, true, opArgModeK, opArgModeK, opTypeABC}, + opProp{"SUB", false, true, opArgModeK, opArgModeK, opTypeABC}, + opProp{"MUL", false, true, opArgModeK, opArgModeK, opTypeABC}, + opProp{"DIV", false, true, opArgModeK, opArgModeK, opTypeABC}, + opProp{"MOD", false, true, opArgModeK, opArgModeK, opTypeABC}, + opProp{"POW", false, true, opArgModeK, opArgModeK, opTypeABC}, + opProp{"UNM", false, true, opArgModeR, opArgModeN, opTypeABC}, + opProp{"NOT", false, true, opArgModeR, opArgModeN, opTypeABC}, + opProp{"LEN", false, true, opArgModeR, opArgModeN, opTypeABC}, + opProp{"CONCAT", false, true, opArgModeR, opArgModeR, opTypeABC}, + opProp{"JMP", false, false, opArgModeR, opArgModeN, opTypeASbx}, + opProp{"EQ", true, false, opArgModeK, opArgModeK, opTypeABC}, + opProp{"LT", true, false, opArgModeK, opArgModeK, opTypeABC}, + opProp{"LE", true, false, opArgModeK, opArgModeK, opTypeABC}, + opProp{"TEST", true, true, opArgModeR, opArgModeU, opTypeABC}, + opProp{"TESTSET", true, true, opArgModeR, opArgModeU, opTypeABC}, + opProp{"CALL", false, true, opArgModeU, opArgModeU, opTypeABC}, + opProp{"TAILCALL", false, true, opArgModeU, opArgModeU, opTypeABC}, + opProp{"RETURN", false, false, opArgModeU, opArgModeN, opTypeABC}, + opProp{"FORLOOP", false, true, opArgModeR, opArgModeN, opTypeASbx}, + opProp{"FORPREP", false, true, opArgModeR, opArgModeN, opTypeASbx}, + opProp{"TFORLOOP", true, false, opArgModeN, opArgModeU, opTypeABC}, + opProp{"SETLIST", false, false, opArgModeU, opArgModeU, opTypeABC}, + opProp{"CLOSE", false, false, opArgModeN, opArgModeN, opTypeABC}, + opProp{"CLOSURE", false, true, opArgModeU, opArgModeN, opTypeABx}, + opProp{"VARARG", false, true, opArgModeU, opArgModeN, opTypeABC}, + opProp{"NOP", false, false, opArgModeR, opArgModeN, opTypeASbx}, +} + +func opGetOpCode(inst uint32) int { + return int(inst >> 26) +} + +func opSetOpCode(inst *uint32, opcode int) { + *inst = (*inst & 0x3ffffff) | uint32(opcode<<26) +} + +func opGetArgA(inst uint32) int { + return int(inst>>18) & 0xff +} + +func opSetArgA(inst *uint32, arg int) { + *inst = (*inst & 0xfc03ffff) | uint32((arg&0xff)<<18) +} + +func opGetArgB(inst uint32) int { + return int(inst & 0x1ff) +} + +func opSetArgB(inst *uint32, arg int) { + *inst = (*inst & 0xfffffe00) | uint32(arg&0x1ff) +} + +func opGetArgC(inst uint32) int { + return int(inst>>9) & 0x1ff +} + +func opSetArgC(inst *uint32, arg int) { + *inst = (*inst & 0xfffc01ff) | uint32((arg&0x1ff)<<9) +} + +func opGetArgBx(inst uint32) int { + return int(inst & 0x3ffff) +} + +func opSetArgBx(inst *uint32, arg int) { + *inst = (*inst & 0xfffc0000) | uint32(arg&0x3ffff) +} + +func opGetArgSbx(inst uint32) int { + return opGetArgBx(inst) - opMaxArgSbx +} + +func opSetArgSbx(inst *uint32, arg int) { + opSetArgBx(inst, arg+opMaxArgSbx) +} + +func opCreateABC(op int, a int, b int, c int) uint32 { + var inst uint32 = 0 + opSetOpCode(&inst, op) + opSetArgA(&inst, a) + opSetArgB(&inst, b) + opSetArgC(&inst, c) + return inst +} + +func opCreateABx(op int, a int, bx int) uint32 { + var inst uint32 = 0 + opSetOpCode(&inst, op) + opSetArgA(&inst, a) + opSetArgBx(&inst, bx) + return inst +} + +func opCreateASbx(op int, a int, sbx int) uint32 { + var inst uint32 = 0 + opSetOpCode(&inst, op) + opSetArgA(&inst, a) + opSetArgSbx(&inst, sbx) + return inst +} + +const opBitRk = 1 << (opSizeB - 1) +const opMaxIndexRk = opBitRk - 1 + +func opIsK(value int) bool { + return bool((value & opBitRk) != 0) +} + +func opIndexK(value int) int { + return value & ^opBitRk +} + +func opRkAsk(value int) int { + return value | opBitRk +} + +func opToString(inst uint32) string { + op := opGetOpCode(inst) + if op > opCodeMax { + return "" + } + prop := &(opProps[op]) + + arga := opGetArgA(inst) + argb := opGetArgB(inst) + argc := opGetArgC(inst) + argbx := opGetArgBx(inst) + argsbx := opGetArgSbx(inst) + + buf := "" + switch prop.Type { + case opTypeABC: + buf = fmt.Sprintf("%s | %d, %d, %d", prop.Name, arga, argb, argc) + case opTypeABx: + buf = fmt.Sprintf("%s | %d, %d", prop.Name, arga, argbx) + case opTypeASbx: + buf = fmt.Sprintf("%s | %d, %d", prop.Name, arga, argsbx) + } + + switch op { + case OP_MOVE: + buf += fmt.Sprintf("; R(%v) := R(%v)", arga, argb) + case OP_MOVEN: + buf += fmt.Sprintf("; R(%v) := R(%v); followed by %v MOVE ops", arga, argb, argc) + case OP_LOADK: + buf += fmt.Sprintf("; R(%v) := Kst(%v)", arga, argbx) + case OP_LOADBOOL: + buf += fmt.Sprintf("; R(%v) := (Bool)%v; if (%v) pc++", arga, argb, argc) + case OP_LOADNIL: + buf += fmt.Sprintf("; R(%v) := ... := R(%v) := nil", arga, argb) + case OP_GETUPVAL: + buf += fmt.Sprintf("; R(%v) := UpValue[%v]", arga, argb) + case OP_GETGLOBAL: + buf += fmt.Sprintf("; R(%v) := Gbl[Kst(%v)]", arga, argbx) + case OP_GETTABLE: + buf += fmt.Sprintf("; R(%v) := R(%v)[RK(%v)]", arga, argb, argc) + case OP_GETTABLEKS: + buf += fmt.Sprintf("; R(%v) := R(%v)[RK(%v)] ; RK(%v) is constant string", arga, argb, argc, argc) + case OP_SETGLOBAL: + buf += fmt.Sprintf("; Gbl[Kst(%v)] := R(%v)", argbx, arga) + case OP_SETUPVAL: + buf += fmt.Sprintf("; UpValue[%v] := R(%v)", argb, arga) + case OP_SETTABLE: + buf += fmt.Sprintf("; R(%v)[RK(%v)] := RK(%v)", arga, argb, argc) + case OP_SETTABLEKS: + buf += fmt.Sprintf("; R(%v)[RK(%v)] := RK(%v) ; RK(%v) is constant string", arga, argb, argc, argb) + case OP_NEWTABLE: + buf += fmt.Sprintf("; R(%v) := {} (size = BC)", arga) + case OP_SELF: + buf += fmt.Sprintf("; R(%v+1) := R(%v); R(%v) := R(%v)[RK(%v)]", arga, argb, arga, argb, argc) + case OP_ADD: + buf += fmt.Sprintf("; R(%v) := RK(%v) + RK(%v)", arga, argb, argc) + case OP_SUB: + buf += fmt.Sprintf("; R(%v) := RK(%v) - RK(%v)", arga, argb, argc) + case OP_MUL: + buf += fmt.Sprintf("; R(%v) := RK(%v) * RK(%v)", arga, argb, argc) + case OP_DIV: + buf += fmt.Sprintf("; R(%v) := RK(%v) / RK(%v)", arga, argb, argc) + case OP_MOD: + buf += fmt.Sprintf("; R(%v) := RK(%v) %% RK(%v)", arga, argb, argc) + case OP_POW: + buf += fmt.Sprintf("; R(%v) := RK(%v) ^ RK(%v)", arga, argb, argc) + case OP_UNM: + buf += fmt.Sprintf("; R(%v) := -R(%v)", arga, argb) + case OP_NOT: + buf += fmt.Sprintf("; R(%v) := not R(%v)", arga, argb) + case OP_LEN: + buf += fmt.Sprintf("; R(%v) := length of R(%v)", arga, argb) + case OP_CONCAT: + buf += fmt.Sprintf("; R(%v) := R(%v).. ... ..R(%v)", arga, argb, argc) + case OP_JMP: + buf += fmt.Sprintf("; pc+=%v", argsbx) + case OP_EQ: + buf += fmt.Sprintf("; if ((RK(%v) == RK(%v)) ~= %v) then pc++", argb, argc, arga) + case OP_LT: + buf += fmt.Sprintf("; if ((RK(%v) < RK(%v)) ~= %v) then pc++", argb, argc, arga) + case OP_LE: + buf += fmt.Sprintf("; if ((RK(%v) <= RK(%v)) ~= %v) then pc++", argb, argc, arga) + case OP_TEST: + buf += fmt.Sprintf("; if not (R(%v) <=> %v) then pc++", arga, argc) + case OP_TESTSET: + buf += fmt.Sprintf("; if (R(%v) <=> %v) then R(%v) := R(%v) else pc++", argb, argc, arga, argb) + case OP_CALL: + buf += fmt.Sprintf("; R(%v) ... R(%v+%v-2) := R(%v)(R(%v+1) ... R(%v+%v-1))", arga, arga, argc, arga, arga, arga, argb) + case OP_TAILCALL: + buf += fmt.Sprintf("; return R(%v)(R(%v+1) ... R(%v+%v-1))", arga, arga, arga, argb) + case OP_RETURN: + buf += fmt.Sprintf("; return R(%v) ... R(%v+%v-2)", arga, arga, argb) + case OP_FORLOOP: + buf += fmt.Sprintf("; R(%v)+=R(%v+2); if R(%v) =) R(%v)", arga) + case OP_CLOSURE: + buf += fmt.Sprintf("; R(%v) := closure(KPROTO[%v] R(%v) ... R(%v+n))", arga, argbx, arga, arga) + case OP_VARARG: + buf += fmt.Sprintf("; R(%v) R(%v+1) ... R(%v+%v-1) = vararg", arga, arga, arga, argb) + case OP_NOP: + /* nothing to do */ + } + return buf +} diff --git a/vendor/github.com/yuin/gopher-lua/oslib.go b/vendor/github.com/yuin/gopher-lua/oslib.go new file mode 100644 index 00000000000..c70a99bf136 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/oslib.go @@ -0,0 +1,221 @@ +package lua + +import ( + "io/ioutil" + "os" + "strings" + "time" +) + +var startedAt time.Time + +func init() { + startedAt = time.Now() +} + +func getIntField(L *LState, tb *LTable, key string, v int) int { + ret := tb.RawGetString(key) + + switch lv := ret.(type) { + case LNumber: + return int(lv) + case LString: + 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 + slv = strings.TrimLeft(slv, "0") + } + if num, err := parseNumber(slv); err == nil { + return int(num) + } + default: + return v + } + + return v +} + +func getBoolField(L *LState, tb *LTable, key string, v bool) bool { + ret := tb.RawGetString(key) + if lb, ok := ret.(LBool); ok { + return bool(lb) + } + return v +} + +func OpenOs(L *LState) int { + osmod := L.RegisterModule(OsLibName, osFuncs) + L.Push(osmod) + return 1 +} + +var osFuncs = map[string]LGFunction{ + "clock": osClock, + "difftime": osDiffTime, + "execute": osExecute, + "exit": osExit, + "date": osDate, + "getenv": osGetEnv, + "remove": osRemove, + "rename": osRename, + "setenv": osSetEnv, + "setlocale": osSetLocale, + "time": osTime, + "tmpname": osTmpname, +} + +func osClock(L *LState) int { + L.Push(LNumber(float64(time.Now().Sub(startedAt)) / float64(time.Second))) + return 1 +} + +func osDiffTime(L *LState) int { + L.Push(LNumber(L.CheckInt64(1) - L.CheckInt64(2))) + return 1 +} + +func osExecute(L *LState) int { + var procAttr os.ProcAttr + procAttr.Files = []*os.File{os.Stdin, os.Stdout, os.Stderr} + cmd, args := popenArgs(L.CheckString(1)) + args = append([]string{cmd}, args...) + process, err := os.StartProcess(cmd, args, &procAttr) + if err != nil { + L.Push(LNumber(1)) + return 1 + } + + ps, err := process.Wait() + if err != nil || !ps.Success() { + L.Push(LNumber(1)) + return 1 + } + L.Push(LNumber(0)) + return 1 +} + +func osExit(L *LState) int { + L.Close() + os.Exit(L.OptInt(1, 0)) + return 1 +} + +func osDate(L *LState) int { + t := time.Now() + cfmt := "%c" + if L.GetTop() >= 1 { + cfmt = L.CheckString(1) + if strings.HasPrefix(cfmt, "!") { + t = time.Now().UTC() + cfmt = strings.TrimLeft(cfmt, "!") + } + if L.GetTop() >= 2 { + t = time.Unix(L.CheckInt64(2), 0) + } + if strings.HasPrefix(cfmt, "*t") { + ret := L.NewTable() + ret.RawSetString("year", LNumber(t.Year())) + ret.RawSetString("month", LNumber(t.Month())) + ret.RawSetString("day", LNumber(t.Day())) + ret.RawSetString("hour", LNumber(t.Hour())) + ret.RawSetString("min", LNumber(t.Minute())) + ret.RawSetString("sec", LNumber(t.Second())) + ret.RawSetString("wday", LNumber(t.Weekday()+1)) + // TODO yday & dst + ret.RawSetString("yday", LNumber(0)) + ret.RawSetString("isdst", LFalse) + L.Push(ret) + return 1 + } + } + L.Push(LString(strftime(t, cfmt))) + return 1 +} + +func osGetEnv(L *LState) int { + v := os.Getenv(L.CheckString(1)) + if len(v) == 0 { + L.Push(LNil) + } else { + L.Push(LString(v)) + } + return 1 +} + +func osRemove(L *LState) int { + err := os.Remove(L.CheckString(1)) + if err != nil { + L.Push(LNil) + L.Push(LString(err.Error())) + return 2 + } else { + L.Push(LTrue) + return 1 + } +} + +func osRename(L *LState) int { + err := os.Rename(L.CheckString(1), L.CheckString(2)) + if err != nil { + L.Push(LNil) + L.Push(LString(err.Error())) + return 2 + } else { + L.Push(LTrue) + return 1 + } +} + +func osSetLocale(L *LState) int { + // setlocale is not supported + L.Push(LFalse) + return 1 +} + +func osSetEnv(L *LState) int { + err := os.Setenv(L.CheckString(1), L.CheckString(2)) + if err != nil { + L.Push(LNil) + L.Push(LString(err.Error())) + return 2 + } else { + L.Push(LTrue) + return 1 + } +} + +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) + } + L.Push(LNumber(t.Unix())) + } + return 1 +} + +func osTmpname(L *LState) int { + file, err := ioutil.TempFile("", "") + if err != nil { + L.RaiseError("unable to generate a unique filename") + } + file.Close() + os.Remove(file.Name()) // ignore errors + L.Push(LString(file.Name())) + return 1 +} + +// diff --git a/vendor/github.com/yuin/gopher-lua/package.go b/vendor/github.com/yuin/gopher-lua/package.go new file mode 100644 index 00000000000..9fde3f0c215 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/package.go @@ -0,0 +1,7 @@ +// GopherLua: VM and compiler for Lua in Go +package lua + +const PackageName = "GopherLua" +const PackageVersion = "0.1" +const PackageAuthors = "Yusuke Inuzuka" +const PackageCopyRight = PackageName + " " + PackageVersion + " Copyright (C) 2015 -2017 " + PackageAuthors diff --git a/vendor/github.com/yuin/gopher-lua/parse/Makefile b/vendor/github.com/yuin/gopher-lua/parse/Makefile new file mode 100644 index 00000000000..6dd048c165f --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/parse/Makefile @@ -0,0 +1,4 @@ +all : parser.go + +parser.go : parser.go.y + goyacc -o $@ parser.go.y; [ -f y.output ] && ( rm -f y.output ) diff --git a/vendor/github.com/yuin/gopher-lua/parse/lexer.go b/vendor/github.com/yuin/gopher-lua/parse/lexer.go new file mode 100644 index 00000000000..d711e78bc19 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/parse/lexer.go @@ -0,0 +1,539 @@ +package parse + +import ( + "bufio" + "bytes" + "fmt" + "github.com/yuin/gopher-lua/ast" + "io" + "reflect" + "strconv" + "strings" +) + +const EOF = -1 +const whitespace1 = 1<<'\t' | 1<<' ' +const whitespace2 = 1<<'\t' | 1<<'\n' | 1<<'\r' | 1<<' ' + +type Error struct { + Pos ast.Position + Message string + Token string +} + +func (e *Error) Error() string { + pos := e.Pos + if pos.Line == EOF { + return fmt.Sprintf("%v at EOF: %s\n", pos.Source, e.Message) + } else { + return fmt.Sprintf("%v line:%d(column:%d) near '%v': %s\n", pos.Source, pos.Line, pos.Column, e.Token, e.Message) + } +} + +func writeChar(buf *bytes.Buffer, c int) { buf.WriteByte(byte(c)) } + +func isDecimal(ch int) bool { return '0' <= ch && ch <= '9' } + +func isIdent(ch int, pos int) bool { + return ch == '_' || 'A' <= ch && ch <= 'Z' || 'a' <= ch && ch <= 'z' || isDecimal(ch) && pos > 0 +} + +func isDigit(ch int) bool { + return '0' <= ch && ch <= '9' || 'a' <= ch && ch <= 'f' || 'A' <= ch && ch <= 'F' +} + +type Scanner struct { + Pos ast.Position + reader *bufio.Reader +} + +func NewScanner(reader io.Reader, source string) *Scanner { + return &Scanner{ + Pos: ast.Position{ + Source: source, + Line: 1, + Column: 0, + }, + reader: bufio.NewReaderSize(reader, 4096), + } +} + +func (sc *Scanner) Error(tok string, msg string) *Error { return &Error{sc.Pos, msg, tok} } + +func (sc *Scanner) TokenError(tok ast.Token, msg string) *Error { return &Error{tok.Pos, msg, tok.Str} } + +func (sc *Scanner) readNext() int { + ch, err := sc.reader.ReadByte() + if err == io.EOF { + return EOF + } + return int(ch) +} + +func (sc *Scanner) Newline(ch int) { + if ch < 0 { + return + } + sc.Pos.Line += 1 + sc.Pos.Column = 0 + next := sc.Peek() + if ch == '\n' && next == '\r' || ch == '\r' && next == '\n' { + sc.reader.ReadByte() + } +} + +func (sc *Scanner) Next() int { + ch := sc.readNext() + switch ch { + case '\n', '\r': + sc.Newline(ch) + ch = int('\n') + case EOF: + sc.Pos.Line = EOF + sc.Pos.Column = 0 + default: + sc.Pos.Column++ + } + return ch +} + +func (sc *Scanner) Peek() int { + ch := sc.readNext() + if ch != EOF { + sc.reader.UnreadByte() + } + return ch +} + +func (sc *Scanner) skipWhiteSpace(whitespace int64) int { + ch := sc.Next() + for ; whitespace&(1<': + if sc.Peek() == '=' { + tok.Type = TGte + tok.Str = ">=" + sc.Next() + } else { + tok.Type = ch + tok.Str = string(ch) + } + case '.': + ch2 := sc.Peek() + switch { + case isDecimal(ch2): + tok.Type = TNumber + err = sc.scanNumber(ch, buf) + tok.Str = buf.String() + case ch2 == '.': + writeChar(buf, ch) + writeChar(buf, sc.Next()) + if sc.Peek() == '.' { + writeChar(buf, sc.Next()) + tok.Type = T3Comma + } else { + tok.Type = T2Comma + } + default: + tok.Type = '.' + } + tok.Str = buf.String() + case '+', '*', '/', '%', '^', '#', '(', ')', '{', '}', ']', ';', ':', ',': + tok.Type = ch + tok.Str = string(ch) + default: + writeChar(buf, ch) + err = sc.Error(buf.String(), "Invalid token") + goto finally + } + } + +finally: + tok.Name = TokenName(int(tok.Type)) + return tok, err +} + +// yacc interface {{{ + +type Lexer struct { + scanner *Scanner + Stmts []ast.Stmt + PNewLine bool + Token ast.Token + PrevTokenType int +} + +func (lx *Lexer) Lex(lval *yySymType) int { + lx.PrevTokenType = lx.Token.Type + tok, err := lx.scanner.Scan(lx) + if err != nil { + panic(err) + } + if tok.Type < 0 { + return 0 + } + lval.token = tok + lx.Token = tok + return int(tok.Type) +} + +func (lx *Lexer) Error(message string) { + panic(lx.scanner.Error(lx.Token.Str, message)) +} + +func (lx *Lexer) TokenError(tok ast.Token, message string) { + panic(lx.scanner.TokenError(tok, message)) +} + +func Parse(reader io.Reader, name string) (chunk []ast.Stmt, err error) { + lexer := &Lexer{NewScanner(reader, name), nil, false, ast.Token{Str: ""}, TNil} + chunk = nil + defer func() { + if e := recover(); e != nil { + err, _ = e.(error) + } + }() + yyParse(lexer) + chunk = lexer.Stmts + return +} + +// }}} + +// Dump {{{ + +func isInlineDumpNode(rv reflect.Value) bool { + switch rv.Kind() { + case reflect.Struct, reflect.Slice, reflect.Interface, reflect.Ptr: + return false + default: + return true + } +} + +func dump(node interface{}, level int, s string) string { + rt := reflect.TypeOf(node) + if fmt.Sprint(rt) == "" { + return strings.Repeat(s, level) + "" + } + + rv := reflect.ValueOf(node) + buf := []string{} + switch rt.Kind() { + case reflect.Slice: + if rv.Len() == 0 { + return strings.Repeat(s, level) + "" + } + for i := 0; i < rv.Len(); i++ { + buf = append(buf, dump(rv.Index(i).Interface(), level, s)) + } + case reflect.Ptr: + vt := rv.Elem() + tt := rt.Elem() + indicies := []int{} + for i := 0; i < tt.NumField(); i++ { + if strings.Index(tt.Field(i).Name, "Base") > -1 { + continue + } + indicies = append(indicies, i) + } + switch { + case len(indicies) == 0: + return strings.Repeat(s, level) + "" + case len(indicies) == 1 && isInlineDumpNode(vt.Field(indicies[0])): + for _, i := range indicies { + buf = append(buf, strings.Repeat(s, level)+"- Node$"+tt.Name()+": "+dump(vt.Field(i).Interface(), 0, s)) + } + default: + buf = append(buf, strings.Repeat(s, level)+"- Node$"+tt.Name()) + for _, i := range indicies { + if isInlineDumpNode(vt.Field(i)) { + inf := dump(vt.Field(i).Interface(), 0, s) + buf = append(buf, strings.Repeat(s, level+1)+tt.Field(i).Name+": "+inf) + } else { + buf = append(buf, strings.Repeat(s, level+1)+tt.Field(i).Name+": ") + buf = append(buf, dump(vt.Field(i).Interface(), level+2, s)) + } + } + } + default: + buf = append(buf, strings.Repeat(s, level)+fmt.Sprint(node)) + } + return strings.Join(buf, "\n") +} + +func Dump(chunk []ast.Stmt) string { + return dump(chunk, 0, " ") +} + +// }} diff --git a/vendor/github.com/yuin/gopher-lua/parse/parser.go b/vendor/github.com/yuin/gopher-lua/parse/parser.go new file mode 100644 index 00000000000..f8f59b36154 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/parse/parser.go @@ -0,0 +1,1137 @@ +//line parser.go.y:2 +package parse + +import __yyfmt__ "fmt" + +//line parser.go.y:2 +import ( + "github.com/yuin/gopher-lua/ast" +) + +//line parser.go.y:34 +type yySymType struct { + yys int + token ast.Token + + stmts []ast.Stmt + stmt ast.Stmt + + funcname *ast.FuncName + funcexpr *ast.FunctionExpr + + exprlist []ast.Expr + expr ast.Expr + + fieldlist []*ast.Field + field *ast.Field + fieldsep string + + namelist []string + parlist *ast.ParList +} + +const TAnd = 57346 +const TBreak = 57347 +const TDo = 57348 +const TElse = 57349 +const TElseIf = 57350 +const TEnd = 57351 +const TFalse = 57352 +const TFor = 57353 +const TFunction = 57354 +const TIf = 57355 +const TIn = 57356 +const TLocal = 57357 +const TNil = 57358 +const TNot = 57359 +const TOr = 57360 +const TReturn = 57361 +const TRepeat = 57362 +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{ + "TAnd", + "TBreak", + "TDo", + "TElse", + "TElseIf", + "TEnd", + "TFalse", + "TFor", + "TFunction", + "TIf", + "TIn", + "TLocal", + "TNil", + "TNot", + "TOr", + "TReturn", + "TRepeat", + "TThen", + "TTrue", + "TUntil", + "TWhile", + "TEqeq", + "TNeq", + "TLte", + "TGte", + "T2Comma", + "T3Comma", + "TIdent", + "TNumber", + "TString", + " {", + " (", + " >", + " <", + " +", + " -", + " *", + " /", + " %", + "UNARY", + " ^", +} +var yyStatenames = []string{} + +const yyEofCode = 1 +const yyErrCode = 2 +const yyMaxDepth = 200 + +//line parser.go.y:514 +func TokenName(c int) string { + if c >= TAnd && c-TAnd < len(yyToknames) { + if yyToknames[c-TAnd] != "" { + return yyToknames[c-TAnd] + } + } + return string([]byte{byte(c)}) +} + +//line yacctab:1 +var yyExca = []int{ + -1, 1, + 1, -1, + -2, 0, + -1, 17, + 46, 31, + 47, 31, + -2, 68, + -1, 93, + 46, 32, + 47, 32, + -2, 68, +} + +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, +} +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 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 yyR1 = []int{ + + 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, + 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, +} +var yyR2 = []int{ + + 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, +} +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, + -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, +} +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, + 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, +} +var yyTok1 = []int{ + + 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, 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, 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, +} +var yyTok2 = []int{ + + 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, +} +var yyTok3 = []int{ + 0, +} + +//line yaccpar:1 + +/* parser for yacc output */ + +var yyDebug = 0 + +type yyLexer interface { + Lex(lval *yySymType) int + Error(s string) +} + +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] + } + } + return __yyfmt__.Sprintf("tok-%v", c) +} + +func yyStatname(s int) string { + if s >= 0 && s < len(yyStatenames) { + if yyStatenames[s] != "" { + return yyStatenames[s] + } + } + return __yyfmt__.Sprintf("state-%v", s) +} + +func yylex1(lex yyLexer, lval *yySymType) int { + c := 0 + char := lex.Lex(lval) + if char <= 0 { + c = yyTok1[0] + goto out + } + if char < len(yyTok1) { + c = yyTok1[char] + goto out + } + if char >= yyPrivate { + if char < yyPrivate+len(yyTok2) { + c = yyTok2[char-yyPrivate] + goto out + } + } + for i := 0; i < len(yyTok3); i += 2 { + c = yyTok3[i+0] + if c == char { + c = yyTok3[i+1] + goto out + } + } + +out: + if c == 0 { + c = yyTok2[1] /* unknown char */ + } + if yyDebug >= 3 { + __yyfmt__.Printf("lex %s(%d)\n", yyTokname(c), uint(char)) + } + return c +} + +func yyParse(yylex yyLexer) int { + var yyn int + var yylval yySymType + var yyVAL yySymType + yyS := make([]yySymType, yyMaxDepth) + + Nerrs := 0 /* number of errors */ + Errflag := 0 /* error recovery flag */ + yystate := 0 + yychar := -1 + yyp := -1 + goto yystack + +ret0: + return 0 + +ret1: + return 1 + +yystack: + /* put a state and value onto the stack */ + if yyDebug >= 4 { + __yyfmt__.Printf("char %v in %v\n", yyTokname(yychar), yyStatname(yystate)) + } + + yyp++ + if yyp >= len(yyS) { + nyys := make([]yySymType, len(yyS)*2) + copy(nyys, yyS) + yyS = nyys + } + yyS[yyp] = yyVAL + yyS[yyp].yys = yystate + +yynewstate: + yyn = yyPact[yystate] + if yyn <= yyFlag { + goto yydefault /* simple state */ + } + if yychar < 0 { + yychar = yylex1(yylex, &yylval) + } + yyn += yychar + if yyn < 0 || yyn >= yyLast { + goto yydefault + } + yyn = yyAct[yyn] + if yyChk[yyn] == yychar { /* valid shift */ + yychar = -1 + yyVAL = yylval + yystate = yyn + if Errflag > 0 { + Errflag-- + } + goto yystack + } + +yydefault: + /* default state action */ + yyn = yyDef[yystate] + if yyn == -2 { + if yychar < 0 { + yychar = yylex1(yylex, &yylval) + } + + /* look through exception table */ + xi := 0 + for { + if yyExca[xi+0] == -1 && yyExca[xi+1] == yystate { + break + } + xi += 2 + } + for xi += 2; ; xi += 2 { + yyn = yyExca[xi+0] + if yyn < 0 || yyn == yychar { + break + } + } + yyn = yyExca[xi+1] + if yyn < 0 { + goto ret0 + } + } + if yyn == 0 { + /* error ... attempt to resume parsing */ + switch Errflag { + case 0: /* brand new error */ + yylex.Error("syntax error") + Nerrs++ + if yyDebug >= 1 { + __yyfmt__.Printf("%s", yyStatname(yystate)) + __yyfmt__.Printf(" saw %s\n", yyTokname(yychar)) + } + fallthrough + + case 1, 2: /* incompletely recovered error ... try again */ + Errflag = 3 + + /* find a state where "error" is a legal shift action */ + for yyp >= 0 { + yyn = yyPact[yyS[yyp].yys] + yyErrCode + if yyn >= 0 && yyn < yyLast { + yystate = yyAct[yyn] /* simulate a shift of "error" */ + if yyChk[yystate] == yyErrCode { + goto yystack + } + } + + /* the current p has no shift on "error", pop stack */ + if yyDebug >= 2 { + __yyfmt__.Printf("error recovery pops state %d\n", yyS[yyp].yys) + } + yyp-- + } + /* there is no state on the stack with an error shift ... abort */ + goto ret1 + + case 3: /* no shift yet; clobber input char */ + if yyDebug >= 2 { + __yyfmt__.Printf("error recovery discards %s\n", yyTokname(yychar)) + } + if yychar == yyEofCode { + goto ret1 + } + yychar = -1 + goto yynewstate /* try again in the same state */ + } + } + + /* reduction by production yyn */ + if yyDebug >= 2 { + __yyfmt__.Printf("reduce %v in:\n\t%v\n", yyn, yyStatname(yystate)) + } + + yynt := yyn + yypt := yyp + _ = yypt // guard against "declared and not used" + + yyp -= yyR2[yyn] + yyVAL = yyS[yyp+1] + + /* consult goto table to find next state */ + yyn = yyR1[yyn] + yyg := yyPgo[yyn] + yyj := yyg + yyS[yyp].yys + 1 + + if yyj >= yyLast { + yystate = yyAct[yyg] + } else { + yystate = yyAct[yyj] + if yyChk[yystate] != -yyn { + yystate = yyAct[yyg] + } + } + // dummy call; replaced with literal code + switch yynt { + + case 1: + //line parser.go.y:73 + { + yyVAL.stmts = yyS[yypt-0].stmts + if l, ok := yylex.(*Lexer); ok { + l.Stmts = yyVAL.stmts + } + } + case 2: + //line parser.go.y:79 + { + yyVAL.stmts = append(yyS[yypt-1].stmts, yyS[yypt-0].stmt) + if l, ok := yylex.(*Lexer); ok { + l.Stmts = yyVAL.stmts + } + } + case 3: + //line parser.go.y:85 + { + yyVAL.stmts = append(yyS[yypt-2].stmts, yyS[yypt-1].stmt) + if l, ok := yylex.(*Lexer); ok { + l.Stmts = yyVAL.stmts + } + } + case 4: + //line parser.go.y:93 + { + yyVAL.stmts = []ast.Stmt{} + } + case 5: + //line parser.go.y:96 + { + yyVAL.stmts = append(yyS[yypt-1].stmts, yyS[yypt-0].stmt) + } + case 6: + //line parser.go.y:99 + { + yyVAL.stmts = yyS[yypt-1].stmts + } + case 7: + //line parser.go.y:104 + { + yyVAL.stmts = yyS[yypt-0].stmts + } + case 8: + //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()) + } + case 9: + //line parser.go.y:114 + { + if _, ok := yyS[yypt-0].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()) + } + } + case 10: + //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) + } + case 11: + //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) + } + case 12: + //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()) + } + case 13: + //line parser.go.y:137 + { + yyVAL.stmt = &ast.IfStmt{Condition: yyS[yypt-4].expr, Then: yyS[yypt-2].stmts} + cur := yyVAL.stmt + for _, elseif := range yyS[yypt-1].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) + } + case 14: + //line parser.go.y:147 + { + yyVAL.stmt = &ast.IfStmt{Condition: yyS[yypt-6].expr, Then: yyS[yypt-4].stmts} + cur := yyVAL.stmt + for _, elseif := range yyS[yypt-3].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) + } + case 15: + //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) + } + case 16: + //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) + } + case 17: + //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) + } + case 18: + //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()) + } + case 19: + //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()) + } + case 20: + //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) + } + case 21: + //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) + } + case 22: + //line parser.go.y:193 + { + yyVAL.stmts = []ast.Stmt{} + } + case 23: + //line parser.go.y:196 + { + 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) + } + case 24: + //line parser.go.y:202 + { + yyVAL.stmt = &ast.ReturnStmt{Exprs: nil} + yyVAL.stmt.SetLine(yyS[yypt-0].token.Pos.Line) + } + case 25: + //line parser.go.y:206 + { + yyVAL.stmt = &ast.ReturnStmt{Exprs: yyS[yypt-0].exprlist} + yyVAL.stmt.SetLine(yyS[yypt-1].token.Pos.Line) + } + case 26: + //line parser.go.y:210 + { + yyVAL.stmt = &ast.BreakStmt{} + yyVAL.stmt.SetLine(yyS[yypt-0].token.Pos.Line) + } + case 27: + //line parser.go.y:216 + { + yyVAL.funcname = yyS[yypt-0].funcname + } + case 28: + //line parser.go.y:219 + { + yyVAL.funcname = &ast.FuncName{Func: nil, Receiver: yyS[yypt-2].funcname.Func, Method: yyS[yypt-0].token.Str} + } + case 29: + //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) + } + case 30: + //line parser.go.y:228 + { + 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} + } + case 31: + //line parser.go.y:237 + { + yyVAL.exprlist = []ast.Expr{yyS[yypt-0].expr} + } + case 32: + //line parser.go.y:240 + { + yyVAL.exprlist = append(yyS[yypt-2].exprlist, yyS[yypt-0].expr) + } + case 33: + //line parser.go.y:245 + { + yyVAL.expr = &ast.IdentExpr{Value: yyS[yypt-0].token.Str} + yyVAL.expr.SetLine(yyS[yypt-0].token.Pos.Line) + } + case 34: + //line parser.go.y:249 + { + yyVAL.expr = &ast.AttrGetExpr{Object: yyS[yypt-3].expr, Key: yyS[yypt-1].expr} + yyVAL.expr.SetLine(yyS[yypt-3].expr.Line()) + } + case 35: + //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()) + } + case 36: + //line parser.go.y:261 + { + yyVAL.namelist = []string{yyS[yypt-0].token.Str} + } + case 37: + //line parser.go.y:264 + { + yyVAL.namelist = append(yyS[yypt-2].namelist, yyS[yypt-0].token.Str) + } + case 38: + //line parser.go.y:269 + { + yyVAL.exprlist = []ast.Expr{yyS[yypt-0].expr} + } + case 39: + //line parser.go.y:272 + { + yyVAL.exprlist = append(yyS[yypt-2].exprlist, yyS[yypt-0].expr) + } + case 40: + //line parser.go.y:277 + { + yyVAL.expr = &ast.NilExpr{} + yyVAL.expr.SetLine(yyS[yypt-0].token.Pos.Line) + } + case 41: + //line parser.go.y:281 + { + yyVAL.expr = &ast.FalseExpr{} + yyVAL.expr.SetLine(yyS[yypt-0].token.Pos.Line) + } + case 42: + //line parser.go.y:285 + { + yyVAL.expr = &ast.TrueExpr{} + yyVAL.expr.SetLine(yyS[yypt-0].token.Pos.Line) + } + case 43: + //line parser.go.y:289 + { + yyVAL.expr = &ast.NumberExpr{Value: yyS[yypt-0].token.Str} + yyVAL.expr.SetLine(yyS[yypt-0].token.Pos.Line) + } + case 44: + //line parser.go.y:293 + { + yyVAL.expr = &ast.Comma3Expr{} + yyVAL.expr.SetLine(yyS[yypt-0].token.Pos.Line) + } + case 45: + //line parser.go.y:297 + { + yyVAL.expr = yyS[yypt-0].expr + } + case 46: + //line parser.go.y:300 + { + yyVAL.expr = yyS[yypt-0].expr + } + case 47: + //line parser.go.y:303 + { + yyVAL.expr = yyS[yypt-0].expr + } + case 48: + //line parser.go.y:306 + { + yyVAL.expr = yyS[yypt-0].expr + } + case 49: + //line parser.go.y:309 + { + yyVAL.expr = &ast.LogicalOpExpr{Lhs: yyS[yypt-2].expr, Operator: "or", Rhs: yyS[yypt-0].expr} + yyVAL.expr.SetLine(yyS[yypt-2].expr.Line()) + } + case 50: + //line parser.go.y:313 + { + yyVAL.expr = &ast.LogicalOpExpr{Lhs: yyS[yypt-2].expr, Operator: "and", Rhs: yyS[yypt-0].expr} + yyVAL.expr.SetLine(yyS[yypt-2].expr.Line()) + } + case 51: + //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()) + } + case 52: + //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()) + } + case 53: + //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()) + } + case 54: + //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()) + } + case 55: + //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()) + } + case 56: + //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()) + } + case 57: + //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()) + } + case 58: + //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()) + } + case 59: + //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()) + } + case 60: + //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()) + } + case 61: + //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()) + } + case 62: + //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()) + } + case 63: + //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()) + } + case 64: + //line parser.go.y:369 + { + yyVAL.expr = &ast.UnaryMinusOpExpr{Expr: yyS[yypt-0].expr} + yyVAL.expr.SetLine(yyS[yypt-0].expr.Line()) + } + case 65: + //line parser.go.y:373 + { + yyVAL.expr = &ast.UnaryNotOpExpr{Expr: yyS[yypt-0].expr} + yyVAL.expr.SetLine(yyS[yypt-0].expr.Line()) + } + case 66: + //line parser.go.y:377 + { + yyVAL.expr = &ast.UnaryLenOpExpr{Expr: yyS[yypt-0].expr} + yyVAL.expr.SetLine(yyS[yypt-0].expr.Line()) + } + case 67: + //line parser.go.y:383 + { + yyVAL.expr = &ast.StringExpr{Value: yyS[yypt-0].token.Str} + yyVAL.expr.SetLine(yyS[yypt-0].token.Pos.Line) + } + case 68: + //line parser.go.y:389 + { + yyVAL.expr = yyS[yypt-0].expr + } + case 69: + //line parser.go.y:392 + { + yyVAL.expr = yyS[yypt-0].expr + } + case 70: + //line parser.go.y:395 + { + yyVAL.expr = yyS[yypt-0].expr + } + case 71: + //line parser.go.y:398 + { + yyVAL.expr = yyS[yypt-1].expr + yyVAL.expr.SetLine(yyS[yypt-2].token.Pos.Line) + } + case 72: + //line parser.go.y:404 + { + yyS[yypt-1].expr.(*ast.FuncCallExpr).AdjustRet = true + yyVAL.expr = yyS[yypt-1].expr + } + case 73: + //line parser.go.y:410 + { + yyVAL.expr = &ast.FuncCallExpr{Func: yyS[yypt-1].expr, Args: yyS[yypt-0].exprlist} + yyVAL.expr.SetLine(yyS[yypt-1].expr.Line()) + } + case 74: + //line parser.go.y:414 + { + 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()) + } + case 75: + //line parser.go.y:420 + { + if yylex.(*Lexer).PNewLine { + yylex.(*Lexer).TokenError(yyS[yypt-1].token, "ambiguous syntax (function call x new statement)") + } + yyVAL.exprlist = []ast.Expr{} + } + case 76: + //line parser.go.y:426 + { + if yylex.(*Lexer).PNewLine { + yylex.(*Lexer).TokenError(yyS[yypt-2].token, "ambiguous syntax (function call x new statement)") + } + yyVAL.exprlist = yyS[yypt-1].exprlist + } + case 77: + //line parser.go.y:432 + { + yyVAL.exprlist = []ast.Expr{yyS[yypt-0].expr} + } + case 78: + //line parser.go.y:435 + { + yyVAL.exprlist = []ast.Expr{yyS[yypt-0].expr} + } + case 79: + //line parser.go.y:440 + { + 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()) + } + case 80: + //line parser.go.y:447 + { + 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) + } + case 81: + //line parser.go.y:452 + { + 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) + } + case 82: + //line parser.go.y:459 + { + yyVAL.parlist = &ast.ParList{HasVargs: true, Names: []string{}} + } + case 83: + //line parser.go.y:462 + { + yyVAL.parlist = &ast.ParList{HasVargs: false, Names: []string{}} + yyVAL.parlist.Names = append(yyVAL.parlist.Names, yyS[yypt-0].namelist...) + } + case 84: + //line parser.go.y:466 + { + 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 + { + yyVAL.expr = &ast.TableExpr{Fields: []*ast.Field{}} + yyVAL.expr.SetLine(yyS[yypt-1].token.Pos.Line) + } + case 86: + //line parser.go.y:477 + { + yyVAL.expr = &ast.TableExpr{Fields: yyS[yypt-1].fieldlist} + yyVAL.expr.SetLine(yyS[yypt-2].token.Pos.Line) + } + case 87: + //line parser.go.y:484 + { + yyVAL.fieldlist = []*ast.Field{yyS[yypt-0].field} + } + case 88: + //line parser.go.y:487 + { + yyVAL.fieldlist = append(yyS[yypt-2].fieldlist, yyS[yypt-0].field) + } + case 89: + //line parser.go.y:490 + { + yyVAL.fieldlist = yyS[yypt-1].fieldlist + } + case 90: + //line parser.go.y:495 + { + 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) + } + case 91: + //line parser.go.y:499 + { + yyVAL.field = &ast.Field{Key: yyS[yypt-3].expr, Value: yyS[yypt-0].expr} + } + case 92: + //line parser.go.y:502 + { + yyVAL.field = &ast.Field{Value: yyS[yypt-0].expr} + } + case 93: + //line parser.go.y:507 + { + yyVAL.fieldsep = "," + } + case 94: + //line parser.go.y:510 + { + yyVAL.fieldsep = ";" + } + } + goto yystack /* stack new state and value */ +} diff --git a/vendor/github.com/yuin/gopher-lua/parse/parser.go.y b/vendor/github.com/yuin/gopher-lua/parse/parser.go.y new file mode 100644 index 00000000000..956133db292 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/parse/parser.go.y @@ -0,0 +1,524 @@ +%{ +package parse + +import ( + "github.com/yuin/gopher-lua/ast" +) +%} +%type chunk +%type chunk1 +%type block +%type stat +%type elseifs +%type laststat +%type funcname +%type funcname1 +%type varlist +%type var +%type namelist +%type exprlist +%type expr +%type string +%type prefixexp +%type functioncall +%type afunctioncall +%type args +%type function +%type funcbody +%type parlist +%type tableconstructor +%type fieldlist +%type field +%type fieldsep + +%union { + token ast.Token + + stmts []ast.Stmt + stmt ast.Stmt + + funcname *ast.FuncName + funcexpr *ast.FunctionExpr + + exprlist []ast.Expr + expr ast.Expr + + fieldlist []*ast.Field + field *ast.Field + fieldsep string + + namelist []string + parlist *ast.ParList +} + +/* Reserved words */ +%token TAnd TBreak TDo TElse TElseIf TEnd TFalse TFor TFunction TIf TIn TLocal TNil TNot TOr TReturn TRepeat TThen TTrue TUntil TWhile + +/* Literals */ +%token TEqeq TNeq TLte TGte T2Comma T3Comma TIdent TNumber TString '{' '(' + +/* Operators */ +%left TOr +%left TAnd +%left '>' '<' TGte TLte TEqeq TNeq +%right T2Comma +%left '+' '-' +%left '*' '/' '%' +%right UNARY /* not # -(unary) */ +%right '^' + +%% + +chunk: + chunk1 { + $$ = $1 + if l, ok := yylex.(*Lexer); ok { + l.Stmts = $$ + } + } | + chunk1 laststat { + $$ = append($1, $2) + if l, ok := yylex.(*Lexer); ok { + l.Stmts = $$ + } + } | + chunk1 laststat ';' { + $$ = append($1, $2) + if l, ok := yylex.(*Lexer); ok { + l.Stmts = $$ + } + } + +chunk1: + { + $$ = []ast.Stmt{} + } | + chunk1 stat { + $$ = append($1, $2) + } | + chunk1 ';' { + $$ = $1 + } + +block: + chunk { + $$ = $1 + } + +stat: + varlist '=' exprlist { + $$ = &ast.AssignStmt{Lhs: $1, Rhs: $3} + $$.SetLine($1[0].Line()) + } | + /* 'stat = functioncal' causes a reduce/reduce conflict */ + prefixexp { + if _, ok := $1.(*ast.FuncCallExpr); !ok { + yylex.(*Lexer).Error("parse error") + } else { + $$ = &ast.FuncCallStmt{Expr: $1} + $$.SetLine($1.Line()) + } + } | + TDo block TEnd { + $$ = &ast.DoBlockStmt{Stmts: $2} + $$.SetLine($1.Pos.Line) + $$.SetLastLine($3.Pos.Line) + } | + TWhile expr TDo block TEnd { + $$ = &ast.WhileStmt{Condition: $2, Stmts: $4} + $$.SetLine($1.Pos.Line) + $$.SetLastLine($5.Pos.Line) + } | + TRepeat block TUntil expr { + $$ = &ast.RepeatStmt{Condition: $4, Stmts: $2} + $$.SetLine($1.Pos.Line) + $$.SetLastLine($4.Line()) + } | + TIf expr TThen block elseifs TEnd { + $$ = &ast.IfStmt{Condition: $2, Then: $4} + cur := $$ + for _, elseif := range $5 { + cur.(*ast.IfStmt).Else = []ast.Stmt{elseif} + cur = elseif + } + $$.SetLine($1.Pos.Line) + $$.SetLastLine($6.Pos.Line) + } | + TIf expr TThen block elseifs TElse block TEnd { + $$ = &ast.IfStmt{Condition: $2, Then: $4} + cur := $$ + for _, elseif := range $5 { + cur.(*ast.IfStmt).Else = []ast.Stmt{elseif} + cur = elseif + } + cur.(*ast.IfStmt).Else = $7 + $$.SetLine($1.Pos.Line) + $$.SetLastLine($8.Pos.Line) + } | + TFor TIdent '=' expr ',' expr TDo block TEnd { + $$ = &ast.NumberForStmt{Name: $2.Str, Init: $4, Limit: $6, Stmts: $8} + $$.SetLine($1.Pos.Line) + $$.SetLastLine($9.Pos.Line) + } | + TFor TIdent '=' expr ',' expr ',' expr TDo block TEnd { + $$ = &ast.NumberForStmt{Name: $2.Str, Init: $4, Limit: $6, Step:$8, Stmts: $10} + $$.SetLine($1.Pos.Line) + $$.SetLastLine($11.Pos.Line) + } | + TFor namelist TIn exprlist TDo block TEnd { + $$ = &ast.GenericForStmt{Names:$2, Exprs:$4, Stmts: $6} + $$.SetLine($1.Pos.Line) + $$.SetLastLine($7.Pos.Line) + } | + TFunction funcname funcbody { + $$ = &ast.FuncDefStmt{Name: $2, Func: $3} + $$.SetLine($1.Pos.Line) + $$.SetLastLine($3.LastLine()) + } | + TLocal TFunction TIdent funcbody { + $$ = &ast.LocalAssignStmt{Names:[]string{$3.Str}, Exprs: []ast.Expr{$4}} + $$.SetLine($1.Pos.Line) + $$.SetLastLine($4.LastLine()) + } | + TLocal namelist '=' exprlist { + $$ = &ast.LocalAssignStmt{Names: $2, Exprs:$4} + $$.SetLine($1.Pos.Line) + } | + TLocal namelist { + $$ = &ast.LocalAssignStmt{Names: $2, Exprs:[]ast.Expr{}} + $$.SetLine($1.Pos.Line) + } + +elseifs: + { + $$ = []ast.Stmt{} + } | + elseifs TElseIf expr TThen block { + $$ = append($1, &ast.IfStmt{Condition: $3, Then: $5}) + $$[len($$)-1].SetLine($2.Pos.Line) + } + +laststat: + TReturn { + $$ = &ast.ReturnStmt{Exprs:nil} + $$.SetLine($1.Pos.Line) + } | + TReturn exprlist { + $$ = &ast.ReturnStmt{Exprs:$2} + $$.SetLine($1.Pos.Line) + } | + TBreak { + $$ = &ast.BreakStmt{} + $$.SetLine($1.Pos.Line) + } + +funcname: + funcname1 { + $$ = $1 + } | + funcname1 ':' TIdent { + $$ = &ast.FuncName{Func:nil, Receiver:$1.Func, Method: $3.Str} + } + +funcname1: + TIdent { + $$ = &ast.FuncName{Func: &ast.IdentExpr{Value:$1.Str}} + $$.Func.SetLine($1.Pos.Line) + } | + funcname1 '.' TIdent { + key:= &ast.StringExpr{Value:$3.Str} + key.SetLine($3.Pos.Line) + fn := &ast.AttrGetExpr{Object: $1.Func, Key: key} + fn.SetLine($3.Pos.Line) + $$ = &ast.FuncName{Func: fn} + } + +varlist: + var { + $$ = []ast.Expr{$1} + } | + varlist ',' var { + $$ = append($1, $3) + } + +var: + TIdent { + $$ = &ast.IdentExpr{Value:$1.Str} + $$.SetLine($1.Pos.Line) + } | + prefixexp '[' expr ']' { + $$ = &ast.AttrGetExpr{Object: $1, Key: $3} + $$.SetLine($1.Line()) + } | + prefixexp '.' TIdent { + key := &ast.StringExpr{Value:$3.Str} + key.SetLine($3.Pos.Line) + $$ = &ast.AttrGetExpr{Object: $1, Key: key} + $$.SetLine($1.Line()) + } + +namelist: + TIdent { + $$ = []string{$1.Str} + } | + namelist ',' TIdent { + $$ = append($1, $3.Str) + } + +exprlist: + expr { + $$ = []ast.Expr{$1} + } | + exprlist ',' expr { + $$ = append($1, $3) + } + +expr: + TNil { + $$ = &ast.NilExpr{} + $$.SetLine($1.Pos.Line) + } | + TFalse { + $$ = &ast.FalseExpr{} + $$.SetLine($1.Pos.Line) + } | + TTrue { + $$ = &ast.TrueExpr{} + $$.SetLine($1.Pos.Line) + } | + TNumber { + $$ = &ast.NumberExpr{Value: $1.Str} + $$.SetLine($1.Pos.Line) + } | + T3Comma { + $$ = &ast.Comma3Expr{} + $$.SetLine($1.Pos.Line) + } | + function { + $$ = $1 + } | + prefixexp { + $$ = $1 + } | + string { + $$ = $1 + } | + tableconstructor { + $$ = $1 + } | + expr TOr expr { + $$ = &ast.LogicalOpExpr{Lhs: $1, Operator: "or", Rhs: $3} + $$.SetLine($1.Line()) + } | + expr TAnd expr { + $$ = &ast.LogicalOpExpr{Lhs: $1, Operator: "and", Rhs: $3} + $$.SetLine($1.Line()) + } | + expr '>' expr { + $$ = &ast.RelationalOpExpr{Lhs: $1, Operator: ">", Rhs: $3} + $$.SetLine($1.Line()) + } | + expr '<' expr { + $$ = &ast.RelationalOpExpr{Lhs: $1, Operator: "<", Rhs: $3} + $$.SetLine($1.Line()) + } | + expr TGte expr { + $$ = &ast.RelationalOpExpr{Lhs: $1, Operator: ">=", Rhs: $3} + $$.SetLine($1.Line()) + } | + expr TLte expr { + $$ = &ast.RelationalOpExpr{Lhs: $1, Operator: "<=", Rhs: $3} + $$.SetLine($1.Line()) + } | + expr TEqeq expr { + $$ = &ast.RelationalOpExpr{Lhs: $1, Operator: "==", Rhs: $3} + $$.SetLine($1.Line()) + } | + expr TNeq expr { + $$ = &ast.RelationalOpExpr{Lhs: $1, Operator: "~=", Rhs: $3} + $$.SetLine($1.Line()) + } | + expr T2Comma expr { + $$ = &ast.StringConcatOpExpr{Lhs: $1, Rhs: $3} + $$.SetLine($1.Line()) + } | + expr '+' expr { + $$ = &ast.ArithmeticOpExpr{Lhs: $1, Operator: "+", Rhs: $3} + $$.SetLine($1.Line()) + } | + expr '-' expr { + $$ = &ast.ArithmeticOpExpr{Lhs: $1, Operator: "-", Rhs: $3} + $$.SetLine($1.Line()) + } | + expr '*' expr { + $$ = &ast.ArithmeticOpExpr{Lhs: $1, Operator: "*", Rhs: $3} + $$.SetLine($1.Line()) + } | + expr '/' expr { + $$ = &ast.ArithmeticOpExpr{Lhs: $1, Operator: "/", Rhs: $3} + $$.SetLine($1.Line()) + } | + expr '%' expr { + $$ = &ast.ArithmeticOpExpr{Lhs: $1, Operator: "%", Rhs: $3} + $$.SetLine($1.Line()) + } | + expr '^' expr { + $$ = &ast.ArithmeticOpExpr{Lhs: $1, Operator: "^", Rhs: $3} + $$.SetLine($1.Line()) + } | + '-' expr %prec UNARY { + $$ = &ast.UnaryMinusOpExpr{Expr: $2} + $$.SetLine($2.Line()) + } | + TNot expr %prec UNARY { + $$ = &ast.UnaryNotOpExpr{Expr: $2} + $$.SetLine($2.Line()) + } | + '#' expr %prec UNARY { + $$ = &ast.UnaryLenOpExpr{Expr: $2} + $$.SetLine($2.Line()) + } + +string: + TString { + $$ = &ast.StringExpr{Value: $1.Str} + $$.SetLine($1.Pos.Line) + } + +prefixexp: + var { + $$ = $1 + } | + afunctioncall { + $$ = $1 + } | + functioncall { + $$ = $1 + } | + '(' expr ')' { + $$ = $2 + $$.SetLine($1.Pos.Line) + } + +afunctioncall: + '(' functioncall ')' { + $2.(*ast.FuncCallExpr).AdjustRet = true + $$ = $2 + } + +functioncall: + prefixexp args { + $$ = &ast.FuncCallExpr{Func: $1, Args: $2} + $$.SetLine($1.Line()) + } | + prefixexp ':' TIdent args { + $$ = &ast.FuncCallExpr{Method: $3.Str, Receiver: $1, Args: $4} + $$.SetLine($1.Line()) + } + +args: + '(' ')' { + if yylex.(*Lexer).PNewLine { + yylex.(*Lexer).TokenError($1, "ambiguous syntax (function call x new statement)") + } + $$ = []ast.Expr{} + } | + '(' exprlist ')' { + if yylex.(*Lexer).PNewLine { + yylex.(*Lexer).TokenError($1, "ambiguous syntax (function call x new statement)") + } + $$ = $2 + } | + tableconstructor { + $$ = []ast.Expr{$1} + } | + string { + $$ = []ast.Expr{$1} + } + +function: + TFunction funcbody { + $$ = &ast.FunctionExpr{ParList:$2.ParList, Stmts: $2.Stmts} + $$.SetLine($1.Pos.Line) + $$.SetLastLine($2.LastLine()) + } + +funcbody: + '(' parlist ')' block TEnd { + $$ = &ast.FunctionExpr{ParList: $2, Stmts: $4} + $$.SetLine($1.Pos.Line) + $$.SetLastLine($5.Pos.Line) + } | + '(' ')' block TEnd { + $$ = &ast.FunctionExpr{ParList: &ast.ParList{HasVargs: false, Names: []string{}}, Stmts: $3} + $$.SetLine($1.Pos.Line) + $$.SetLastLine($4.Pos.Line) + } + +parlist: + T3Comma { + $$ = &ast.ParList{HasVargs: true, Names: []string{}} + } | + namelist { + $$ = &ast.ParList{HasVargs: false, Names: []string{}} + $$.Names = append($$.Names, $1...) + } | + namelist ',' T3Comma { + $$ = &ast.ParList{HasVargs: true, Names: []string{}} + $$.Names = append($$.Names, $1...) + } + + +tableconstructor: + '{' '}' { + $$ = &ast.TableExpr{Fields: []*ast.Field{}} + $$.SetLine($1.Pos.Line) + } | + '{' fieldlist '}' { + $$ = &ast.TableExpr{Fields: $2} + $$.SetLine($1.Pos.Line) + } + + +fieldlist: + field { + $$ = []*ast.Field{$1} + } | + fieldlist fieldsep field { + $$ = append($1, $3) + } | + fieldlist fieldsep { + $$ = $1 + } + +field: + TIdent '=' expr { + $$ = &ast.Field{Key: &ast.StringExpr{Value:$1.Str}, Value: $3} + $$.Key.SetLine($1.Pos.Line) + } | + '[' expr ']' '=' expr { + $$ = &ast.Field{Key: $2, Value: $5} + } | + expr { + $$ = &ast.Field{Value: $1} + } + +fieldsep: + ',' { + $$ = "," + } | + ';' { + $$ = ";" + } + +%% + +func TokenName(c int) string { + if c >= TAnd && c-TAnd < len(yyToknames) { + if yyToknames[c-TAnd] != "" { + return yyToknames[c-TAnd] + } + } + return string([]byte{byte(c)}) +} + diff --git a/vendor/github.com/yuin/gopher-lua/pm/pm.go b/vendor/github.com/yuin/gopher-lua/pm/pm.go new file mode 100644 index 00000000000..e15bc21005d --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/pm/pm.go @@ -0,0 +1,638 @@ +// Lua pattern match functions for Go +package pm + +import ( + "fmt" +) + +const EOS = -1 +const _UNKNOWN = -2 + +/* Error {{{ */ + +type Error struct { + Pos int + Message string +} + +func newError(pos int, message string, args ...interface{}) *Error { + if len(args) == 0 { + return &Error{pos, message} + } + return &Error{pos, fmt.Sprintf(message, args...)} +} + +func (e *Error) Error() string { + switch e.Pos { + case EOS: + return fmt.Sprintf("%s at EOS", e.Message) + case _UNKNOWN: + return fmt.Sprintf("%s", e.Message) + default: + return fmt.Sprintf("%s at %d", e.Message, e.Pos) + } +} + +/* }}} */ + +/* MatchData {{{ */ + +type MatchData struct { + // captured positions + // layout + // xxxx xxxx xxxx xxx0 : caputured positions + // xxxx xxxx xxxx xxx1 : position captured positions + captures []uint32 +} + +func newMatchState() *MatchData { return &MatchData{[]uint32{}} } + +func (st *MatchData) addPosCapture(s, pos int) { + for s+1 >= len(st.captures) { + st.captures = append(st.captures, 0) + } + st.captures[s] = (uint32(pos) << 1) | 1 + st.captures[s+1] = (uint32(pos) << 1) | 1 +} + +func (st *MatchData) setCapture(s, pos int) uint32 { + for s >= len(st.captures) { + st.captures = append(st.captures, 0) + } + v := st.captures[s] + st.captures[s] = (uint32(pos) << 1) + return v +} + +func (st *MatchData) restoreCapture(s int, pos uint32) { st.captures[s] = pos } + +func (st *MatchData) CaptureLength() int { return len(st.captures) } + +func (st *MatchData) IsPosCapture(idx int) bool { return (st.captures[idx] & 1) == 1 } + +func (st *MatchData) Capture(idx int) int { return int(st.captures[idx] >> 1) } + +/* }}} */ + +/* scanner {{{ */ + +type scannerState struct { + Pos int + started bool +} + +type scanner struct { + src []byte + State scannerState + saved scannerState +} + +func newScanner(src []byte) *scanner { + return &scanner{ + src: src, + State: scannerState{ + Pos: 0, + started: false, + }, + saved: scannerState{}, + } +} + +func (sc *scanner) Length() int { return len(sc.src) } + +func (sc *scanner) Next() int { + if !sc.State.started { + sc.State.started = true + if len(sc.src) == 0 { + sc.State.Pos = EOS + } + } else { + sc.State.Pos = sc.NextPos() + } + if sc.State.Pos == EOS { + return EOS + } + return int(sc.src[sc.State.Pos]) +} + +func (sc *scanner) CurrentPos() int { + return sc.State.Pos +} + +func (sc *scanner) NextPos() int { + if sc.State.Pos == EOS || sc.State.Pos >= len(sc.src)-1 { + return EOS + } + if !sc.State.started { + return 0 + } else { + return sc.State.Pos + 1 + } +} + +func (sc *scanner) Peek() int { + cureof := sc.State.Pos == EOS + ch := sc.Next() + if !cureof { + if sc.State.Pos == EOS { + sc.State.Pos = len(sc.src) - 1 + } else { + sc.State.Pos-- + if sc.State.Pos < 0 { + sc.State.Pos = 0 + sc.State.started = false + } + } + } + return ch +} + +func (sc *scanner) Save() { sc.saved = sc.State } + +func (sc *scanner) Restore() { sc.State = sc.saved } + +/* }}} */ + +/* bytecode {{{ */ + +type opCode int + +const ( + opChar opCode = iota + opMatch + opTailMatch + opJmp + opSplit + opSave + opPSave + opBrace + opNumber +) + +type inst struct { + OpCode opCode + Class class + Operand1 int + Operand2 int +} + +/* }}} */ + +/* classes {{{ */ + +type class interface { + Matches(ch int) bool +} + +type dotClass struct{} + +func (pn *dotClass) Matches(ch int) bool { return true } + +type charClass struct { + Ch int +} + +func (pn *charClass) Matches(ch int) bool { return pn.Ch == ch } + +type singleClass struct { + Class int +} + +func (pn *singleClass) Matches(ch int) bool { + ret := false + switch pn.Class { + case 'a', 'A': + ret = 'A' <= ch && ch <= 'Z' || 'a' <= ch && ch <= 'z' + case 'c', 'C': + ret = (0x00 <= ch && ch <= 0x1F) || ch == 0x7F + case 'd', 'D': + ret = '0' <= ch && ch <= '9' + 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) + case 's', 'S': + switch ch { + case ' ', '\f', '\n', '\r', '\t', '\v': + ret = true + } + case 'u', 'U': + ret = 'A' <= ch && ch <= 'Z' + case 'w', 'W': + ret = '0' <= ch && ch <= '9' || 'A' <= ch && ch <= 'Z' || 'a' <= ch && ch <= 'z' + case 'x', 'X': + ret = '0' <= ch && ch <= '9' || 'a' <= ch && ch <= 'f' || 'A' <= ch && ch <= 'F' + case 'z', 'Z': + ret = ch == 0 + default: + return ch == pn.Class + } + if 'A' <= pn.Class && pn.Class <= 'Z' { + return !ret + } + return ret +} + +type setClass struct { + IsNot bool + Classes []class +} + +func (pn *setClass) Matches(ch int) bool { + for _, class := range pn.Classes { + if class.Matches(ch) { + return !pn.IsNot + } + } + return pn.IsNot +} + +type rangeClass struct { + Begin class + End class +} + +func (pn *rangeClass) Matches(ch int) bool { + switch begin := pn.Begin.(type) { + case *charClass: + end, ok := pn.End.(*charClass) + if !ok { + return false + } + return begin.Ch <= ch && ch <= end.Ch + } + return false +} + +// }}} + +// patterns {{{ + +type pattern interface{} + +type singlePattern struct { + Class class +} + +type seqPattern struct { + MustHead bool + MustTail bool + Patterns []pattern +} + +type repeatPattern struct { + Type int + Class class +} + +type posCapPattern struct{} + +type capPattern struct { + Pattern pattern +} + +type numberPattern struct { + N int +} + +type bracePattern struct { + Begin int + End int +} + +// }}} + +/* parse {{{ */ + +func parseClass(sc *scanner, allowset bool) class { + ch := sc.Next() + switch ch { + case '%': + return &singleClass{sc.Next()} + case '.': + if allowset { + return &dotClass{} + } + return &charClass{ch} + case '[': + if allowset { + return parseClassSet(sc) + } + return &charClass{ch} + //case '^' '$', '(', ')', ']', '*', '+', '-', '?': + // panic(newError(sc.CurrentPos(), "invalid %c", ch)) + case EOS: + panic(newError(sc.CurrentPos(), "unexpected EOS")) + default: + return &charClass{ch} + } +} + +func parseClassSet(sc *scanner) class { + set := &setClass{false, []class{}} + if sc.Peek() == '^' { + set.IsNot = true + sc.Next() + } + isrange := false + for { + ch := sc.Peek() + switch ch { + // case '[': + // panic(newError(sc.CurrentPos(), "'[' can not be nested")) + case EOS: + panic(newError(sc.CurrentPos(), "unexpected EOS")) + case ']': + if len(set.Classes) > 0 { + sc.Next() + goto exit + } + fallthrough + case '-': + if len(set.Classes) > 0 { + sc.Next() + isrange = true + continue + } + fallthrough + default: + set.Classes = append(set.Classes, parseClass(sc, false)) + } + if isrange { + begin := set.Classes[len(set.Classes)-2] + end := set.Classes[len(set.Classes)-1] + set.Classes = set.Classes[0 : len(set.Classes)-2] + set.Classes = append(set.Classes, &rangeClass{begin, end}) + isrange = false + } + } +exit: + if isrange { + set.Classes = append(set.Classes, &charClass{'-'}) + } + + return set +} + +func parsePattern(sc *scanner, toplevel bool) *seqPattern { + pat := &seqPattern{} + if toplevel { + if sc.Peek() == '^' { + sc.Next() + pat.MustHead = true + } + } + for { + ch := sc.Peek() + switch ch { + case '%': + sc.Save() + sc.Next() + switch sc.Peek() { + case '0': + panic(newError(sc.CurrentPos(), "invalid capture index")) + case '1', '2', '3', '4', '5', '6', '7', '8', '9': + pat.Patterns = append(pat.Patterns, &numberPattern{sc.Next() - 48}) + case 'b': + sc.Next() + pat.Patterns = append(pat.Patterns, &bracePattern{sc.Next(), sc.Next()}) + default: + sc.Restore() + pat.Patterns = append(pat.Patterns, &singlePattern{parseClass(sc, true)}) + } + case '.', '[', ']': + pat.Patterns = append(pat.Patterns, &singlePattern{parseClass(sc, true)}) + //case ']': + // panic(newError(sc.CurrentPos(), "invalid ']'")) + case ')': + if toplevel { + panic(newError(sc.CurrentPos(), "invalid ')'")) + } + return pat + case '(': + sc.Next() + if sc.Peek() == ')' { + sc.Next() + pat.Patterns = append(pat.Patterns, &posCapPattern{}) + } else { + ret := &capPattern{parsePattern(sc, false)} + if sc.Peek() != ')' { + panic(newError(sc.CurrentPos(), "unfinished capture")) + } + sc.Next() + pat.Patterns = append(pat.Patterns, ret) + } + case '*', '+', '-', '?': + sc.Next() + if len(pat.Patterns) > 0 { + spat, ok := pat.Patterns[len(pat.Patterns)-1].(*singlePattern) + if ok { + pat.Patterns = pat.Patterns[0 : len(pat.Patterns)-1] + pat.Patterns = append(pat.Patterns, &repeatPattern{ch, spat.Class}) + continue + } + } + pat.Patterns = append(pat.Patterns, &singlePattern{&charClass{ch}}) + case '$': + if toplevel && (sc.NextPos() == sc.Length()-1 || sc.NextPos() == EOS) { + pat.MustTail = true + } else { + pat.Patterns = append(pat.Patterns, &singlePattern{&charClass{ch}}) + } + sc.Next() + case EOS: + sc.Next() + goto exit + default: + sc.Next() + pat.Patterns = append(pat.Patterns, &singlePattern{&charClass{ch}}) + } + } +exit: + return pat +} + +type iptr struct { + insts []inst + capture int +} + +func compilePattern(p pattern, ps ...*iptr) []inst { + var ptr *iptr + toplevel := false + if len(ps) == 0 { + toplevel = true + ptr = &iptr{[]inst{inst{opSave, nil, 0, -1}}, 2} + } else { + ptr = ps[0] + } + switch pat := p.(type) { + case *singlePattern: + ptr.insts = append(ptr.insts, inst{opChar, pat.Class, -1, -1}) + case *seqPattern: + for _, cp := range pat.Patterns { + compilePattern(cp, ptr) + } + case *repeatPattern: + idx := len(ptr.insts) + switch pat.Type { + case '*': + ptr.insts = append(ptr.insts, + inst{opSplit, nil, idx + 1, idx + 3}, + inst{opChar, pat.Class, -1, -1}, + inst{opJmp, nil, idx, -1}) + case '+': + ptr.insts = append(ptr.insts, + inst{opChar, pat.Class, -1, -1}, + inst{opSplit, nil, idx, idx + 2}) + case '-': + ptr.insts = append(ptr.insts, + inst{opSplit, nil, idx + 3, idx + 1}, + inst{opChar, pat.Class, -1, -1}, + inst{opJmp, nil, idx, -1}) + case '?': + ptr.insts = append(ptr.insts, + inst{opSplit, nil, idx + 1, idx + 2}, + inst{opChar, pat.Class, -1, -1}) + } + case *posCapPattern: + ptr.insts = append(ptr.insts, inst{opPSave, nil, ptr.capture, -1}) + ptr.capture += 2 + case *capPattern: + c0, c1 := ptr.capture, ptr.capture+1 + ptr.capture += 2 + ptr.insts = append(ptr.insts, inst{opSave, nil, c0, -1}) + compilePattern(pat.Pattern, ptr) + ptr.insts = append(ptr.insts, inst{opSave, nil, c1, -1}) + case *bracePattern: + ptr.insts = append(ptr.insts, inst{opBrace, nil, pat.Begin, pat.End}) + case *numberPattern: + ptr.insts = append(ptr.insts, inst{opNumber, nil, pat.N, -1}) + } + if toplevel { + if p.(*seqPattern).MustTail { + ptr.insts = append(ptr.insts, inst{opSave, nil, 1, -1}, inst{opTailMatch, nil, -1, -1}) + } + ptr.insts = append(ptr.insts, inst{opSave, nil, 1, -1}, inst{opMatch, nil, -1, -1}) + } + return ptr.insts +} + +/* }}} parse */ + +/* VM {{{ */ + +// Simple recursive virtual machine based on the +// "Regular Expression Matching: the Virtual Machine Approach" (https://swtch.com/~rsc/regexp/regexp2.html) +func recursiveVM(src []byte, insts []inst, pc, sp int, ms ...*MatchData) (bool, int, *MatchData) { + var m *MatchData + if len(ms) == 0 { + m = newMatchState() + } else { + m = ms[0] + } +redo: + inst := insts[pc] + switch inst.OpCode { + case opChar: + if sp >= len(src) || !inst.Class.Matches(int(src[sp])) { + return false, sp, m + } + pc++ + sp++ + goto redo + case opMatch: + return true, sp, m + case opTailMatch: + return sp >= len(src), sp, m + case opJmp: + pc = inst.Operand1 + goto redo + case opSplit: + if ok, nsp, _ := recursiveVM(src, insts, inst.Operand1, sp, m); ok { + return true, nsp, m + } + pc = inst.Operand2 + goto redo + case opSave: + s := m.setCapture(inst.Operand1, sp) + if ok, nsp, _ := recursiveVM(src, insts, pc+1, sp, m); ok { + return true, nsp, m + } + m.restoreCapture(inst.Operand1, s) + return false, sp, m + case opPSave: + m.addPosCapture(inst.Operand1, sp+1) + pc++ + goto redo + case opBrace: + if sp >= len(src) || int(src[sp]) != inst.Operand1 { + return false, sp, m + } + count := 1 + for sp = sp + 1; sp < len(src); sp++ { + if int(src[sp]) == inst.Operand2 { + count-- + } + if count == 0 { + pc++ + sp++ + goto redo + } + if int(src[sp]) == inst.Operand1 { + count++ + } + } + return false, sp, m + case opNumber: + idx := inst.Operand1 * 2 + if idx >= m.CaptureLength()-1 { + panic(newError(_UNKNOWN, "invalid capture index")) + } + capture := src[m.Capture(idx):m.Capture(idx+1)] + for i := 0; i < len(capture); i++ { + if i+sp >= len(src) || capture[i] != src[i+sp] { + return false, sp, m + } + } + pc++ + sp += len(capture) + goto redo + } + panic("should not reach here") +} + +/* }}} */ + +/* API {{{ */ + +func Find(p string, src []byte, offset, limit int) (matches []*MatchData, err error) { + defer func() { + if v := recover(); v != nil { + if perr, ok := v.(*Error); ok { + err = perr + } else { + panic(v) + } + } + }() + pat := parsePattern(newScanner([]byte(p)), true) + insts := compilePattern(pat) + matches = []*MatchData{} + for sp := offset; sp <= len(src); { + ok, nsp, ms := recursiveVM(src, insts, 0, sp) + sp++ + if ok { + if sp < nsp { + sp = nsp + } + matches = append(matches, ms) + } + if len(matches) == limit || pat.MustHead { + break + } + } + return +} + +/* }}} */ diff --git a/vendor/github.com/yuin/gopher-lua/state.go b/vendor/github.com/yuin/gopher-lua/state.go new file mode 100644 index 00000000000..b8d084a62e8 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/state.go @@ -0,0 +1,2236 @@ +package lua + +//////////////////////////////////////////////////////// +// This file was generated by go-inline. DO NOT EDIT. // +//////////////////////////////////////////////////////// + +import ( + "context" + "fmt" + "io" + "math" + "os" + "runtime" + "strings" + "sync" + "sync/atomic" + "time" + + "github.com/yuin/gopher-lua/parse" +) + +const MultRet = -1 +const RegistryIndex = -10000 +const EnvironIndex = -10001 +const GlobalsIndex = -10002 + +/* ApiError {{{ */ + +type ApiError struct { + Type ApiErrorType + Object LValue + StackTrace string + // Underlying error. This attribute is set only if the Type is ApiErrorFile or ApiErrorSyntax + Cause error +} + +func newApiError(code ApiErrorType, object LValue) *ApiError { + return &ApiError{code, object, "", nil} +} + +func newApiErrorS(code ApiErrorType, message string) *ApiError { + return newApiError(code, LString(message)) +} + +func newApiErrorE(code ApiErrorType, err error) *ApiError { + return &ApiError{code, LString(err.Error()), "", err} +} + +func (e *ApiError) Error() string { + if len(e.StackTrace) > 0 { + return fmt.Sprintf("%s\n%s", e.Object.String(), e.StackTrace) + } + return e.Object.String() +} + +type ApiErrorType int + +const ( + ApiErrorSyntax ApiErrorType = iota + ApiErrorFile + ApiErrorRun + ApiErrorError + ApiErrorPanic +) + +/* }}} */ + +/* ResumeState {{{ */ + +type ResumeState int + +const ( + ResumeOK ResumeState = iota + ResumeYield + ResumeError +) + +/* }}} */ + +/* P {{{ */ + +type P struct { + Fn LValue + NRet int + Protect bool + Handler *LFunction +} + +/* }}} */ + +/* Options {{{ */ + +// Options is a configuration that is used to create a new LState. +type Options struct { + // Call stack size. This defaults to `lua.CallStackSize`. + CallStackSize int + // Data stack size. This defaults to `lua.RegistrySize`. + RegistrySize int + // Allow the registry to grow from the registry size specified up to a value of RegistryMaxSize. A value of 0 + // indicates no growth is permitted. The registry will not shrink again after any growth. + RegistryMaxSize int + // If growth is enabled, step up by an additional `RegistryGrowStep` each time to avoid having to resize too often. + // This defaults to `lua.RegistryGrowStep` + RegistryGrowStep int + // Controls whether or not libraries are opened by default + SkipOpenLibs bool + // Tells whether a Go stacktrace should be included in a Lua stacktrace when panics occur. + IncludeGoStackTrace bool + // If `MinimizeStackMemory` is set, the call stack will be automatically grown or shrank up to a limit of + // `CallStackSize` in order to minimize memory usage. This does incur a slight performance penalty. + MinimizeStackMemory bool +} + +/* }}} */ + +/* Debug {{{ */ + +type Debug struct { + frame *callFrame + Name string + What string + Source string + CurrentLine int + NUpvalues int + LineDefined int + LastLineDefined int +} + +/* }}} */ + +/* callFrame {{{ */ + +type callFrame struct { + Idx int + Fn *LFunction + Parent *callFrame + Pc int + Base int + LocalBase int + ReturnBase int + NArgs int + NRet int + TailCall int +} + +type callFrameStack interface { + Push(v callFrame) + Pop() *callFrame + Last() *callFrame + + SetSp(sp int) + Sp() int + At(sp int) *callFrame + + IsFull() bool + IsEmpty() bool + + FreeAll() +} + +type fixedCallFrameStack struct { + array []callFrame + sp int +} + +func newFixedCallFrameStack(size int) callFrameStack { + return &fixedCallFrameStack{ + array: make([]callFrame, size), + sp: 0, + } +} + +func (cs *fixedCallFrameStack) IsEmpty() bool { return cs.sp == 0 } + +func (cs *fixedCallFrameStack) IsFull() bool { return cs.sp == len(cs.array) } + +func (cs *fixedCallFrameStack) Clear() { + cs.sp = 0 +} + +func (cs *fixedCallFrameStack) Push(v callFrame) { + cs.array[cs.sp] = v + cs.array[cs.sp].Idx = cs.sp + cs.sp++ +} + +func (cs *fixedCallFrameStack) Sp() int { + return cs.sp +} + +func (cs *fixedCallFrameStack) SetSp(sp int) { + cs.sp = sp +} + +func (cs *fixedCallFrameStack) Last() *callFrame { + if cs.sp == 0 { + return nil + } + return &cs.array[cs.sp-1] +} + +func (cs *fixedCallFrameStack) At(sp int) *callFrame { + return &cs.array[sp] +} + +func (cs *fixedCallFrameStack) Pop() *callFrame { + cs.sp-- + return &cs.array[cs.sp] +} + +func (cs *fixedCallFrameStack) FreeAll() { + // nothing to do for fixed callframestack +} + +// FramesPerSegment should be a power of 2 constant for performance reasons. It will allow the go compiler to change +// the divs and mods into bitshifts. Max is 256 due to current use of uint8 to count how many frames in a segment are +// used. +const FramesPerSegment = 8 + +type callFrameStackSegment struct { + array [FramesPerSegment]callFrame +} +type segIdx uint16 +type autoGrowingCallFrameStack struct { + segments []*callFrameStackSegment + segIdx segIdx + // segSp is the number of frames in the current segment which are used. Full 'sp' value is segIdx * FramesPerSegment + segSp. + // It points to the next stack slot to use, so 0 means to use the 0th element in the segment, and a value of + // FramesPerSegment indicates that the segment is full and cannot accommodate another frame. + segSp uint8 +} + +var segmentPool sync.Pool + +func newCallFrameStackSegment() *callFrameStackSegment { + seg := segmentPool.Get() + if seg == nil { + return &callFrameStackSegment{} + } + return seg.(*callFrameStackSegment) +} + +func freeCallFrameStackSegment(seg *callFrameStackSegment) { + segmentPool.Put(seg) +} + +// newCallFrameStack allocates a new stack for a lua state, which will auto grow up to a max size of at least maxSize. +// it will actually grow up to the next segment size multiple after maxSize, where the segment size is dictated by +// FramesPerSegment. +func newAutoGrowingCallFrameStack(maxSize int) callFrameStack { + cs := &autoGrowingCallFrameStack{ + segments: make([]*callFrameStackSegment, (maxSize+(FramesPerSegment-1))/FramesPerSegment), + segIdx: 0, + } + cs.segments[0] = newCallFrameStackSegment() + return cs +} + +func (cs *autoGrowingCallFrameStack) IsEmpty() bool { + return cs.segIdx == 0 && cs.segSp == 0 +} + +// IsFull returns true if the stack cannot receive any more stack pushes without overflowing +func (cs *autoGrowingCallFrameStack) IsFull() bool { + return int(cs.segIdx) == len(cs.segments) && cs.segSp >= FramesPerSegment +} + +func (cs *autoGrowingCallFrameStack) Clear() { + for i := segIdx(1); i <= cs.segIdx; i++ { + freeCallFrameStackSegment(cs.segments[i]) + cs.segments[i] = nil + } + cs.segIdx = 0 + cs.segSp = 0 +} + +func (cs *autoGrowingCallFrameStack) FreeAll() { + for i := segIdx(0); i <= cs.segIdx; i++ { + freeCallFrameStackSegment(cs.segments[i]) + cs.segments[i] = nil + } +} + +// Push pushes the passed callFrame onto the stack. it panics if the stack is full, caller should call IsFull() before +// invoking this to avoid this. +func (cs *autoGrowingCallFrameStack) Push(v callFrame) { + curSeg := cs.segments[cs.segIdx] + if cs.segSp >= FramesPerSegment { + // segment full, push new segment if allowed + if cs.segIdx < segIdx(len(cs.segments)-1) { + curSeg = newCallFrameStackSegment() + cs.segIdx++ + cs.segments[cs.segIdx] = curSeg + cs.segSp = 0 + } else { + panic("lua callstack overflow") + } + } + curSeg.array[cs.segSp] = v + curSeg.array[cs.segSp].Idx = int(cs.segSp) + FramesPerSegment*int(cs.segIdx) + cs.segSp++ +} + +// Sp retrieves the current stack depth, which is the number of frames currently pushed on the stack. +func (cs *autoGrowingCallFrameStack) Sp() int { + return int(cs.segSp) + int(cs.segIdx)*FramesPerSegment +} + +// SetSp can be used to rapidly unwind the stack, freeing all stack frames on the way. It should not be used to +// allocate new stack space, use Push() for that. +func (cs *autoGrowingCallFrameStack) SetSp(sp int) { + desiredSegIdx := segIdx(sp / FramesPerSegment) + desiredFramesInLastSeg := uint8(sp % FramesPerSegment) + for { + if cs.segIdx <= desiredSegIdx { + break + } + freeCallFrameStackSegment(cs.segments[cs.segIdx]) + cs.segments[cs.segIdx] = nil + cs.segIdx-- + } + cs.segSp = desiredFramesInLastSeg +} + +func (cs *autoGrowingCallFrameStack) Last() *callFrame { + curSeg := cs.segments[cs.segIdx] + segSp := cs.segSp + if segSp == 0 { + if cs.segIdx == 0 { + return nil + } + curSeg = cs.segments[cs.segIdx-1] + segSp = FramesPerSegment + } + return &curSeg.array[segSp-1] +} + +func (cs *autoGrowingCallFrameStack) At(sp int) *callFrame { + segIdx := segIdx(sp / FramesPerSegment) + frameIdx := uint8(sp % FramesPerSegment) + return &cs.segments[segIdx].array[frameIdx] +} + +// Pop pops off the most recent stack frame and returns it +func (cs *autoGrowingCallFrameStack) Pop() *callFrame { + curSeg := cs.segments[cs.segIdx] + if cs.segSp == 0 { + if cs.segIdx == 0 { + // stack empty + return nil + } + freeCallFrameStackSegment(curSeg) + cs.segments[cs.segIdx] = nil + cs.segIdx-- + cs.segSp = FramesPerSegment + curSeg = cs.segments[cs.segIdx] + } + cs.segSp-- + return &curSeg.array[cs.segSp] +} + +/* }}} */ + +/* registry {{{ */ + +type registryHandler interface { + registryOverflow() +} +type registry struct { + array []LValue + top int + growBy int + maxSize int + alloc *allocator + handler registryHandler +} + +func newRegistry(handler registryHandler, initialSize int, growBy int, maxSize int, alloc *allocator) *registry { + return ®istry{make([]LValue, initialSize), 0, growBy, maxSize, alloc, handler} +} + +func (rg *registry) checkSize(requiredSize int) { // +inline-start + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } +} // +inline-end + +func (rg *registry) resize(requiredSize int) { // +inline-start + newSize := requiredSize + rg.growBy // give some padding + if newSize > rg.maxSize { + newSize = rg.maxSize + } + if newSize < requiredSize { + rg.handler.registryOverflow() + return + } + rg.forceResize(newSize) +} // +inline-end + +func (rg *registry) forceResize(newSize int) { + newSlice := make([]LValue, newSize) + 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) { + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := top + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + oldtop := rg.top + rg.top = top + for i := oldtop; 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] + for i := range nilRange { + nilRange[i] = nil + } + } + //for i := rg.top; i < oldtop; i++ { + // rg.array[i] = LNil + //} +} + +func (rg *registry) Top() int { + return rg.top +} + +func (rg *registry) Push(v LValue) { + newSize := rg.top + 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[rg.top] = v + rg.top++ +} + +func (rg *registry) Pop() LValue { + v := rg.array[rg.top-1] + rg.array[rg.top-1] = LNil + rg.top-- + return v +} + +func (rg *registry) Get(reg int) LValue { + return rg.array[reg] +} + +// CopyRange will move a section of values from index `start` to index `regv` +// It will move `n` values. +// `limit` specifies the maximum end range that can be copied from. If it's set to -1, then it defaults to stopping at +// the top of the registry (values beyond the top are not initialized, so if specifying an alternative `limit` you should +// pass a value <= rg.top. +// If start+n is beyond the limit, then nil values will be copied to the destination slots. +// After the copy, the registry is truncated to be at the end of the copied range, ie the original of the copied values +// are nilled out. (So top will be regv+n) +// CopyRange should ideally be renamed to MoveRange. +func (rg *registry) CopyRange(regv, start, limit, n int) { // +inline-start + newSize := regv + n + // 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) + } + } + if limit == -1 || limit > rg.top { + limit = rg.top + } + for i := 0; i < n; i++ { + srcIdx := start + i + if srcIdx >= limit || srcIdx < 0 { + rg.array[regv+i] = LNil + } else { + rg.array[regv+i] = rg.array[srcIdx] + } + } + + // 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 + oldtop := rg.top + rg.top = regv + n + if rg.top < oldtop { + nilRange := rg.array[rg.top:oldtop] + for i := range nilRange { + nilRange[i] = nil + } + } +} // +inline-end + +// FillNil fills the registry with nil values from regm to regm+n and then sets the registry top to regm+n +func (rg *registry) FillNil(regm, n int) { // +inline-start + newSize := regm + n + // 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) + } + } + for i := 0; i < n; i++ { + rg.array[regm+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 + oldtop := rg.top + rg.top = regm + n + if rg.top < oldtop { + nilRange := rg.array[rg.top:oldtop] + for i := range nilRange { + nilRange[i] = nil + } + } +} // +inline-end + +func (rg *registry) Insert(value LValue, reg int) { + top := rg.Top() + if reg >= top { + 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)) + } + rg.Set(reg, value) +} + +func (rg *registry) Set(reg int, val LValue) { + newSize := reg + 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[reg] = val + if reg >= rg.top { + rg.top = reg + 1 + } +} + +func (rg *registry) SetNumber(reg int, val LNumber) { + newSize := reg + 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[reg] = rg.alloc.LNumber2I(val) + if reg >= rg.top { + rg.top = reg + 1 + } +} + +func (rg *registry) IsFull() bool { + return rg.top >= cap(rg.array) +} + +/* }}} */ + +/* Global {{{ */ + +func newGlobal() *Global { + return &Global{ + MainThread: nil, + Registry: newLTable(0, 32), + Global: newLTable(0, 64), + builtinMts: make(map[int]LValue), + tempFiles: make([]*os.File, 0, 10), + } +} + +/* }}} */ + +/* package local methods {{{ */ + +func panicWithTraceback(L *LState) { + err := newApiError(ApiErrorRun, L.Get(-1)) + err.StackTrace = L.stackTrace(0) + panic(err) +} + +func panicWithoutTraceback(L *LState) { + err := newApiError(ApiErrorRun, L.Get(-1)) + panic(err) +} + +func newLState(options Options) *LState { + al := newAllocator(32) + ls := &LState{ + G: newGlobal(), + Parent: nil, + Panic: panicWithTraceback, + Dead: false, + Options: options, + + stop: 0, + alloc: al, + currentFrame: nil, + wrapped: false, + uvcache: nil, + hasErrorFunc: false, + mainLoop: mainLoop, + ctx: nil, + } + if options.MinimizeStackMemory { + ls.stack = newAutoGrowingCallFrameStack(options.CallStackSize) + } else { + ls.stack = newFixedCallFrameStack(options.CallStackSize) + } + ls.reg = newRegistry(ls, options.RegistrySize, options.RegistryGrowStep, options.RegistryMaxSize, al) + ls.Env = ls.G.Global + return ls +} + +func (ls *LState) printReg() { + println("-------------------------") + println("thread:", ls) + println("top:", ls.reg.Top()) + if ls.currentFrame != nil { + println("function base:", ls.currentFrame.Base) + println("return base:", ls.currentFrame.ReturnBase) + } else { + println("(vm not started)") + } + println("local base:", ls.currentLocalBase()) + for i := 0; i < ls.reg.Top(); i++ { + println(i, ls.reg.Get(i).String()) + } + println("-------------------------") +} + +func (ls *LState) printCallStack() { + println("-------------------------") + for i := 0; i < ls.stack.Sp(); i++ { + print(i) + print(" ") + frame := ls.stack.At(i) + if frame == nil { + break + } + if frame.Fn.IsG { + println("IsG:", true, "Frame:", frame, "Fn:", frame.Fn) + } else { + println("IsG:", false, "Frame:", frame, "Fn:", frame.Fn, "pc:", frame.Pc) + } + } + println("-------------------------") +} + +func (ls *LState) closeAllUpvalues() { // +inline-start + for cf := ls.currentFrame; cf != nil; cf = cf.Parent { + if !cf.Fn.IsG { + ls.closeUpvalues(cf.LocalBase) + } + } +} // +inline-end + +func (ls *LState) raiseError(level int, format string, args ...interface{}) { + if !ls.hasErrorFunc { + ls.closeAllUpvalues() + } + message := format + if len(args) > 0 { + message = fmt.Sprintf(format, args...) + } + if level > 0 { + message = fmt.Sprintf("%v %v", ls.where(level-1, true), message) + } + if ls.reg.IsFull() { + // if the registry is full then it won't be possible to push a value, in this case, force a larger size + ls.reg.forceResize(ls.reg.Top() + 1) + } + ls.reg.Push(LString(message)) + ls.Panic(ls) +} + +func (ls *LState) findLocal(frame *callFrame, no int) string { + fn := frame.Fn + if !fn.IsG { + if name, ok := fn.LocalName(no, frame.Pc-1); ok { + return name + } + } + var top int + if ls.currentFrame == frame { + top = ls.reg.Top() + } else if frame.Idx+1 < ls.stack.Sp() { + top = ls.stack.At(frame.Idx + 1).Base + } else { + return "" + } + if top-frame.LocalBase >= no { + return "(*temporary)" + } + return "" +} + +func (ls *LState) where(level int, skipg bool) string { + dbg, ok := ls.GetStack(level) + if !ok { + return "" + } + cf := dbg.frame + proto := cf.Fn.Proto + sourcename := "[G]" + if proto != nil { + sourcename = proto.SourceName + } else if skipg { + return ls.where(level+1, skipg) + } + line := "" + if proto != nil { + line = fmt.Sprintf("%v:", proto.DbgSourcePositions[cf.Pc-1]) + } + return fmt.Sprintf("%v:%v", sourcename, line) +} + +func (ls *LState) stackTrace(level int) string { + buf := []string{} + header := "stack traceback:" + if ls.currentFrame != nil { + i := 0 + for dbg, ok := ls.GetStack(i); ok; dbg, ok = ls.GetStack(i) { + cf := dbg.frame + buf = append(buf, fmt.Sprintf("\t%v in %v", ls.Where(i), ls.formattedFrameFuncName(cf))) + if !cf.Fn.IsG && cf.TailCall > 0 { + for tc := cf.TailCall; tc > 0; tc-- { + buf = append(buf, "\t(tailcall): ?") + i++ + } + } + i++ + } + } + buf = append(buf, fmt.Sprintf("\t%v: %v", "[G]", "?")) + buf = buf[intMax(0, intMin(level, len(buf))):len(buf)] + if len(buf) > 20 { + newbuf := make([]string, 0, 20) + newbuf = append(newbuf, buf[0:7]...) + newbuf = append(newbuf, "\t...") + newbuf = append(newbuf, buf[len(buf)-7:len(buf)]...) + buf = newbuf + } + return fmt.Sprintf("%s\n%s", header, strings.Join(buf, "\n")) +} + +func (ls *LState) formattedFrameFuncName(fr *callFrame) string { + name, ischunk := ls.frameFuncName(fr) + if ischunk { + return name + } + if name[0] != '(' && name[0] != '<' { + return fmt.Sprintf("function '%s'", name) + } + return fmt.Sprintf("function %s", name) +} + +func (ls *LState) rawFrameFuncName(fr *callFrame) string { + name, _ := ls.frameFuncName(fr) + return name +} + +func (ls *LState) frameFuncName(fr *callFrame) (string, bool) { + frame := fr.Parent + if frame == nil { + if ls.Parent == nil { + return "main chunk", true + } else { + return "corountine", true + } + } + if !frame.Fn.IsG { + pc := frame.Pc - 1 + for _, call := range frame.Fn.Proto.DbgCalls { + if call.Pc == pc { + name := call.Name + if (name == "?" || fr.TailCall > 0) && !fr.Fn.IsG { + name = fmt.Sprintf("<%v:%v>", fr.Fn.Proto.SourceName, fr.Fn.Proto.LineDefined) + } + return name, false + } + } + } + if !fr.Fn.IsG { + return fmt.Sprintf("<%v:%v>", fr.Fn.Proto.SourceName, fr.Fn.Proto.LineDefined), false + } + return "(anonymous)", false +} + +func (ls *LState) isStarted() bool { + return ls.currentFrame != nil +} + +func (ls *LState) kill() { + ls.Dead = true +} + +func (ls *LState) indexToReg(idx int) int { + base := ls.currentLocalBase() + if idx > 0 { + return base + idx - 1 + } else if idx == 0 { + return -1 + } else { + tidx := ls.reg.Top() + idx + if tidx < base { + return -1 + } + return tidx + } +} + +func (ls *LState) currentLocalBase() int { + base := 0 + if ls.currentFrame != nil { + base = ls.currentFrame.LocalBase + } + return base +} + +func (ls *LState) currentEnv() *LTable { + return ls.Env + /* + if ls.currentFrame == nil { + return ls.Env + } + return ls.currentFrame.Fn.Env + */ +} + +func (ls *LState) rkValue(idx int) LValue { + /* + if OpIsK(idx) { + return ls.currentFrame.Fn.Proto.Constants[opIndexK(idx)] + } + return ls.reg.Get(ls.currentFrame.LocalBase + idx) + */ + if (idx & opBitRk) != 0 { + return ls.currentFrame.Fn.Proto.Constants[idx & ^opBitRk] + } + return ls.reg.array[ls.currentFrame.LocalBase+idx] +} + +func (ls *LState) rkString(idx int) string { + if (idx & opBitRk) != 0 { + return ls.currentFrame.Fn.Proto.stringConstants[idx & ^opBitRk] + } + return string(ls.reg.array[ls.currentFrame.LocalBase+idx].(LString)) +} + +func (ls *LState) closeUpvalues(idx int) { // +inline-start + if ls.uvcache != nil { + var prev *Upvalue + for uv := ls.uvcache; uv != nil; uv = uv.next { + if uv.index >= idx { + if prev != nil { + prev.next = nil + } else { + ls.uvcache = nil + } + uv.Close() + } + prev = uv + } + } +} // +inline-end + +func (ls *LState) findUpvalue(idx int) *Upvalue { + var prev *Upvalue + var next *Upvalue + if ls.uvcache != nil { + for uv := ls.uvcache; uv != nil; uv = uv.next { + if uv.index == idx { + return uv + } + if uv.index > idx { + next = uv + break + } + prev = uv + } + } + uv := &Upvalue{reg: ls.reg, index: idx, closed: false} + if prev != nil { + prev.next = uv + } else { + ls.uvcache = uv + } + if next != nil { + uv.next = next + } + return uv +} + +func (ls *LState) metatable(lvalue LValue, rawget bool) LValue { + var metatable LValue = LNil + switch obj := lvalue.(type) { + case *LTable: + metatable = obj.Metatable + case *LUserData: + metatable = obj.Metatable + default: + if table, ok := ls.G.builtinMts[int(obj.Type())]; ok { + metatable = table + } + } + + if !rawget && metatable != LNil { + oldmt := metatable + if tb, ok := metatable.(*LTable); ok { + metatable = tb.RawGetString("__metatable") + if metatable == LNil { + metatable = oldmt + } + } + } + + return metatable +} + +func (ls *LState) metaOp1(lvalue LValue, event string) LValue { + if mt := ls.metatable(lvalue, true); mt != LNil { + if tb, ok := mt.(*LTable); ok { + return tb.RawGetString(event) + } + } + return LNil +} + +func (ls *LState) metaOp2(value1, value2 LValue, event string) LValue { + if mt := ls.metatable(value1, true); mt != LNil { + if tb, ok := mt.(*LTable); ok { + if ret := tb.RawGetString(event); ret != LNil { + return ret + } + } + } + if mt := ls.metatable(value2, true); mt != LNil { + if tb, ok := mt.(*LTable); ok { + return tb.RawGetString(event) + } + } + return LNil +} + +func (ls *LState) metaCall(lvalue LValue) (*LFunction, bool) { + if fn, ok := lvalue.(*LFunction); ok { + return fn, false + } + if fn, ok := ls.metaOp1(lvalue, "__call").(*LFunction); ok { + return fn, true + } + return nil, false +} + +func (ls *LState) initCallFrame(cf *callFrame) { // +inline-start + if cf.Fn.IsG { + ls.reg.SetTop(cf.LocalBase + cf.NArgs) + } else { + proto := cf.Fn.Proto + nargs := cf.NArgs + np := int(proto.NumParameters) + newSize := cf.LocalBase + np + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + rg := ls.reg + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + for i := nargs; i < np; i++ { + ls.reg.array[cf.LocalBase+i] = LNil + nargs = np + } + + if (proto.IsVarArg & VarArgIsVarArg) == 0 { + if nargs < int(proto.NumUsedRegisters) { + nargs = int(proto.NumUsedRegisters) + } + newSize = cf.LocalBase + nargs + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + rg := ls.reg + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + for i := np; i < nargs; i++ { + ls.reg.array[cf.LocalBase+i] = LNil + } + ls.reg.top = cf.LocalBase + int(proto.NumUsedRegisters) + } else { + /* swap vararg positions: + closure + namedparam1 <- lbase + namedparam2 + vararg1 + vararg2 + + TO + + closure + nil + nil + vararg1 + vararg2 + namedparam1 <- lbase + namedparam2 + */ + nvarargs := nargs - np + if nvarargs < 0 { + nvarargs = 0 + } + + ls.reg.SetTop(cf.LocalBase + nargs + np) + for i := 0; i < np; i++ { + //ls.reg.Set(cf.LocalBase+nargs+i, ls.reg.Get(cf.LocalBase+i)) + ls.reg.array[cf.LocalBase+nargs+i] = ls.reg.array[cf.LocalBase+i] + //ls.reg.Set(cf.LocalBase+i, LNil) + ls.reg.array[cf.LocalBase+i] = LNil + } + + if CompatVarArg { + ls.reg.SetTop(cf.LocalBase + nargs + np + 1) + if (proto.IsVarArg & VarArgNeedsArg) != 0 { + argtb := newLTable(nvarargs, 0) + for i := 0; i < nvarargs; i++ { + argtb.RawSetInt(i+1, ls.reg.Get(cf.LocalBase+np+i)) + } + argtb.RawSetString("n", LNumber(nvarargs)) + //ls.reg.Set(cf.LocalBase+nargs+np, argtb) + ls.reg.array[cf.LocalBase+nargs+np] = argtb + } else { + ls.reg.array[cf.LocalBase+nargs+np] = LNil + } + } + cf.LocalBase += nargs + maxreg := cf.LocalBase + int(proto.NumUsedRegisters) + ls.reg.SetTop(maxreg) + } + } +} // +inline-end + +func (ls *LState) pushCallFrame(cf callFrame, fn LValue, meta bool) { // +inline-start + if meta { + cf.NArgs++ + ls.reg.Insert(fn, cf.LocalBase) + } + if cf.Fn == nil { + ls.RaiseError("attempt to call a non-function object") + } + if ls.stack.IsFull() { + ls.RaiseError("stack overflow") + } + ls.stack.Push(cf) + newcf := ls.stack.Last() + // this section is inlined by go-inline + // source function is 'func (ls *LState) initCallFrame(cf *callFrame) ' in '_state.go' + { + cf := newcf + if cf.Fn.IsG { + ls.reg.SetTop(cf.LocalBase + cf.NArgs) + } else { + proto := cf.Fn.Proto + nargs := cf.NArgs + np := int(proto.NumParameters) + newSize := cf.LocalBase + np + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + rg := ls.reg + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + for i := nargs; i < np; i++ { + ls.reg.array[cf.LocalBase+i] = LNil + nargs = np + } + + if (proto.IsVarArg & VarArgIsVarArg) == 0 { + if nargs < int(proto.NumUsedRegisters) { + nargs = int(proto.NumUsedRegisters) + } + newSize = cf.LocalBase + nargs + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + rg := ls.reg + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + for i := np; i < nargs; i++ { + ls.reg.array[cf.LocalBase+i] = LNil + } + ls.reg.top = cf.LocalBase + int(proto.NumUsedRegisters) + } else { + /* swap vararg positions: + closure + namedparam1 <- lbase + namedparam2 + vararg1 + vararg2 + + TO + + closure + nil + nil + vararg1 + vararg2 + namedparam1 <- lbase + namedparam2 + */ + nvarargs := nargs - np + if nvarargs < 0 { + nvarargs = 0 + } + + ls.reg.SetTop(cf.LocalBase + nargs + np) + for i := 0; i < np; i++ { + //ls.reg.Set(cf.LocalBase+nargs+i, ls.reg.Get(cf.LocalBase+i)) + ls.reg.array[cf.LocalBase+nargs+i] = ls.reg.array[cf.LocalBase+i] + //ls.reg.Set(cf.LocalBase+i, LNil) + ls.reg.array[cf.LocalBase+i] = LNil + } + + if CompatVarArg { + ls.reg.SetTop(cf.LocalBase + nargs + np + 1) + if (proto.IsVarArg & VarArgNeedsArg) != 0 { + argtb := newLTable(nvarargs, 0) + for i := 0; i < nvarargs; i++ { + argtb.RawSetInt(i+1, ls.reg.Get(cf.LocalBase+np+i)) + } + argtb.RawSetString("n", LNumber(nvarargs)) + //ls.reg.Set(cf.LocalBase+nargs+np, argtb) + ls.reg.array[cf.LocalBase+nargs+np] = argtb + } else { + ls.reg.array[cf.LocalBase+nargs+np] = LNil + } + } + cf.LocalBase += nargs + maxreg := cf.LocalBase + int(proto.NumUsedRegisters) + ls.reg.SetTop(maxreg) + } + } + } + ls.currentFrame = newcf +} // +inline-end + +func (ls *LState) callR(nargs, nret, rbase int) { + base := ls.reg.Top() - nargs - 1 + if rbase < 0 { + rbase = base + } + lv := ls.reg.Get(base) + fn, meta := ls.metaCall(lv) + ls.pushCallFrame(callFrame{ + Fn: fn, + Pc: 0, + Base: base, + LocalBase: base + 1, + ReturnBase: rbase, + NArgs: nargs, + NRet: nret, + Parent: ls.currentFrame, + TailCall: 0, + }, lv, meta) + if ls.G.MainThread == nil { + ls.G.MainThread = ls + ls.G.CurrentThread = ls + ls.mainLoop(ls, nil) + } else { + ls.mainLoop(ls, ls.currentFrame) + } + if nret != MultRet { + ls.reg.SetTop(rbase + nret) + } +} + +func (ls *LState) getField(obj LValue, key LValue) LValue { + curobj := obj + for i := 0; i < MaxTableGetLoop; i++ { + tb, istable := curobj.(*LTable) + if istable { + ret := tb.RawGet(key) + if ret != LNil { + return ret + } + } + metaindex := ls.metaOp1(curobj, "__index") + if metaindex == LNil { + if !istable { + ls.RaiseError("attempt to index a non-table object(%v) with key '%s'", curobj.Type().String(), key.String()) + } + return LNil + } + if metaindex.Type() == LTFunction { + ls.reg.Push(metaindex) + ls.reg.Push(curobj) + ls.reg.Push(key) + ls.Call(2, 1) + return ls.reg.Pop() + } else { + curobj = metaindex + } + } + ls.RaiseError("too many recursions in gettable") + return nil +} + +func (ls *LState) getFieldString(obj LValue, key string) LValue { + curobj := obj + for i := 0; i < MaxTableGetLoop; i++ { + tb, istable := curobj.(*LTable) + if istable { + ret := tb.RawGetString(key) + if ret != LNil { + return ret + } + } + metaindex := ls.metaOp1(curobj, "__index") + if metaindex == LNil { + if !istable { + ls.RaiseError("attempt to index a non-table object(%v) with key '%s'", curobj.Type().String(), key) + } + return LNil + } + if metaindex.Type() == LTFunction { + ls.reg.Push(metaindex) + ls.reg.Push(curobj) + ls.reg.Push(LString(key)) + ls.Call(2, 1) + return ls.reg.Pop() + } else { + curobj = metaindex + } + } + ls.RaiseError("too many recursions in gettable") + return nil +} + +func (ls *LState) setField(obj LValue, key LValue, value LValue) { + curobj := obj + for i := 0; i < MaxTableGetLoop; i++ { + tb, istable := curobj.(*LTable) + if istable { + if tb.RawGet(key) != LNil { + ls.RawSet(tb, key, value) + return + } + } + metaindex := ls.metaOp1(curobj, "__newindex") + if metaindex == LNil { + if !istable { + ls.RaiseError("attempt to index a non-table object(%v) with key '%s'", curobj.Type().String(), key.String()) + } + ls.RawSet(tb, key, value) + return + } + if metaindex.Type() == LTFunction { + ls.reg.Push(metaindex) + ls.reg.Push(curobj) + ls.reg.Push(key) + ls.reg.Push(value) + ls.Call(3, 0) + return + } else { + curobj = metaindex + } + } + ls.RaiseError("too many recursions in settable") +} + +func (ls *LState) setFieldString(obj LValue, key string, value LValue) { + curobj := obj + for i := 0; i < MaxTableGetLoop; i++ { + tb, istable := curobj.(*LTable) + if istable { + if tb.RawGetString(key) != LNil { + tb.RawSetString(key, value) + return + } + } + metaindex := ls.metaOp1(curobj, "__newindex") + if metaindex == LNil { + if !istable { + ls.RaiseError("attempt to index a non-table object(%v) with key '%s'", curobj.Type().String(), key) + } + tb.RawSetString(key, value) + return + } + if metaindex.Type() == LTFunction { + ls.reg.Push(metaindex) + ls.reg.Push(curobj) + ls.reg.Push(LString(key)) + ls.reg.Push(value) + ls.Call(3, 0) + return + } else { + curobj = metaindex + } + } + ls.RaiseError("too many recursions in settable") +} + +/* }}} */ + +/* api methods {{{ */ + +func NewState(opts ...Options) *LState { + var ls *LState + if len(opts) == 0 { + ls = newLState(Options{ + CallStackSize: CallStackSize, + RegistrySize: RegistrySize, + }) + ls.OpenLibs() + } else { + if opts[0].CallStackSize < 1 { + opts[0].CallStackSize = CallStackSize + } + if opts[0].RegistrySize < 128 { + opts[0].RegistrySize = RegistrySize + } + if opts[0].RegistryMaxSize < opts[0].RegistrySize { + opts[0].RegistryMaxSize = 0 // disable growth if max size is smaller than initial size + } else { + // if growth enabled, grow step is set + if opts[0].RegistryGrowStep < 1 { + opts[0].RegistryGrowStep = RegistryGrowStep + } + } + ls = newLState(opts[0]) + if !opts[0].SkipOpenLibs { + ls.OpenLibs() + } + } + return ls +} + +func (ls *LState) IsClosed() bool { + return ls.stack == nil +} + +func (ls *LState) Close() { + atomic.AddInt32(&ls.stop, 1) + for _, file := range ls.G.tempFiles { + // ignore errors in these operations + file.Close() + os.Remove(file.Name()) + } + ls.stack.FreeAll() + ls.stack = nil +} + +/* registry operations {{{ */ + +func (ls *LState) GetTop() int { + return ls.reg.Top() - ls.currentLocalBase() +} + +func (ls *LState) SetTop(idx int) { + base := ls.currentLocalBase() + newtop := ls.indexToReg(idx) + 1 + if newtop < base { + ls.reg.SetTop(base) + } else { + ls.reg.SetTop(newtop) + } +} + +func (ls *LState) Replace(idx int, value LValue) { + base := ls.currentLocalBase() + if idx > 0 { + reg := base + idx - 1 + if reg < ls.reg.Top() { + ls.reg.Set(reg, value) + } + } else if idx == 0 { + } else if idx > RegistryIndex { + if tidx := ls.reg.Top() + idx; tidx >= base { + ls.reg.Set(tidx, value) + } + } else { + switch idx { + case RegistryIndex: + if tb, ok := value.(*LTable); ok { + ls.G.Registry = tb + } else { + ls.RaiseError("registry must be a table(%v)", value.Type().String()) + } + case EnvironIndex: + if ls.currentFrame == nil { + ls.RaiseError("no calling environment") + } + if tb, ok := value.(*LTable); ok { + ls.currentFrame.Fn.Env = tb + } else { + ls.RaiseError("environment must be a table(%v)", value.Type().String()) + } + case GlobalsIndex: + if tb, ok := value.(*LTable); ok { + ls.G.Global = tb + } else { + ls.RaiseError("_G must be a table(%v)", value.Type().String()) + } + default: + fn := ls.currentFrame.Fn + index := GlobalsIndex - idx - 1 + if index < len(fn.Upvalues) { + fn.Upvalues[index].SetValue(value) + } + } + } +} + +func (ls *LState) Get(idx int) LValue { + base := ls.currentLocalBase() + if idx > 0 { + reg := base + idx - 1 + if reg < ls.reg.Top() { + return ls.reg.Get(reg) + } + return LNil + } else if idx == 0 { + return LNil + } else if idx > RegistryIndex { + tidx := ls.reg.Top() + idx + if tidx < base { + return LNil + } + return ls.reg.Get(tidx) + } else { + switch idx { + case RegistryIndex: + return ls.G.Registry + case EnvironIndex: + if ls.currentFrame == nil { + return ls.Env + } + return ls.currentFrame.Fn.Env + case GlobalsIndex: + return ls.G.Global + default: + fn := ls.currentFrame.Fn + index := GlobalsIndex - idx - 1 + if index < len(fn.Upvalues) { + return fn.Upvalues[index].Value() + } + return LNil + } + } + return LNil +} + +func (ls *LState) Push(value LValue) { + ls.reg.Push(value) +} + +func (ls *LState) Pop(n int) { + for i := 0; i < n; i++ { + if ls.GetTop() == 0 { + ls.RaiseError("register underflow") + } + ls.reg.Pop() + } +} + +func (ls *LState) Insert(value LValue, index int) { + reg := ls.indexToReg(index) + top := ls.reg.Top() + if reg >= top { + ls.reg.Set(reg, value) + return + } + if reg <= ls.currentLocalBase() { + reg = ls.currentLocalBase() + } + top-- + for ; top >= reg; top-- { + ls.reg.Set(top+1, ls.reg.Get(top)) + } + ls.reg.Set(reg, value) +} + +func (ls *LState) Remove(index int) { + reg := ls.indexToReg(index) + top := ls.reg.Top() + switch { + case reg >= top: + return + case reg < ls.currentLocalBase(): + return + case reg == top-1: + ls.Pop(1) + return + } + for i := reg; i < top-1; i++ { + ls.reg.Set(i, ls.reg.Get(i+1)) + } + ls.reg.SetTop(top - 1) +} + +/* }}} */ + +/* object allocation {{{ */ + +func (ls *LState) NewTable() *LTable { + return newLTable(defaultArrayCap, defaultHashCap) +} + +func (ls *LState) CreateTable(acap, hcap int) *LTable { + return newLTable(acap, hcap) +} + +// NewThread returns a new LState that shares with the original state all global objects. +// If the original state has context.Context, the new state has a new child context of the original state and this function returns its cancel function. +func (ls *LState) NewThread() (*LState, context.CancelFunc) { + thread := newLState(ls.Options) + thread.G = ls.G + thread.Env = ls.Env + var f context.CancelFunc = nil + if ls.ctx != nil { + thread.mainLoop = mainLoopWithContext + thread.ctx, f = context.WithCancel(ls.ctx) + } + return thread, f +} + +func (ls *LState) NewFunctionFromProto(proto *FunctionProto) *LFunction { + return newLFunctionL(proto, ls.Env, int(proto.NumUpvalues)) +} + +func (ls *LState) NewUserData() *LUserData { + return &LUserData{ + Env: ls.currentEnv(), + Metatable: LNil, + } +} + +func (ls *LState) NewFunction(fn LGFunction) *LFunction { + return newLFunctionG(fn, ls.currentEnv(), 0) +} + +func (ls *LState) NewClosure(fn LGFunction, upvalues ...LValue) *LFunction { + cl := newLFunctionG(fn, ls.currentEnv(), len(upvalues)) + for i, lv := range upvalues { + cl.Upvalues[i] = &Upvalue{} + cl.Upvalues[i].Close() + cl.Upvalues[i].SetValue(lv) + } + return cl +} + +/* }}} */ + +/* toType {{{ */ + +func (ls *LState) ToBool(n int) bool { + return LVAsBool(ls.Get(n)) +} + +func (ls *LState) ToInt(n int) int { + if lv, ok := ls.Get(n).(LNumber); ok { + return int(lv) + } + if lv, ok := ls.Get(n).(LString); ok { + if num, err := parseNumber(string(lv)); err == nil { + return int(num) + } + } + return 0 +} + +func (ls *LState) ToInt64(n int) int64 { + if lv, ok := ls.Get(n).(LNumber); ok { + return int64(lv) + } + if lv, ok := ls.Get(n).(LString); ok { + if num, err := parseNumber(string(lv)); err == nil { + return int64(num) + } + } + return 0 +} + +func (ls *LState) ToNumber(n int) LNumber { + return LVAsNumber(ls.Get(n)) +} + +func (ls *LState) ToString(n int) string { + return LVAsString(ls.Get(n)) +} + +func (ls *LState) ToTable(n int) *LTable { + if lv, ok := ls.Get(n).(*LTable); ok { + return lv + } + return nil +} + +func (ls *LState) ToFunction(n int) *LFunction { + if lv, ok := ls.Get(n).(*LFunction); ok { + return lv + } + return nil +} + +func (ls *LState) ToUserData(n int) *LUserData { + if lv, ok := ls.Get(n).(*LUserData); ok { + return lv + } + return nil +} + +func (ls *LState) ToThread(n int) *LState { + if lv, ok := ls.Get(n).(*LState); ok { + return lv + } + return nil +} + +/* }}} */ + +/* error & debug operations {{{ */ + +func (ls *LState) registryOverflow() { + ls.RaiseError("registry overflow") +} + +// This function is equivalent to luaL_error( http://www.lua.org/manual/5.1/manual.html#luaL_error ). +func (ls *LState) RaiseError(format string, args ...interface{}) { + ls.raiseError(1, format, args...) +} + +// This function is equivalent to lua_error( http://www.lua.org/manual/5.1/manual.html#lua_error ). +func (ls *LState) Error(lv LValue, level int) { + if str, ok := lv.(LString); ok { + ls.raiseError(level, string(str)) + } else { + if !ls.hasErrorFunc { + ls.closeAllUpvalues() + } + ls.Push(lv) + ls.Panic(ls) + } +} + +func (ls *LState) GetInfo(what string, dbg *Debug, fn LValue) (LValue, error) { + if !strings.HasPrefix(what, ">") { + fn = dbg.frame.Fn + } else { + what = what[1:] + } + f, ok := fn.(*LFunction) + if !ok { + return LNil, newApiErrorS(ApiErrorRun, "can not get debug info(an object in not a function)") + } + + retfn := false + for _, c := range what { + switch c { + case 'f': + retfn = true + case 'S': + if dbg.frame != nil && dbg.frame.Parent == nil { + dbg.What = "main" + } else if f.IsG { + dbg.What = "G" + } else if dbg.frame != nil && dbg.frame.TailCall > 0 { + dbg.What = "tail" + } else { + dbg.What = "Lua" + } + if !f.IsG { + dbg.Source = f.Proto.SourceName + dbg.LineDefined = f.Proto.LineDefined + dbg.LastLineDefined = f.Proto.LastLineDefined + } + case 'l': + if !f.IsG && dbg.frame != nil { + if dbg.frame.Pc > 0 { + dbg.CurrentLine = f.Proto.DbgSourcePositions[dbg.frame.Pc-1] + } + } else { + dbg.CurrentLine = -1 + } + case 'u': + dbg.NUpvalues = len(f.Upvalues) + case 'n': + if dbg.frame != nil { + dbg.Name = ls.rawFrameFuncName(dbg.frame) + } + default: + return LNil, newApiErrorS(ApiErrorRun, "invalid what: "+string(c)) + } + } + + if retfn { + return f, nil + } + return LNil, nil + +} + +func (ls *LState) GetStack(level int) (*Debug, bool) { + frame := ls.currentFrame + for ; level > 0 && frame != nil; frame = frame.Parent { + level-- + if !frame.Fn.IsG { + level -= frame.TailCall + } + } + + if level == 0 && frame != nil { + return &Debug{frame: frame}, true + } else if level < 0 && ls.stack.Sp() > 0 { + return &Debug{frame: ls.stack.At(0)}, true + } + return &Debug{}, false +} + +func (ls *LState) GetLocal(dbg *Debug, no int) (string, LValue) { + frame := dbg.frame + if name := ls.findLocal(frame, no); len(name) > 0 { + return name, ls.reg.Get(frame.LocalBase + no - 1) + } + return "", LNil +} + +func (ls *LState) SetLocal(dbg *Debug, no int, lv LValue) string { + frame := dbg.frame + if name := ls.findLocal(frame, no); len(name) > 0 { + ls.reg.Set(frame.LocalBase+no-1, lv) + return name + } + return "" +} + +func (ls *LState) GetUpvalue(fn *LFunction, no int) (string, LValue) { + if fn.IsG { + return "", LNil + } + + no-- + if no >= 0 && no < len(fn.Upvalues) { + return fn.Proto.DbgUpvalues[no], fn.Upvalues[no].Value() + } + return "", LNil +} + +func (ls *LState) SetUpvalue(fn *LFunction, no int, lv LValue) string { + if fn.IsG { + return "" + } + + no-- + if no >= 0 && no < len(fn.Upvalues) { + fn.Upvalues[no].SetValue(lv) + return fn.Proto.DbgUpvalues[no] + } + return "" +} + +/* }}} */ + +/* env operations {{{ */ + +func (ls *LState) GetFEnv(obj LValue) LValue { + switch lv := obj.(type) { + case *LFunction: + return lv.Env + case *LUserData: + return lv.Env + case *LState: + return lv.Env + } + return LNil +} + +func (ls *LState) SetFEnv(obj LValue, env LValue) { + tb, ok := env.(*LTable) + if !ok { + ls.RaiseError("cannot use %v as an environment", env.Type().String()) + } + + switch lv := obj.(type) { + case *LFunction: + lv.Env = tb + case *LUserData: + lv.Env = tb + case *LState: + lv.Env = tb + } + /* do nothing */ +} + +/* }}} */ + +/* table operations {{{ */ + +func (ls *LState) RawGet(tb *LTable, key LValue) LValue { + return tb.RawGet(key) +} + +func (ls *LState) RawGetInt(tb *LTable, key int) LValue { + return tb.RawGetInt(key) +} + +func (ls *LState) GetField(obj LValue, skey string) LValue { + return ls.getFieldString(obj, skey) +} + +func (ls *LState) GetTable(obj LValue, key LValue) LValue { + return ls.getField(obj, key) +} + +func (ls *LState) RawSet(tb *LTable, key LValue, value LValue) { + if n, ok := key.(LNumber); ok && math.IsNaN(float64(n)) { + ls.RaiseError("table index is NaN") + } else if key == LNil { + ls.RaiseError("table index is nil") + } + tb.RawSet(key, value) +} + +func (ls *LState) RawSetInt(tb *LTable, key int, value LValue) { + tb.RawSetInt(key, value) +} + +func (ls *LState) SetField(obj LValue, key string, value LValue) { + ls.setFieldString(obj, key, value) +} + +func (ls *LState) SetTable(obj LValue, key LValue, value LValue) { + ls.setField(obj, key, value) +} + +func (ls *LState) ForEach(tb *LTable, cb func(LValue, LValue)) { + tb.ForEach(cb) +} + +func (ls *LState) GetGlobal(name string) LValue { + return ls.GetField(ls.Get(GlobalsIndex), name) +} + +func (ls *LState) SetGlobal(name string, value LValue) { + ls.SetField(ls.Get(GlobalsIndex), name, value) +} + +func (ls *LState) Next(tb *LTable, key LValue) (LValue, LValue) { + return tb.Next(key) +} + +/* }}} */ + +/* unary operations {{{ */ + +func (ls *LState) ObjLen(v1 LValue) int { + if v1.Type() == LTString { + return len(string(v1.(LString))) + } + op := ls.metaOp1(v1, "__len") + if op.Type() == LTFunction { + ls.Push(op) + ls.Push(v1) + ls.Call(1, 1) + ret := ls.reg.Pop() + if ret.Type() == LTNumber { + return int(ret.(LNumber)) + } + } else if v1.Type() == LTTable { + return v1.(*LTable).Len() + } + return 0 +} + +/* }}} */ + +/* binary operations {{{ */ + +func (ls *LState) Concat(values ...LValue) string { + top := ls.reg.Top() + for _, value := range values { + ls.reg.Push(value) + } + ret := stringConcat(ls, len(values), ls.reg.Top()-1) + ls.reg.SetTop(top) + return LVAsString(ret) +} + +func (ls *LState) LessThan(lhs, rhs LValue) bool { + return lessThan(ls, lhs, rhs) +} + +func (ls *LState) Equal(lhs, rhs LValue) bool { + return equals(ls, lhs, rhs, false) +} + +func (ls *LState) RawEqual(lhs, rhs LValue) bool { + return equals(ls, lhs, rhs, true) +} + +/* }}} */ + +/* register operations {{{ */ + +func (ls *LState) Register(name string, fn LGFunction) { + ls.SetGlobal(name, ls.NewFunction(fn)) +} + +/* }}} */ + +/* load and function call operations {{{ */ + +func (ls *LState) Load(reader io.Reader, name string) (*LFunction, error) { + chunk, err := parse.Parse(reader, name) + if err != nil { + return nil, newApiErrorE(ApiErrorSyntax, err) + } + proto, err := Compile(chunk, name) + if err != nil { + return nil, newApiErrorE(ApiErrorSyntax, err) + } + return newLFunctionL(proto, ls.currentEnv(), 0), nil +} + +func (ls *LState) Call(nargs, nret int) { + ls.callR(nargs, nret, -1) +} + +func (ls *LState) PCall(nargs, nret int, errfunc *LFunction) (err error) { + err = nil + sp := ls.stack.Sp() + base := ls.reg.Top() - nargs - 1 + oldpanic := ls.Panic + ls.Panic = panicWithoutTraceback + if errfunc != nil { + ls.hasErrorFunc = true + } + defer func() { + ls.Panic = oldpanic + ls.hasErrorFunc = false + rcv := recover() + if rcv != nil { + if _, ok := rcv.(*ApiError); !ok { + err = newApiErrorS(ApiErrorPanic, fmt.Sprint(rcv)) + if ls.Options.IncludeGoStackTrace { + buf := make([]byte, 4096) + runtime.Stack(buf, false) + err.(*ApiError).StackTrace = strings.Trim(string(buf), "\000") + "\n" + ls.stackTrace(0) + } + } else { + err = rcv.(*ApiError) + } + if errfunc != nil { + ls.Push(errfunc) + ls.Push(err.(*ApiError).Object) + ls.Panic = panicWithoutTraceback + defer func() { + ls.Panic = oldpanic + rcv := recover() + if rcv != nil { + if _, ok := rcv.(*ApiError); !ok { + err = newApiErrorS(ApiErrorPanic, fmt.Sprint(rcv)) + if ls.Options.IncludeGoStackTrace { + buf := make([]byte, 4096) + runtime.Stack(buf, false) + err.(*ApiError).StackTrace = strings.Trim(string(buf), "\000") + ls.stackTrace(0) + } + } else { + err = rcv.(*ApiError) + err.(*ApiError).StackTrace = ls.stackTrace(0) + } + } + }() + ls.Call(1, 1) + err = newApiError(ApiErrorError, ls.Get(-1)) + } else if len(err.(*ApiError).StackTrace) == 0 { + err.(*ApiError).StackTrace = ls.stackTrace(0) + } + ls.stack.SetSp(sp) + ls.currentFrame = ls.stack.Last() + ls.reg.SetTop(base) + } + ls.stack.SetSp(sp) + if sp == 0 { + ls.currentFrame = nil + } + }() + + ls.Call(nargs, nret) + + return +} + +func (ls *LState) GPCall(fn LGFunction, data LValue) error { + ls.Push(newLFunctionG(fn, ls.currentEnv(), 0)) + ls.Push(data) + return ls.PCall(1, MultRet, nil) +} + +func (ls *LState) CallByParam(cp P, args ...LValue) error { + ls.Push(cp.Fn) + for _, arg := range args { + ls.Push(arg) + } + + if cp.Protect { + return ls.PCall(len(args), cp.NRet, cp.Handler) + } + ls.Call(len(args), cp.NRet) + return nil +} + +/* }}} */ + +/* metatable operations {{{ */ + +func (ls *LState) GetMetatable(obj LValue) LValue { + return ls.metatable(obj, false) +} + +func (ls *LState) SetMetatable(obj LValue, mt LValue) { + switch mt.(type) { + case *LNilType, *LTable: + default: + ls.RaiseError("metatable must be a table or nil, but got %v", mt.Type().String()) + } + + switch v := obj.(type) { + case *LTable: + v.Metatable = mt + case *LUserData: + v.Metatable = mt + default: + ls.G.builtinMts[int(obj.Type())] = mt + } +} + +/* }}} */ + +/* coroutine operations {{{ */ + +func (ls *LState) Status(th *LState) string { + status := "suspended" + if th.Dead { + status = "dead" + } else if ls.G.CurrentThread == th { + status = "running" + } else if ls.Parent == th { + status = "normal" + } + return status +} + +func (ls *LState) Resume(th *LState, fn *LFunction, args ...LValue) (ResumeState, error, []LValue) { + isstarted := th.isStarted() + if !isstarted { + base := 0 + th.stack.Push(callFrame{ + Fn: fn, + Pc: 0, + Base: base, + LocalBase: base + 1, + ReturnBase: base, + NArgs: 0, + NRet: MultRet, + Parent: nil, + TailCall: 0, + }) + } + + if ls.G.CurrentThread == th { + return ResumeError, newApiErrorS(ApiErrorRun, "can not resume a running thread"), nil + } + if th.Dead { + return ResumeError, newApiErrorS(ApiErrorRun, "can not resume a dead thread"), nil + } + th.Parent = ls + ls.G.CurrentThread = th + if !isstarted { + cf := th.stack.Last() + th.currentFrame = cf + th.SetTop(0) + for _, arg := range args { + th.Push(arg) + } + cf.NArgs = len(args) + th.initCallFrame(cf) + th.Panic = panicWithoutTraceback + } else { + for _, arg := range args { + th.Push(arg) + } + } + top := ls.GetTop() + threadRun(th) + haserror := LVIsFalse(ls.Get(top + 1)) + ret := make([]LValue, 0, ls.GetTop()) + for idx := top + 2; idx <= ls.GetTop(); idx++ { + ret = append(ret, ls.Get(idx)) + } + if len(ret) == 0 { + ret = append(ret, LNil) + } + ls.SetTop(top) + + if haserror { + return ResumeError, newApiError(ApiErrorRun, ret[0]), nil + } else if th.stack.IsEmpty() { + return ResumeOK, nil, ret + } + return ResumeYield, nil, ret +} + +func (ls *LState) Yield(values ...LValue) int { + ls.SetTop(0) + for _, lv := range values { + ls.Push(lv) + } + return -1 +} + +func (ls *LState) XMoveTo(other *LState, n int) { + if ls == other { + return + } + top := ls.GetTop() + n = intMin(n, top) + for i := n; i > 0; i-- { + other.Push(ls.Get(top - i + 1)) + } + ls.SetTop(top - n) +} + +/* }}} */ + +/* GopherLua original APIs {{{ */ + +// Set maximum memory size. This function can only be called from the main thread. +func (ls *LState) SetMx(mx int) { + if ls.Parent != nil { + ls.RaiseError("sub threads are not allowed to set a memory limit") + } + go func() { + limit := uint64(mx * 1024 * 1024) //MB + var s runtime.MemStats + for atomic.LoadInt32(&ls.stop) == 0 { + runtime.ReadMemStats(&s) + if s.Alloc >= limit { + fmt.Println("out of memory") + os.Exit(3) + } + time.Sleep(100 * time.Millisecond) + } + }() +} + +// SetContext set a context ctx to this LState. The provided ctx must be non-nil. +func (ls *LState) SetContext(ctx context.Context) { + ls.mainLoop = mainLoopWithContext + ls.ctx = ctx +} + +// Context returns the LState's context. To change the context, use WithContext. +func (ls *LState) Context() context.Context { + return ls.ctx +} + +// RemoveContext removes the context associated with this LState and returns this context. +func (ls *LState) RemoveContext() context.Context { + oldctx := ls.ctx + ls.mainLoop = mainLoop + ls.ctx = nil + return oldctx +} + +// Converts the Lua value at the given acceptable index to the chan LValue. +func (ls *LState) ToChannel(n int) chan LValue { + if lv, ok := ls.Get(n).(LChannel); ok { + return (chan LValue)(lv) + } + return nil +} + +// RemoveCallerFrame removes the stack frame above the current stack frame. This is useful in tail calls. It returns +// the new current frame. +func (ls *LState) RemoveCallerFrame() *callFrame { + cs := ls.stack + sp := cs.Sp() + parentFrame := cs.At(sp - 2) + currentFrame := cs.At(sp - 1) + parentsParentFrame := parentFrame.Parent + *parentFrame = *currentFrame + parentFrame.Parent = parentsParentFrame + parentFrame.Idx = sp - 2 + cs.Pop() + return parentFrame +} + +/* }}} */ + +/* }}} */ + +// diff --git a/vendor/github.com/yuin/gopher-lua/stringlib.go b/vendor/github.com/yuin/gopher-lua/stringlib.go new file mode 100644 index 00000000000..f484c2b33af --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/stringlib.go @@ -0,0 +1,448 @@ +package lua + +import ( + "fmt" + "strings" + + "github.com/yuin/gopher-lua/pm" +) + +const emptyLString LString = LString("") + +func OpenString(L *LState) int { + var mod *LTable + //_, ok := L.G.builtinMts[int(LTString)] + //if !ok { + mod = L.RegisterModule(StringLibName, strFuncs).(*LTable) + gmatch := L.NewClosure(strGmatch, L.NewFunction(strGmatchIter)) + mod.RawSetString("gmatch", gmatch) + mod.RawSetString("gfind", gmatch) + mod.RawSetString("__index", mod) + L.G.builtinMts[int(LTString)] = mod + //} + L.Push(mod) + return 1 +} + +var strFuncs = map[string]LGFunction{ + "byte": strByte, + "char": strChar, + "dump": strDump, + "find": strFind, + "format": strFormat, + "gsub": strGsub, + "len": strLen, + "lower": strLower, + "match": strMatch, + "rep": strRep, + "reverse": strReverse, + "sub": strSub, + "upper": strUpper, +} + +func strByte(L *LState) int { + str := L.CheckString(1) + start := L.OptInt(2, 1) - 1 + end := L.OptInt(3, -1) + l := len(str) + if start < 0 { + start = l + start + 1 + } + if end < 0 { + end = l + end + 1 + } + + if L.GetTop() == 2 { + if start < 0 || start >= l { + return 0 + } + L.Push(LNumber(str[start])) + return 1 + } + + start = intMax(start, 0) + end = intMin(end, l) + if end < 0 || end <= start || start >= l { + return 0 + } + + for i := start; i < end; i++ { + L.Push(LNumber(str[i])) + } + return end - start +} + +func strChar(L *LState) int { + top := L.GetTop() + bytes := make([]byte, L.GetTop()) + for i := 1; i <= top; i++ { + bytes[i-1] = uint8(L.CheckInt(i)) + } + L.Push(LString(string(bytes))) + return 1 +} + +func strDump(L *LState) int { + L.RaiseError("GopherLua does not support the string.dump") + return 0 +} + +func strFind(L *LState) int { + str := L.CheckString(1) + pattern := L.CheckString(2) + if len(pattern) == 0 { + L.Push(LNumber(1)) + L.Push(LNumber(0)) + return 2 + } + init := luaIndex2StringIndex(str, L.OptInt(3, 1), true) + plain := false + if L.GetTop() == 4 { + plain = LVAsBool(L.Get(4)) + } + + if plain { + pos := strings.Index(str[init:], pattern) + if pos < 0 { + L.Push(LNil) + return 1 + } + L.Push(LNumber(init+pos) + 1) + L.Push(LNumber(init + pos + len(pattern))) + return 2 + } + + mds, err := pm.Find(pattern, unsafeFastStringToReadOnlyBytes(str), init, 1) + if err != nil { + L.RaiseError(err.Error()) + } + if len(mds) == 0 { + L.Push(LNil) + return 1 + } + md := mds[0] + L.Push(LNumber(md.Capture(0) + 1)) + L.Push(LNumber(md.Capture(1))) + for i := 2; i < md.CaptureLength(); i += 2 { + if md.IsPosCapture(i) { + L.Push(LNumber(md.Capture(i))) + } else { + L.Push(LString(str[md.Capture(i):md.Capture(i+1)])) + } + } + return md.CaptureLength()/2 + 1 +} + +func strFormat(L *LState) int { + str := L.CheckString(1) + args := make([]interface{}, L.GetTop()-1) + top := L.GetTop() + for i := 2; i <= top; i++ { + args[i-2] = L.Get(i) + } + npat := strings.Count(str, "%") - strings.Count(str, "%%") + L.Push(LString(fmt.Sprintf(str, args[:intMin(npat, len(args))]...))) + return 1 +} + +func strGsub(L *LState) int { + str := L.CheckString(1) + pat := L.CheckString(2) + L.CheckTypes(3, LTString, LTTable, LTFunction) + repl := L.CheckAny(3) + limit := L.OptInt(4, -1) + + mds, err := pm.Find(pat, unsafeFastStringToReadOnlyBytes(str), 0, limit) + if err != nil { + L.RaiseError(err.Error()) + } + if len(mds) == 0 { + L.SetTop(1) + L.Push(LNumber(0)) + return 2 + } + switch lv := repl.(type) { + case LString: + L.Push(LString(strGsubStr(L, str, string(lv), mds))) + case *LTable: + L.Push(LString(strGsubTable(L, str, lv, mds))) + case *LFunction: + L.Push(LString(strGsubFunc(L, str, lv, mds))) + } + L.Push(LNumber(len(mds))) + return 2 +} + +type replaceInfo struct { + Indicies []int + String string +} + +func checkCaptureIndex(L *LState, m *pm.MatchData, idx int) { + if idx <= 2 { + return + } + if idx >= m.CaptureLength() { + L.RaiseError("invalid capture index") + } +} + +func capturedString(L *LState, m *pm.MatchData, str string, idx int) string { + checkCaptureIndex(L, m, idx) + if idx >= m.CaptureLength() && idx == 2 { + idx = 0 + } + if m.IsPosCapture(idx) { + return fmt.Sprint(m.Capture(idx)) + } else { + return str[m.Capture(idx):m.Capture(idx+1)] + } + +} + +func strGsubDoReplace(str string, info []replaceInfo) string { + offset := 0 + buf := []byte(str) + for _, replace := range info { + oldlen := len(buf) + b1 := append([]byte(""), buf[0:offset+replace.Indicies[0]]...) + b2 := []byte("") + index2 := offset + replace.Indicies[1] + if index2 <= len(buf) { + b2 = append(b2, buf[index2:len(buf)]...) + } + buf = append(b1, replace.String...) + buf = append(buf, b2...) + offset += len(buf) - oldlen + } + return string(buf) +} + +func strGsubStr(L *LState, str string, repl string, matches []*pm.MatchData) string { + infoList := make([]replaceInfo, 0, len(matches)) + for _, match := range matches { + start, end := match.Capture(0), match.Capture(1) + sc := newFlagScanner('%', "", "", repl) + for c, eos := sc.Next(); !eos; c, eos = sc.Next() { + if !sc.ChangeFlag { + if sc.HasFlag { + if c >= '0' && c <= '9' { + sc.AppendString(capturedString(L, match, str, 2*(int(c)-48))) + } else { + sc.AppendChar('%') + sc.AppendChar(c) + } + sc.HasFlag = false + } else { + sc.AppendChar(c) + } + } + } + infoList = append(infoList, replaceInfo{[]int{start, end}, sc.String()}) + } + + return strGsubDoReplace(str, infoList) +} + +func strGsubTable(L *LState, str string, repl *LTable, matches []*pm.MatchData) string { + infoList := make([]replaceInfo, 0, len(matches)) + for _, match := range matches { + idx := 0 + if match.CaptureLength() > 2 { // has captures + idx = 2 + } + var value LValue + if match.IsPosCapture(idx) { + value = L.GetTable(repl, LNumber(match.Capture(idx))) + } else { + value = L.GetField(repl, str[match.Capture(idx):match.Capture(idx+1)]) + } + if !LVIsFalse(value) { + infoList = append(infoList, replaceInfo{[]int{match.Capture(0), match.Capture(1)}, LVAsString(value)}) + } + } + return strGsubDoReplace(str, infoList) +} + +func strGsubFunc(L *LState, str string, repl *LFunction, matches []*pm.MatchData) string { + infoList := make([]replaceInfo, 0, len(matches)) + for _, match := range matches { + start, end := match.Capture(0), match.Capture(1) + L.Push(repl) + nargs := 0 + if match.CaptureLength() > 2 { // has captures + for i := 2; i < match.CaptureLength(); i += 2 { + if match.IsPosCapture(i) { + L.Push(LNumber(match.Capture(i))) + } else { + L.Push(LString(capturedString(L, match, str, i))) + } + nargs++ + } + } else { + L.Push(LString(capturedString(L, match, str, 0))) + nargs++ + } + L.Call(nargs, 1) + ret := L.reg.Pop() + if !LVIsFalse(ret) { + infoList = append(infoList, replaceInfo{[]int{start, end}, LVAsString(ret)}) + } + } + return strGsubDoReplace(str, infoList) +} + +type strMatchData struct { + str string + pos int + matches []*pm.MatchData +} + +func strGmatchIter(L *LState) int { + md := L.CheckUserData(1).Value.(*strMatchData) + str := md.str + matches := md.matches + idx := md.pos + md.pos += 1 + if idx == len(matches) { + return 0 + } + L.Push(L.Get(1)) + match := matches[idx] + if match.CaptureLength() == 2 { + L.Push(LString(str[match.Capture(0):match.Capture(1)])) + return 1 + } + + for i := 2; i < match.CaptureLength(); i += 2 { + if match.IsPosCapture(i) { + L.Push(LNumber(match.Capture(i))) + } else { + L.Push(LString(str[match.Capture(i):match.Capture(i+1)])) + } + } + return match.CaptureLength()/2 - 1 +} + +func strGmatch(L *LState) int { + str := L.CheckString(1) + pattern := L.CheckString(2) + mds, err := pm.Find(pattern, []byte(str), 0, -1) + if err != nil { + L.RaiseError(err.Error()) + } + L.Push(L.Get(UpvalueIndex(1))) + ud := L.NewUserData() + ud.Value = &strMatchData{str, 0, mds} + L.Push(ud) + return 2 +} + +func strLen(L *LState) int { + str := L.CheckString(1) + L.Push(LNumber(len(str))) + return 1 +} + +func strLower(L *LState) int { + str := L.CheckString(1) + L.Push(LString(strings.ToLower(str))) + return 1 +} + +func strMatch(L *LState) int { + str := L.CheckString(1) + pattern := L.CheckString(2) + offset := L.OptInt(3, 1) + l := len(str) + if offset < 0 { + offset = l + offset + 1 + } + offset-- + if offset < 0 { + offset = 0 + } + + mds, err := pm.Find(pattern, unsafeFastStringToReadOnlyBytes(str), offset, 1) + if err != nil { + L.RaiseError(err.Error()) + } + if len(mds) == 0 { + L.Push(LNil) + return 0 + } + md := mds[0] + nsubs := md.CaptureLength() / 2 + switch nsubs { + case 1: + L.Push(LString(str[md.Capture(0):md.Capture(1)])) + return 1 + default: + for i := 2; i < md.CaptureLength(); i += 2 { + if md.IsPosCapture(i) { + L.Push(LNumber(md.Capture(i))) + } else { + L.Push(LString(str[md.Capture(i):md.Capture(i+1)])) + } + } + return nsubs - 1 + } +} + +func strRep(L *LState) int { + str := L.CheckString(1) + n := L.CheckInt(2) + if n < 0 { + L.Push(emptyLString) + } else { + L.Push(LString(strings.Repeat(str, n))) + } + return 1 +} + +func strReverse(L *LState) int { + str := L.CheckString(1) + bts := []byte(str) + out := make([]byte, len(bts)) + for i, j := 0, len(bts)-1; j >= 0; i, j = i+1, j-1 { + out[i] = bts[j] + } + L.Push(LString(string(out))) + return 1 +} + +func strSub(L *LState) int { + str := L.CheckString(1) + start := luaIndex2StringIndex(str, L.CheckInt(2), true) + end := luaIndex2StringIndex(str, L.OptInt(3, -1), false) + l := len(str) + if start >= l || end < start { + L.Push(emptyLString) + } else { + L.Push(LString(str[start:end])) + } + return 1 +} + +func strUpper(L *LState) int { + str := L.CheckString(1) + L.Push(LString(strings.ToUpper(str))) + return 1 +} + +func luaIndex2StringIndex(str string, i int, start bool) int { + if start && i != 0 { + i -= 1 + } + l := len(str) + if i < 0 { + i = l + i + 1 + } + i = intMax(0, i) + if !start && i > l { + i = l + } + return i +} + +// diff --git a/vendor/github.com/yuin/gopher-lua/table.go b/vendor/github.com/yuin/gopher-lua/table.go new file mode 100644 index 00000000000..e220bd9c3b6 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/table.go @@ -0,0 +1,387 @@ +package lua + +const defaultArrayCap = 32 +const defaultHashCap = 32 + +type lValueArraySorter struct { + L *LState + Fn *LFunction + Values []LValue +} + +func (lv lValueArraySorter) Len() int { + return len(lv.Values) +} + +func (lv lValueArraySorter) Swap(i, j int) { + lv.Values[i], lv.Values[j] = lv.Values[j], lv.Values[i] +} + +func (lv lValueArraySorter) Less(i, j int) bool { + if lv.Fn != nil { + lv.L.Push(lv.Fn) + lv.L.Push(lv.Values[i]) + lv.L.Push(lv.Values[j]) + lv.L.Call(2, 1) + return LVAsBool(lv.L.reg.Pop()) + } + return lessThan(lv.L, lv.Values[i], lv.Values[j]) +} + +func newLTable(acap int, hcap int) *LTable { + if acap < 0 { + acap = 0 + } + if hcap < 0 { + hcap = 0 + } + tb := <able{} + tb.Metatable = LNil + if acap != 0 { + tb.array = make([]LValue, 0, acap) + } + if hcap != 0 { + tb.strdict = make(map[string]LValue, hcap) + } + return tb +} + +// Len returns length of this LTable. +func (tb *LTable) Len() int { + if tb.array == nil { + return 0 + } + var prev LValue = LNil + for i := len(tb.array) - 1; i >= 0; i-- { + v := tb.array[i] + if prev == LNil && v != LNil { + return i + 1 + } + prev = v + } + return 0 +} + +// Append appends a given LValue to this LTable. +func (tb *LTable) Append(value LValue) { + if value == LNil { + return + } + if tb.array == nil { + tb.array = make([]LValue, 0, defaultArrayCap) + } + if len(tb.array) == 0 || tb.array[len(tb.array)-1] != LNil { + tb.array = append(tb.array, value) + } else { + i := len(tb.array) - 2 + for ; i >= 0; i-- { + if tb.array[i] != LNil { + break + } + } + tb.array[i+1] = value + } +} + +// Insert inserts a given LValue at position `i` in this table. +func (tb *LTable) Insert(i int, value LValue) { + if tb.array == nil { + tb.array = make([]LValue, 0, defaultArrayCap) + } + if i > len(tb.array) { + tb.RawSetInt(i, value) + return + } + if i <= 0 { + tb.RawSet(LNumber(i), value) + return + } + i -= 1 + tb.array = append(tb.array, LNil) + copy(tb.array[i+1:], tb.array[i:]) + tb.array[i] = value +} + +// MaxN returns a maximum number key that nil value does not exist before it. +func (tb *LTable) MaxN() int { + if tb.array == nil { + return 0 + } + for i := len(tb.array) - 1; i >= 0; i-- { + if tb.array[i] != LNil { + return i + 1 + } + } + return 0 +} + +// Remove removes from this table the element at a given position. +func (tb *LTable) Remove(pos int) LValue { + if tb.array == nil { + return LNil + } + larray := len(tb.array) + if larray == 0 { + return LNil + } + i := pos - 1 + oldval := LNil + switch { + case i >= larray: + // nothing to do + case i == larray-1 || i < 0: + oldval = tb.array[larray-1] + tb.array = tb.array[:larray-1] + default: + oldval = tb.array[i] + copy(tb.array[i:], tb.array[i+1:]) + tb.array[larray-1] = nil + tb.array = tb.array[:larray-1] + } + return oldval +} + +// RawSet sets a given LValue to a given index without the __newindex metamethod. +// It is recommended to use `RawSetString` or `RawSetInt` for performance +// if you already know the given LValue is a string or number. +func (tb *LTable) RawSet(key LValue, value LValue) { + switch v := key.(type) { + case LNumber: + if isArrayKey(v) { + if tb.array == nil { + tb.array = make([]LValue, 0, defaultArrayCap) + } + index := int(v) - 1 + alen := len(tb.array) + switch { + case index == alen: + tb.array = append(tb.array, value) + case index > alen: + for i := 0; i < (index - alen); i++ { + tb.array = append(tb.array, LNil) + } + tb.array = append(tb.array, value) + case index < alen: + tb.array[index] = value + } + return + } + case LString: + tb.RawSetString(string(v), value) + return + } + + tb.RawSetH(key, value) +} + +// RawSetInt sets a given LValue at a position `key` without the __newindex metamethod. +func (tb *LTable) RawSetInt(key int, value LValue) { + if key < 1 || key >= MaxArrayIndex { + tb.RawSetH(LNumber(key), value) + return + } + if tb.array == nil { + tb.array = make([]LValue, 0, 32) + } + index := key - 1 + alen := len(tb.array) + switch { + case index == alen: + tb.array = append(tb.array, value) + case index > alen: + for i := 0; i < (index - alen); i++ { + tb.array = append(tb.array, LNil) + } + tb.array = append(tb.array, value) + case index < alen: + tb.array[index] = value + } +} + +// RawSetString sets a given LValue to a given string index without the __newindex metamethod. +func (tb *LTable) RawSetString(key string, value LValue) { + if tb.strdict == nil { + tb.strdict = make(map[string]LValue, defaultHashCap) + } + if tb.keys == nil { + tb.keys = []LValue{} + tb.k2i = map[LValue]int{} + } + + if value == LNil { + // TODO tb.keys and tb.k2i should also be removed + delete(tb.strdict, key) + } else { + tb.strdict[key] = value + lkey := LString(key) + if _, ok := tb.k2i[lkey]; !ok { + tb.k2i[lkey] = len(tb.keys) + tb.keys = append(tb.keys, lkey) + } + } +} + +// RawSetH sets a given LValue to a given index without the __newindex metamethod. +func (tb *LTable) RawSetH(key LValue, value LValue) { + if s, ok := key.(LString); ok { + tb.RawSetString(string(s), value) + return + } + if tb.dict == nil { + tb.dict = make(map[LValue]LValue, len(tb.strdict)) + } + if tb.keys == nil { + tb.keys = []LValue{} + tb.k2i = map[LValue]int{} + } + + if value == LNil { + // TODO tb.keys and tb.k2i should also be removed + delete(tb.dict, key) + } else { + tb.dict[key] = value + if _, ok := tb.k2i[key]; !ok { + tb.k2i[key] = len(tb.keys) + tb.keys = append(tb.keys, key) + } + } +} + +// RawGet returns an LValue associated with a given key without __index metamethod. +func (tb *LTable) RawGet(key LValue) LValue { + switch v := key.(type) { + case LNumber: + if isArrayKey(v) { + if tb.array == nil { + return LNil + } + index := int(v) - 1 + if index >= len(tb.array) { + return LNil + } + return tb.array[index] + } + case LString: + if tb.strdict == nil { + return LNil + } + if ret, ok := tb.strdict[string(v)]; ok { + return ret + } + return LNil + } + if tb.dict == nil { + return LNil + } + if v, ok := tb.dict[key]; ok { + return v + } + return LNil +} + +// RawGetInt returns an LValue at position `key` without __index metamethod. +func (tb *LTable) RawGetInt(key int) LValue { + if tb.array == nil { + return LNil + } + index := int(key) - 1 + if index >= len(tb.array) || index < 0 { + return LNil + } + return tb.array[index] +} + +// RawGet returns an LValue associated with a given key without __index metamethod. +func (tb *LTable) RawGetH(key LValue) LValue { + if s, sok := key.(LString); sok { + if tb.strdict == nil { + return LNil + } + if v, vok := tb.strdict[string(s)]; vok { + return v + } + return LNil + } + if tb.dict == nil { + return LNil + } + if v, ok := tb.dict[key]; ok { + return v + } + return LNil +} + +// RawGetString returns an LValue associated with a given key without __index metamethod. +func (tb *LTable) RawGetString(key string) LValue { + if tb.strdict == nil { + return LNil + } + if v, vok := tb.strdict[string(key)]; vok { + return v + } + return LNil +} + +// ForEach iterates over this table of elements, yielding each in turn to a given function. +func (tb *LTable) ForEach(cb func(LValue, LValue)) { + if tb.array != nil { + for i, v := range tb.array { + if v != LNil { + cb(LNumber(i+1), v) + } + } + } + if tb.strdict != nil { + for k, v := range tb.strdict { + if v != LNil { + cb(LString(k), v) + } + } + } + if tb.dict != nil { + for k, v := range tb.dict { + if v != LNil { + cb(k, v) + } + } + } +} + +// This function is equivalent to lua_next ( http://www.lua.org/manual/5.1/manual.html#lua_next ). +func (tb *LTable) Next(key LValue) (LValue, LValue) { + init := false + if key == LNil { + key = LNumber(0) + init = true + } + + if init || key != LNumber(0) { + if kv, ok := key.(LNumber); ok && isInteger(kv) && int(kv) >= 0 && kv < LNumber(MaxArrayIndex) { + index := int(kv) + if tb.array != nil { + for ; index < len(tb.array); index++ { + if v := tb.array[index]; v != LNil { + return LNumber(index + 1), v + } + } + } + if tb.array == nil || index == len(tb.array) { + if (tb.dict == nil || len(tb.dict) == 0) && (tb.strdict == nil || len(tb.strdict) == 0) { + return LNil, LNil + } + key = tb.keys[0] + if v := tb.RawGetH(key); v != LNil { + return key, v + } + } + } + } + + for i := tb.k2i[key] + 1; i < len(tb.keys); i++ { + key := tb.keys[i] + if v := tb.RawGetH(key); v != LNil { + return key, v + } + } + return LNil, LNil +} diff --git a/vendor/github.com/yuin/gopher-lua/tablelib.go b/vendor/github.com/yuin/gopher-lua/tablelib.go new file mode 100644 index 00000000000..f3f460702f0 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/tablelib.go @@ -0,0 +1,100 @@ +package lua + +import ( + "sort" +) + +func OpenTable(L *LState) int { + tabmod := L.RegisterModule(TabLibName, tableFuncs) + L.Push(tabmod) + return 1 +} + +var tableFuncs = map[string]LGFunction{ + "getn": tableGetN, + "concat": tableConcat, + "insert": tableInsert, + "maxn": tableMaxN, + "remove": tableRemove, + "sort": tableSort, +} + +func tableSort(L *LState) int { + tbl := L.CheckTable(1) + sorter := lValueArraySorter{L, nil, tbl.array} + if L.GetTop() != 1 { + sorter.Fn = L.CheckFunction(2) + } + sort.Sort(sorter) + return 0 +} + +func tableGetN(L *LState) int { + L.Push(LNumber(L.CheckTable(1).Len())) + return 1 +} + +func tableMaxN(L *LState) int { + L.Push(LNumber(L.CheckTable(1).MaxN())) + return 1 +} + +func tableRemove(L *LState) int { + tbl := L.CheckTable(1) + if L.GetTop() == 1 { + L.Push(tbl.Remove(-1)) + } else { + L.Push(tbl.Remove(L.CheckInt(2))) + } + return 1 +} + +func tableConcat(L *LState) int { + tbl := L.CheckTable(1) + sep := LString(L.OptString(2, "")) + i := L.OptInt(3, 1) + j := L.OptInt(4, tbl.Len()) + if L.GetTop() == 3 { + if i > tbl.Len() || i < 1 { + L.Push(emptyLString) + return 1 + } + } + i = intMax(intMin(i, tbl.Len()), 1) + j = intMin(intMin(j, tbl.Len()), tbl.Len()) + if i > j { + L.Push(emptyLString) + return 1 + } + //TODO should flushing? + retbottom := L.GetTop() + for ; i <= j; i++ { + v := tbl.RawGetInt(i) + if !LVCanConvToString(v) { + L.RaiseError("invalid value (%s) at index %d in table for concat", v.Type().String(), i) + } + L.Push(v) + if i != j { + L.Push(sep) + } + } + L.Push(stringConcat(L, L.GetTop()-retbottom, L.reg.Top()-1)) + return 1 +} + +func tableInsert(L *LState) int { + tbl := L.CheckTable(1) + nargs := L.GetTop() + if nargs == 1 { + L.RaiseError("wrong number of arguments") + } + + if L.GetTop() == 2 { + tbl.Append(L.Get(2)) + return 0 + } + tbl.Insert(int(L.CheckInt(2)), L.CheckAny(3)) + return 0 +} + +// diff --git a/vendor/github.com/yuin/gopher-lua/utils.go b/vendor/github.com/yuin/gopher-lua/utils.go new file mode 100644 index 00000000000..2df68dc73eb --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/utils.go @@ -0,0 +1,265 @@ +package lua + +import ( + "bufio" + "fmt" + "io" + "reflect" + "strconv" + "strings" + "time" + "unsafe" +) + +func intMin(a, b int) int { + if a < b { + return a + } else { + return b + } +} + +func intMax(a, b int) int { + if a > b { + return a + } else { + return b + } +} + +func defaultFormat(v interface{}, f fmt.State, c rune) { + buf := make([]string, 0, 10) + buf = append(buf, "%") + for i := 0; i < 128; i++ { + if f.Flag(i) { + buf = append(buf, string(rune(i))) + } + } + + if w, ok := f.Width(); ok { + buf = append(buf, strconv.Itoa(w)) + } + if p, ok := f.Precision(); ok { + buf = append(buf, "."+strconv.Itoa(p)) + } + buf = append(buf, string(c)) + format := strings.Join(buf, "") + fmt.Fprintf(f, format, v) +} + +type flagScanner struct { + flag byte + start string + end string + buf []byte + str string + Length int + Pos int + HasFlag bool + ChangeFlag bool +} + +func newFlagScanner(flag byte, start, end, str string) *flagScanner { + return &flagScanner{flag, start, end, make([]byte, 0, len(str)), str, len(str), 0, false, false} +} + +func (fs *flagScanner) AppendString(str string) { fs.buf = append(fs.buf, str...) } + +func (fs *flagScanner) AppendChar(ch byte) { fs.buf = append(fs.buf, ch) } + +func (fs *flagScanner) String() string { return string(fs.buf) } + +func (fs *flagScanner) Next() (byte, bool) { + c := byte('\000') + fs.ChangeFlag = false + if fs.Pos == fs.Length { + if fs.HasFlag { + fs.AppendString(fs.end) + } + return c, true + } else { + c = fs.str[fs.Pos] + if c == fs.flag { + if fs.Pos < (fs.Length-1) && fs.str[fs.Pos+1] == fs.flag { + fs.HasFlag = false + fs.AppendChar(fs.flag) + fs.Pos += 2 + return fs.Next() + } else if fs.Pos != fs.Length-1 { + if fs.HasFlag { + fs.AppendString(fs.end) + } + fs.AppendString(fs.start) + fs.ChangeFlag = true + fs.HasFlag = true + } + } + } + fs.Pos++ + return c, false +} + +var cDateFlagToGo = map[byte]string{ + 'a': "mon", 'A': "Monday", 'b': "Jan", 'B': "January", 'c': "02 Jan 06 15:04 MST", 'd': "02", + 'F': "2006-01-02", 'H': "15", 'I': "03", 'm': "01", 'M': "04", 'p': "PM", 'P': "pm", 'S': "05", + 'x': "15/04/05", 'X': "15:04:05", 'y': "06", 'Y': "2006", 'z': "-0700", 'Z': "MST"} + +func strftime(t time.Time, cfmt string) string { + sc := newFlagScanner('%', "", "", cfmt) + for c, eos := sc.Next(); !eos; c, eos = sc.Next() { + if !sc.ChangeFlag { + if sc.HasFlag { + if v, ok := cDateFlagToGo[c]; ok { + sc.AppendString(t.Format(v)) + } else { + switch c { + case 'w': + sc.AppendString(fmt.Sprint(int(t.Weekday()))) + default: + sc.AppendChar('%') + sc.AppendChar(c) + } + } + sc.HasFlag = false + } else { + sc.AppendChar(c) + } + } + } + + return sc.String() +} + +func isInteger(v LNumber) bool { + return float64(v) == float64(int64(v)) + //_, frac := math.Modf(float64(v)) + //return frac == 0.0 +} + +func isArrayKey(v LNumber) bool { + return isInteger(v) && v < LNumber(int((^uint(0))>>1)) && v > LNumber(0) && v < LNumber(MaxArrayIndex) +} + +func parseNumber(number string) (LNumber, error) { + var value LNumber + number = strings.Trim(number, " \t\n") + if v, err := strconv.ParseInt(number, 0, LNumberBit); err != nil { + if v2, err2 := strconv.ParseFloat(number, LNumberBit); err2 != nil { + return LNumber(0), err2 + } else { + value = LNumber(v2) + } + } else { + value = LNumber(v) + } + return value, nil +} + +func popenArgs(arg string) (string, []string) { + cmd := "/bin/sh" + args := []string{"-c"} + if LuaOS == "windows" { + cmd = "C:\\Windows\\system32\\cmd.exe" + args = []string{"/c"} + } + args = append(args, arg) + return cmd, args +} + +func isGoroutineSafe(lv LValue) bool { + switch v := lv.(type) { + case *LFunction, *LUserData, *LState: + return false + case *LTable: + return v.Metatable == LNil + default: + return true + } +} + +func readBufioSize(reader *bufio.Reader, size int64) ([]byte, error, bool) { + result := []byte{} + read := int64(0) + var err error + var n int + for read != size { + buf := make([]byte, size-read) + n, err = reader.Read(buf) + if err != nil { + break + } + read += int64(n) + result = append(result, buf[:n]...) + } + e := err + if e != nil && e == io.EOF { + e = nil + } + + return result, e, len(result) == 0 && err == io.EOF +} + +func readBufioLine(reader *bufio.Reader) ([]byte, error, bool) { + result := []byte{} + var buf []byte + var err error + var isprefix bool = true + for isprefix { + buf, isprefix, err = reader.ReadLine() + if err != nil { + break + } + result = append(result, buf...) + } + e := err + if e != nil && e == io.EOF { + e = nil + } + + return result, e, len(result) == 0 && err == io.EOF +} + +func int2Fb(val int) int { + e := 0 + x := val + for x >= 16 { + x = (x + 1) >> 1 + e++ + } + if x < 8 { + return x + } + return ((e + 1) << 3) | (x - 8) +} + +func strCmp(s1, s2 string) int { + len1 := len(s1) + len2 := len(s2) + for i := 0; ; i++ { + c1 := -1 + if i < len1 { + c1 = int(s1[i]) + } + c2 := -1 + if i != len2 { + c2 = int(s2[i]) + } + switch { + case c1 < c2: + return -1 + case c1 > c2: + return +1 + case c1 < 0: + return 0 + } + } +} + +func unsafeFastStringToReadOnlyBytes(s string) (bs []byte) { + sh := (*reflect.StringHeader)(unsafe.Pointer(&s)) + bh := (*reflect.SliceHeader)(unsafe.Pointer(&bs)) + bh.Data = sh.Data + bh.Cap = sh.Len + bh.Len = sh.Len + return +} diff --git a/vendor/github.com/yuin/gopher-lua/value.go b/vendor/github.com/yuin/gopher-lua/value.go new file mode 100644 index 00000000000..0d4af80816e --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/value.go @@ -0,0 +1,247 @@ +package lua + +import ( + "context" + "fmt" + "os" +) + +type LValueType int + +const ( + LTNil LValueType = iota + LTBool + LTNumber + LTString + LTFunction + LTUserData + LTThread + LTTable + LTChannel +) + +var lValueNames = [9]string{"nil", "boolean", "number", "string", "function", "userdata", "thread", "table", "channel"} + +func (vt LValueType) String() string { + return lValueNames[int(vt)] +} + +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. +func LVIsFalse(v LValue) bool { return v == LNil || v == LFalse } + +// LVIsFalse returns false if a given LValue is a nil or false otherwise true. +func LVAsBool(v LValue) bool { return v != LNil && v != LFalse } + +// LVAsString returns string representation of a given LValue +// if the LValue is a string or number, otherwise an empty string. +func LVAsString(v LValue) string { + switch sn := v.(type) { + case LString, LNumber: + return sn.String() + default: + return "" + } +} + +// LVCanConvToString returns true if a given LValue is a string or number +// otherwise false. +func LVCanConvToString(v LValue) bool { + switch v.(type) { + case LString, LNumber: + return true + default: + return false + } +} + +// LVAsNumber tries to convert a given LValue to a number. +func LVAsNumber(v LValue) LNumber { + switch lv := v.(type) { + case LNumber: + return lv + case LString: + if num, err := parseNumber(string(lv)); err == nil { + return num + } + } + return LNumber(0) +} + +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 } + +var LNil = LValue(&LNilType{}) + +type LBool bool + +func (bl LBool) String() string { + if bool(bl) { + return "true" + } + 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 } + +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 } + +// fmt.Formatter interface +func (st LString) Format(f fmt.State, c rune) { + switch c { + case 'd', 'i': + if nm, err := parseNumber(string(st)); err != nil { + defaultFormat(nm, f, 'd') + } else { + defaultFormat(string(st), f, 's') + } + default: + defaultFormat(string(st), f, c) + } +} + +func (nm LNumber) String() string { + if isInteger(nm) { + return fmt.Sprint(int64(nm)) + } + 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 } + +// fmt.Formatter interface +func (nm LNumber) Format(f fmt.State, c rune) { + switch c { + case 'q', 's': + defaultFormat(nm.String(), f, c) + case 'b', 'c', 'd', 'o', 'x', 'X', 'U': + defaultFormat(int64(nm), f, c) + case 'e', 'E', 'f', 'F', 'g', 'G': + defaultFormat(float64(nm), f, c) + case 'i': + defaultFormat(int64(nm), f, 'd') + default: + if isInteger(nm) { + defaultFormat(int64(nm), f, c) + } else { + defaultFormat(float64(nm), f, c) + } + } +} + +type LTable struct { + Metatable LValue + + array []LValue + dict map[LValue]LValue + strdict map[string]LValue + keys []LValue + 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 } + +type LFunction struct { + IsG bool + Env *LTable + Proto *FunctionProto + GFunction LGFunction + Upvalues []*Upvalue +} +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 } + +type Global struct { + MainThread *LState + CurrentThread *LState + Registry *LTable + Global *LTable + + builtinMts map[int]LValue + tempFiles []*os.File + gccount int32 +} + +type LState struct { + G *Global + Parent *LState + Env *LTable + Panic func(*LState) + Dead bool + Options Options + + stop int32 + reg *registry + stack callFrameStack + alloc *allocator + currentFrame *callFrame + wrapped bool + uvcache *Upvalue + hasErrorFunc bool + mainLoop func(*LState, *callFrame) + ctx context.Context +} + +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 } + +type LUserData struct { + Value interface{} + Env *LTable + 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 } + +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 } diff --git a/vendor/github.com/yuin/gopher-lua/vm.go b/vendor/github.com/yuin/gopher-lua/vm.go new file mode 100644 index 00000000000..c3c17bdb776 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/vm.go @@ -0,0 +1,1718 @@ +package lua + +//////////////////////////////////////////////////////// +// This file was generated by go-inline. DO NOT EDIT. // +//////////////////////////////////////////////////////// + +import ( + "fmt" + "math" + "strings" +) + +func mainLoop(L *LState, baseframe *callFrame) { + var inst uint32 + var cf *callFrame + + if L.stack.IsEmpty() { + return + } + + L.currentFrame = L.stack.Last() + if L.currentFrame.Fn.IsG { + callGFunction(L, false) + return + } + + for { + cf = L.currentFrame + inst = cf.Fn.Proto.Code[cf.Pc] + cf.Pc++ + if jumpTable[int(inst>>26)](L, inst, baseframe) == 1 { + return + } + } +} + +func mainLoopWithContext(L *LState, baseframe *callFrame) { + var inst uint32 + var cf *callFrame + + if L.stack.IsEmpty() { + return + } + + L.currentFrame = L.stack.Last() + if L.currentFrame.Fn.IsG { + callGFunction(L, false) + return + } + + for { + cf = L.currentFrame + inst = cf.Fn.Proto.Code[cf.Pc] + cf.Pc++ + select { + case <-L.ctx.Done(): + L.RaiseError(L.ctx.Err().Error()) + return + default: + if jumpTable[int(inst>>26)](L, inst, baseframe) == 1 { + return + } + } + } +} + +// regv is the first target register to copy the return values to. +// It can be reg.top, indicating that the copied values are going into new registers, or it can be below reg.top +// Indicating that the values should be within the existing registers. +// b is the available number of return values + 1. +// n is the desired number of return values. +// If n more than the available return values then the extra values are set to nil. +// When this function returns the top of the registry will be set to regv+n. +func copyReturnValues(L *LState, regv, start, n, b int) { // +inline-start + if b == 1 { + // this section is inlined by go-inline + // source function is 'func (rg *registry) FillNil(regm, n int) ' in '_state.go' + { + rg := L.reg + regm := regv + newSize := regm + n + // 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) + } + } + for i := 0; i < n; i++ { + rg.array[regm+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 + oldtop := rg.top + rg.top = regm + n + if rg.top < oldtop { + nilRange := rg.array[rg.top:oldtop] + for i := range nilRange { + nilRange[i] = nil + } + } + } + } else { + // this section is inlined by go-inline + // source function is 'func (rg *registry) CopyRange(regv, start, limit, n int) ' in '_state.go' + { + rg := L.reg + limit := -1 + newSize := regv + n + // 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) + } + } + if limit == -1 || limit > rg.top { + limit = rg.top + } + for i := 0; i < n; i++ { + srcIdx := start + i + if srcIdx >= limit || srcIdx < 0 { + rg.array[regv+i] = LNil + } else { + rg.array[regv+i] = rg.array[srcIdx] + } + } + + // 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 + oldtop := rg.top + rg.top = regv + n + if rg.top < oldtop { + nilRange := rg.array[rg.top:oldtop] + for i := range nilRange { + nilRange[i] = nil + } + } + } + if b > 1 && n > (b-1) { + // this section is inlined by go-inline + // source function is 'func (rg *registry) FillNil(regm, n int) ' in '_state.go' + { + rg := L.reg + regm := regv + b - 1 + n := n - (b - 1) + newSize := regm + n + // 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) + } + } + for i := 0; i < n; i++ { + rg.array[regm+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 + oldtop := rg.top + rg.top = regm + n + if rg.top < oldtop { + nilRange := rg.array[rg.top:oldtop] + for i := range nilRange { + nilRange[i] = nil + } + } + } + } + } +} // +inline-end + +func switchToParentThread(L *LState, nargs int, haserror bool, kill bool) { + parent := L.Parent + if parent == nil { + L.RaiseError("can not yield from outside of a coroutine") + } + L.G.CurrentThread = parent + L.Parent = nil + if !L.wrapped { + if haserror { + parent.Push(LFalse) + } else { + parent.Push(LTrue) + } + } + L.XMoveTo(parent, nargs) + L.stack.Pop() + offset := L.currentFrame.LocalBase - L.currentFrame.ReturnBase + L.currentFrame = L.stack.Last() + L.reg.SetTop(L.reg.Top() - offset) // remove 'yield' function(including tailcalled functions) + if kill { + L.kill() + } +} + +func callGFunction(L *LState, tailcall bool) bool { + frame := L.currentFrame + gfnret := frame.Fn.GFunction(L) + if tailcall { + L.currentFrame = L.RemoveCallerFrame() + } + + if gfnret < 0 { + switchToParentThread(L, L.GetTop(), false, false) + return true + } + + wantret := frame.NRet + if wantret == MultRet { + wantret = gfnret + } + + if tailcall && L.Parent != nil && L.stack.Sp() == 1 { + switchToParentThread(L, wantret, false, true) + return true + } + + // this section is inlined by go-inline + // source function is 'func (rg *registry) CopyRange(regv, start, limit, n int) ' in '_state.go' + { + rg := L.reg + regv := frame.ReturnBase + start := L.reg.Top() - gfnret + limit := -1 + n := wantret + newSize := regv + n + // 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) + } + } + if limit == -1 || limit > rg.top { + limit = rg.top + } + for i := 0; i < n; i++ { + srcIdx := start + i + if srcIdx >= limit || srcIdx < 0 { + rg.array[regv+i] = LNil + } else { + rg.array[regv+i] = rg.array[srcIdx] + } + } + + // 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 + oldtop := rg.top + rg.top = regv + n + if rg.top < oldtop { + nilRange := rg.array[rg.top:oldtop] + for i := range nilRange { + nilRange[i] = nil + } + } + } + L.stack.Pop() + L.currentFrame = L.stack.Last() + return false +} + +func threadRun(L *LState) { + if L.stack.IsEmpty() { + return + } + + defer func() { + if rcv := recover(); rcv != nil { + var lv LValue + if v, ok := rcv.(*ApiError); ok { + lv = v.Object + } else { + lv = LString(fmt.Sprint(rcv)) + } + if parent := L.Parent; parent != nil { + if L.wrapped { + L.Push(lv) + parent.Panic(L) + } else { + L.SetTop(0) + L.Push(lv) + switchToParentThread(L, 1, true, true) + } + } else { + panic(rcv) + } + } + }() + L.mainLoop(L, nil) +} + +type instFunc func(*LState, uint32, *callFrame) int + +var jumpTable [opCodeMax + 1]instFunc + +func init() { + jumpTable = [opCodeMax + 1]instFunc{ + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_MOVE + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + reg.Set(RA, reg.Get(lbase+B)) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_MOVEN + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + 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)) + code := cf.Fn.Proto.Code + pc := cf.Pc + for i := 0; i < C; i++ { + inst = code[pc] + pc++ + A = int(inst>>18) & 0xff //GETA + B = int(inst & 0x1ff) //GETB + reg.Set(lbase+A, reg.Get(lbase+B)) + } + cf.Pc = pc + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_LOADK + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + Bx := int(inst & 0x3ffff) //GETBX + reg.Set(RA, cf.Fn.Proto.Constants[Bx]) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_LOADBOOL + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + if B != 0 { + reg.Set(RA, LTrue) + } else { + reg.Set(RA, LFalse) + } + if C != 0 { + cf.Pc++ + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_LOADNIL + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + for i := RA; i <= lbase+B; i++ { + reg.Set(i, LNil) + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_GETUPVAL + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + reg.Set(RA, cf.Fn.Upvalues[B].Value()) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_GETGLOBAL + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + 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])) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_GETTABLE + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + 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))) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_GETTABLEKS + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + 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))) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_SETGLOBAL + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + Bx := int(inst & 0x3ffff) //GETBX + //L.setField(cf.Fn.Env, cf.Fn.Proto.Constants[Bx], reg.Get(RA)) + L.setFieldString(cf.Fn.Env, cf.Fn.Proto.stringConstants[Bx], reg.Get(RA)) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_SETUPVAL + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + cf.Fn.Upvalues[B].SetValue(reg.Get(RA)) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_SETTABLE + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + L.setField(reg.Get(RA), L.rkValue(B), L.rkValue(C)) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_SETTABLEKS + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + L.setFieldString(reg.Get(RA), L.rkString(B), L.rkValue(C)) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_NEWTABLE + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + reg.Set(RA, newLTable(B, C)) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_SELF + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + 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) + return 0 + }, + opArith, // OP_ADD + opArith, // OP_SUB + opArith, // OP_MUL + opArith, // OP_DIV + opArith, // OP_MOD + opArith, // OP_POW + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_UNM + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + unaryv := L.rkValue(B) + if nm, ok := unaryv.(LNumber); ok { + reg.SetNumber(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()) + } else if str, ok1 := unaryv.(LString); ok1 { + if num, err := parseNumber(string(str)); err == nil { + reg.Set(RA, -num) + } else { + L.RaiseError("__unm undefined") + } + } else { + L.RaiseError("__unm undefined") + } + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_NOT + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + if LVIsFalse(reg.Get(lbase + B)) { + reg.Set(RA, LTrue) + } else { + reg.Set(RA, LFalse) + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_LEN + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + switch lv := L.rkValue(B).(type) { + case LString: + reg.SetNumber(RA, LNumber(len(lv))) + default: + op := L.metaOp1(lv, "__len") + if op.Type() == LTFunction { + reg.Push(op) + reg.Push(lv) + L.Call(1, 1) + ret := reg.Pop() + if ret.Type() == LTNumber { + reg.SetNumber(RA, ret.(LNumber)) + } else { + reg.SetNumber(RA, LNumber(0)) + } + } else if lv.Type() == LTTable { + reg.SetNumber(RA, LNumber(lv.(*LTable).Len())) + } else { + L.RaiseError("__len undefined") + } + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_CONCAT + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + RC := lbase + C + RB := lbase + B + reg.Set(RA, stringConcat(L, RC-RB+1, RC)) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_JMP + cf := L.currentFrame + Sbx := int(inst&0x3ffff) - opMaxArgSbx //GETSBX + cf.Pc += Sbx + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_EQ + cf := L.currentFrame + A := int(inst>>18) & 0xff //GETA + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + ret := equals(L, L.rkValue(B), L.rkValue(C), false) + v := 1 + if ret { + v = 0 + } + if v == A { + cf.Pc++ + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_LT + cf := L.currentFrame + A := int(inst>>18) & 0xff //GETA + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + ret := lessThan(L, L.rkValue(B), L.rkValue(C)) + v := 1 + if ret { + v = 0 + } + if v == A { + cf.Pc++ + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_LE + cf := L.currentFrame + A := int(inst>>18) & 0xff //GETA + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + lhs := L.rkValue(B) + rhs := L.rkValue(C) + ret := false + + if v1, ok1 := lhs.assertFloat64(); ok1 { + if v2, ok2 := rhs.assertFloat64(); ok2 { + ret = v1 <= v2 + } else { + L.RaiseError("attempt to compare %v with %v", lhs.Type().String(), rhs.Type().String()) + } + } else { + if lhs.Type() != rhs.Type() { + L.RaiseError("attempt to compare %v with %v", lhs.Type().String(), rhs.Type().String()) + } + switch lhs.Type() { + case LTString: + ret = strCmp(string(lhs.(LString)), string(rhs.(LString))) <= 0 + default: + switch objectRational(L, lhs, rhs, "__le") { + case 1: + ret = true + case 0: + ret = false + default: + ret = !objectRationalWithError(L, rhs, lhs, "__lt") + } + } + } + + v := 1 + if ret { + v = 0 + } + if v == A { + cf.Pc++ + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_TEST + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + C := int(inst>>9) & 0x1ff //GETC + if LVAsBool(reg.Get(RA)) == (C == 0) { + cf.Pc++ + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_TESTSET + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + 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) + } else { + cf.Pc++ + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_CALL + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + nargs := B - 1 + if B == 0 { + nargs = reg.Top() - (RA + 1) + } + lv := reg.Get(RA) + nret := C - 1 + var callable *LFunction + var meta bool + if fn, ok := lv.assertFunction(); ok { + callable = fn + meta = false + } else { + callable, meta = L.metaCall(lv) + } + // this section is inlined by go-inline + // source function is 'func (ls *LState) pushCallFrame(cf callFrame, fn LValue, meta bool) ' in '_state.go' + { + ls := L + cf := callFrame{Fn: callable, Pc: 0, Base: RA, LocalBase: RA + 1, ReturnBase: RA, NArgs: nargs, NRet: nret, Parent: cf, TailCall: 0} + fn := lv + if meta { + cf.NArgs++ + ls.reg.Insert(fn, cf.LocalBase) + } + if cf.Fn == nil { + ls.RaiseError("attempt to call a non-function object") + } + if ls.stack.IsFull() { + ls.RaiseError("stack overflow") + } + ls.stack.Push(cf) + newcf := ls.stack.Last() + // this section is inlined by go-inline + // source function is 'func (ls *LState) initCallFrame(cf *callFrame) ' in '_state.go' + { + cf := newcf + if cf.Fn.IsG { + ls.reg.SetTop(cf.LocalBase + cf.NArgs) + } else { + proto := cf.Fn.Proto + nargs := cf.NArgs + np := int(proto.NumParameters) + newSize := cf.LocalBase + np + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + rg := ls.reg + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + for i := nargs; i < np; i++ { + ls.reg.array[cf.LocalBase+i] = LNil + nargs = np + } + + if (proto.IsVarArg & VarArgIsVarArg) == 0 { + if nargs < int(proto.NumUsedRegisters) { + nargs = int(proto.NumUsedRegisters) + } + newSize = cf.LocalBase + nargs + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + rg := ls.reg + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + for i := np; i < nargs; i++ { + ls.reg.array[cf.LocalBase+i] = LNil + } + ls.reg.top = cf.LocalBase + int(proto.NumUsedRegisters) + } else { + /* swap vararg positions: + closure + namedparam1 <- lbase + namedparam2 + vararg1 + vararg2 + + TO + + closure + nil + nil + vararg1 + vararg2 + namedparam1 <- lbase + namedparam2 + */ + nvarargs := nargs - np + if nvarargs < 0 { + nvarargs = 0 + } + + ls.reg.SetTop(cf.LocalBase + nargs + np) + for i := 0; i < np; i++ { + //ls.reg.Set(cf.LocalBase+nargs+i, ls.reg.Get(cf.LocalBase+i)) + ls.reg.array[cf.LocalBase+nargs+i] = ls.reg.array[cf.LocalBase+i] + //ls.reg.Set(cf.LocalBase+i, LNil) + ls.reg.array[cf.LocalBase+i] = LNil + } + + if CompatVarArg { + ls.reg.SetTop(cf.LocalBase + nargs + np + 1) + if (proto.IsVarArg & VarArgNeedsArg) != 0 { + argtb := newLTable(nvarargs, 0) + for i := 0; i < nvarargs; i++ { + argtb.RawSetInt(i+1, ls.reg.Get(cf.LocalBase+np+i)) + } + argtb.RawSetString("n", LNumber(nvarargs)) + //ls.reg.Set(cf.LocalBase+nargs+np, argtb) + ls.reg.array[cf.LocalBase+nargs+np] = argtb + } else { + ls.reg.array[cf.LocalBase+nargs+np] = LNil + } + } + cf.LocalBase += nargs + maxreg := cf.LocalBase + int(proto.NumUsedRegisters) + ls.reg.SetTop(maxreg) + } + } + } + ls.currentFrame = newcf + } + if callable.IsG && callGFunction(L, false) { + return 1 + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_TAILCALL + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + nargs := B - 1 + if B == 0 { + nargs = reg.Top() - (RA + 1) + } + lv := reg.Get(RA) + var callable *LFunction + var meta bool + if fn, ok := lv.assertFunction(); ok { + callable = fn + meta = false + } else { + callable, meta = L.metaCall(lv) + } + if callable == nil { + L.RaiseError("attempt to call a non-function object") + } + // this section is inlined by go-inline + // source function is 'func (ls *LState) closeUpvalues(idx int) ' in '_state.go' + { + ls := L + idx := lbase + if ls.uvcache != nil { + var prev *Upvalue + for uv := ls.uvcache; uv != nil; uv = uv.next { + if uv.index >= idx { + if prev != nil { + prev.next = nil + } else { + ls.uvcache = nil + } + uv.Close() + } + prev = uv + } + } + } + if callable.IsG { + luaframe := cf + L.pushCallFrame(callFrame{ + Fn: callable, + Pc: 0, + Base: RA, + LocalBase: RA + 1, + ReturnBase: cf.ReturnBase, + NArgs: nargs, + NRet: cf.NRet, + Parent: cf, + TailCall: 0, + }, lv, meta) + if callGFunction(L, true) { + return 1 + } + if L.currentFrame == nil || L.currentFrame.Fn.IsG || luaframe == baseframe { + return 1 + } + } else { + base := cf.Base + cf.Fn = callable + cf.Pc = 0 + cf.Base = RA + cf.LocalBase = RA + 1 + cf.ReturnBase = cf.ReturnBase + cf.NArgs = nargs + cf.NRet = cf.NRet + cf.TailCall++ + lbase := cf.LocalBase + if meta { + cf.NArgs++ + L.reg.Insert(lv, cf.LocalBase) + } + // this section is inlined by go-inline + // source function is 'func (ls *LState) initCallFrame(cf *callFrame) ' in '_state.go' + { + ls := L + if cf.Fn.IsG { + ls.reg.SetTop(cf.LocalBase + cf.NArgs) + } else { + proto := cf.Fn.Proto + nargs := cf.NArgs + np := int(proto.NumParameters) + newSize := cf.LocalBase + np + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + rg := ls.reg + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + for i := nargs; i < np; i++ { + ls.reg.array[cf.LocalBase+i] = LNil + nargs = np + } + + if (proto.IsVarArg & VarArgIsVarArg) == 0 { + if nargs < int(proto.NumUsedRegisters) { + nargs = int(proto.NumUsedRegisters) + } + newSize = cf.LocalBase + nargs + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + rg := ls.reg + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + for i := np; i < nargs; i++ { + ls.reg.array[cf.LocalBase+i] = LNil + } + ls.reg.top = cf.LocalBase + int(proto.NumUsedRegisters) + } else { + /* swap vararg positions: + closure + namedparam1 <- lbase + namedparam2 + vararg1 + vararg2 + + TO + + closure + nil + nil + vararg1 + vararg2 + namedparam1 <- lbase + namedparam2 + */ + nvarargs := nargs - np + if nvarargs < 0 { + nvarargs = 0 + } + + ls.reg.SetTop(cf.LocalBase + nargs + np) + for i := 0; i < np; i++ { + //ls.reg.Set(cf.LocalBase+nargs+i, ls.reg.Get(cf.LocalBase+i)) + ls.reg.array[cf.LocalBase+nargs+i] = ls.reg.array[cf.LocalBase+i] + //ls.reg.Set(cf.LocalBase+i, LNil) + ls.reg.array[cf.LocalBase+i] = LNil + } + + if CompatVarArg { + ls.reg.SetTop(cf.LocalBase + nargs + np + 1) + if (proto.IsVarArg & VarArgNeedsArg) != 0 { + argtb := newLTable(nvarargs, 0) + for i := 0; i < nvarargs; i++ { + argtb.RawSetInt(i+1, ls.reg.Get(cf.LocalBase+np+i)) + } + argtb.RawSetString("n", LNumber(nvarargs)) + //ls.reg.Set(cf.LocalBase+nargs+np, argtb) + ls.reg.array[cf.LocalBase+nargs+np] = argtb + } else { + ls.reg.array[cf.LocalBase+nargs+np] = LNil + } + } + cf.LocalBase += nargs + maxreg := cf.LocalBase + int(proto.NumUsedRegisters) + ls.reg.SetTop(maxreg) + } + } + } + // this section is inlined by go-inline + // source function is 'func (rg *registry) CopyRange(regv, start, limit, n int) ' in '_state.go' + { + rg := L.reg + regv := base + start := RA + limit := -1 + n := reg.Top() - RA - 1 + newSize := regv + n + // 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) + } + } + if limit == -1 || limit > rg.top { + limit = rg.top + } + for i := 0; i < n; i++ { + srcIdx := start + i + if srcIdx >= limit || srcIdx < 0 { + rg.array[regv+i] = LNil + } else { + rg.array[regv+i] = rg.array[srcIdx] + } + } + + // 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 + oldtop := rg.top + rg.top = regv + n + if rg.top < oldtop { + nilRange := rg.array[rg.top:oldtop] + for i := range nilRange { + nilRange[i] = nil + } + } + } + cf.Base = base + cf.LocalBase = base + (cf.LocalBase - lbase + 1) + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_RETURN + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + // this section is inlined by go-inline + // source function is 'func (ls *LState) closeUpvalues(idx int) ' in '_state.go' + { + ls := L + idx := lbase + if ls.uvcache != nil { + var prev *Upvalue + for uv := ls.uvcache; uv != nil; uv = uv.next { + if uv.index >= idx { + if prev != nil { + prev.next = nil + } else { + ls.uvcache = nil + } + uv.Close() + } + prev = uv + } + } + } + nret := B - 1 + if B == 0 { + nret = reg.Top() - RA + } + n := cf.NRet + if cf.NRet == MultRet { + n = nret + } + + if L.Parent != nil && L.stack.Sp() == 1 { + // this section is inlined by go-inline + // source function is 'func copyReturnValues(L *LState, regv, start, n, b int) ' in '_vm.go' + { + regv := reg.Top() + start := RA + b := B + if b == 1 { + // this section is inlined by go-inline + // source function is 'func (rg *registry) FillNil(regm, n int) ' in '_state.go' + { + rg := L.reg + regm := regv + newSize := regm + n + // 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) + } + } + for i := 0; i < n; i++ { + rg.array[regm+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 + oldtop := rg.top + rg.top = regm + n + if rg.top < oldtop { + nilRange := rg.array[rg.top:oldtop] + for i := range nilRange { + nilRange[i] = nil + } + } + } + } else { + // this section is inlined by go-inline + // source function is 'func (rg *registry) CopyRange(regv, start, limit, n int) ' in '_state.go' + { + rg := L.reg + limit := -1 + newSize := regv + n + // 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) + } + } + if limit == -1 || limit > rg.top { + limit = rg.top + } + for i := 0; i < n; i++ { + srcIdx := start + i + if srcIdx >= limit || srcIdx < 0 { + rg.array[regv+i] = LNil + } else { + rg.array[regv+i] = rg.array[srcIdx] + } + } + + // 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 + oldtop := rg.top + rg.top = regv + n + if rg.top < oldtop { + nilRange := rg.array[rg.top:oldtop] + for i := range nilRange { + nilRange[i] = nil + } + } + } + if b > 1 && n > (b-1) { + // this section is inlined by go-inline + // source function is 'func (rg *registry) FillNil(regm, n int) ' in '_state.go' + { + rg := L.reg + regm := regv + b - 1 + n := n - (b - 1) + newSize := regm + n + // 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) + } + } + for i := 0; i < n; i++ { + rg.array[regm+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 + oldtop := rg.top + rg.top = regm + n + if rg.top < oldtop { + nilRange := rg.array[rg.top:oldtop] + for i := range nilRange { + nilRange[i] = nil + } + } + } + } + } + } + switchToParentThread(L, n, false, true) + return 1 + } + islast := baseframe == L.stack.Pop() || L.stack.IsEmpty() + // this section is inlined by go-inline + // source function is 'func copyReturnValues(L *LState, regv, start, n, b int) ' in '_vm.go' + { + regv := cf.ReturnBase + start := RA + b := B + if b == 1 { + // this section is inlined by go-inline + // source function is 'func (rg *registry) FillNil(regm, n int) ' in '_state.go' + { + rg := L.reg + regm := regv + newSize := regm + n + // 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) + } + } + for i := 0; i < n; i++ { + rg.array[regm+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 + oldtop := rg.top + rg.top = regm + n + if rg.top < oldtop { + nilRange := rg.array[rg.top:oldtop] + for i := range nilRange { + nilRange[i] = nil + } + } + } + } else { + // this section is inlined by go-inline + // source function is 'func (rg *registry) CopyRange(regv, start, limit, n int) ' in '_state.go' + { + rg := L.reg + limit := -1 + newSize := regv + n + // 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) + } + } + if limit == -1 || limit > rg.top { + limit = rg.top + } + for i := 0; i < n; i++ { + srcIdx := start + i + if srcIdx >= limit || srcIdx < 0 { + rg.array[regv+i] = LNil + } else { + rg.array[regv+i] = rg.array[srcIdx] + } + } + + // 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 + oldtop := rg.top + rg.top = regv + n + if rg.top < oldtop { + nilRange := rg.array[rg.top:oldtop] + for i := range nilRange { + nilRange[i] = nil + } + } + } + if b > 1 && n > (b-1) { + // this section is inlined by go-inline + // source function is 'func (rg *registry) FillNil(regm, n int) ' in '_state.go' + { + rg := L.reg + regm := regv + b - 1 + n := n - (b - 1) + newSize := regm + n + // 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) + } + } + for i := 0; i < n; i++ { + rg.array[regm+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 + oldtop := rg.top + rg.top = regm + n + if rg.top < oldtop { + nilRange := rg.array[rg.top:oldtop] + for i := range nilRange { + nilRange[i] = nil + } + } + } + } + } + } + L.currentFrame = L.stack.Last() + if islast || L.currentFrame == nil || L.currentFrame.Fn.IsG { + return 1 + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_FORLOOP + reg := L.reg + cf := L.currentFrame + 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 { + init += step + reg.SetNumber(RA, LNumber(init)) + if (step > 0 && init <= limit) || (step <= 0 && init >= limit) { + Sbx := int(inst&0x3ffff) - opMaxArgSbx //GETSBX + cf.Pc += Sbx + reg.SetNumber(RA+3, LNumber(init)) + } else { + reg.SetTop(RA + 1) + } + } else { + L.RaiseError("for statement step must be a number") + } + } else { + L.RaiseError("for statement limit must be a number") + } + } else { + L.RaiseError("for statement init must be a number") + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_FORPREP + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + 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)) + } else { + L.RaiseError("for statement step must be a number") + } + } else { + L.RaiseError("for statement init must be a number") + } + cf.Pc += Sbx + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_TFORLOOP + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + 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)) + L.callR(2, nret, RA+3) + if value := reg.Get(RA + 3); value != LNil { + reg.Set(RA+2, value) + pc := cf.Fn.Proto.Code[cf.Pc] + cf.Pc += int(pc&0x3ffff) - opMaxArgSbx + } + cf.Pc++ + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_SETLIST + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + if C == 0 { + C = int(cf.Fn.Proto.Code[cf.Pc]) + cf.Pc++ + } + offset := (C - 1) * FieldsPerFlush + table := reg.Get(RA).(*LTable) + nelem := B + if B == 0 { + nelem = reg.Top() - RA - 1 + } + for i := 1; i <= nelem; i++ { + table.RawSetInt(offset+i, reg.Get(RA+i)) + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_CLOSE + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + // this section is inlined by go-inline + // source function is 'func (ls *LState) closeUpvalues(idx int) ' in '_state.go' + { + ls := L + idx := RA + if ls.uvcache != nil { + var prev *Upvalue + for uv := ls.uvcache; uv != nil; uv = uv.next { + if uv.index >= idx { + if prev != nil { + prev.next = nil + } else { + ls.uvcache = nil + } + uv.Close() + } + prev = uv + } + } + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_CLOSURE + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + Bx := int(inst & 0x3ffff) //GETBX + proto := cf.Fn.Proto.FunctionPrototypes[Bx] + closure := newLFunctionL(proto, cf.Fn.Env, int(proto.NumUpvalues)) + reg.Set(RA, closure) + for i := 0; i < int(proto.NumUpvalues); i++ { + inst = cf.Fn.Proto.Code[cf.Pc] + cf.Pc++ + B := opGetArgB(inst) + switch opGetOpCode(inst) { + case OP_MOVE: + closure.Upvalues[i] = L.findUpvalue(lbase + B) + case OP_GETUPVAL: + closure.Upvalues[i] = cf.Fn.Upvalues[B] + } + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_VARARG + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + nparams := int(cf.Fn.Proto.NumParameters) + nvarargs := cf.NArgs - nparams + if nvarargs < 0 { + nvarargs = 0 + } + nwant := B - 1 + if B == 0 { + nwant = nvarargs + } + // this section is inlined by go-inline + // source function is 'func (rg *registry) CopyRange(regv, start, limit, n int) ' in '_state.go' + { + rg := reg + regv := RA + start := cf.Base + nparams + 1 + limit := cf.LocalBase + n := nwant + newSize := regv + n + // 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) + } + } + if limit == -1 || limit > rg.top { + limit = rg.top + } + for i := 0; i < n; i++ { + srcIdx := start + i + if srcIdx >= limit || srcIdx < 0 { + rg.array[regv+i] = LNil + } else { + rg.array[regv+i] = rg.array[srcIdx] + } + } + + // 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 + oldtop := rg.top + rg.top = regv + n + if rg.top < oldtop { + nilRange := rg.array[rg.top:oldtop] + for i := range nilRange { + nilRange[i] = nil + } + } + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_NOP + return 0 + }, + } +} + +func opArith(L *LState, inst uint32, baseframe *callFrame) int { //OP_ADD, OP_SUB, OP_MUL, OP_DIV, OP_MOD, OP_POW + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + opcode := int(inst >> 26) //GETOPCODE + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + lhs := L.rkValue(B) + rhs := L.rkValue(C) + v1, ok1 := lhs.assertFloat64() + v2, ok2 := rhs.assertFloat64() + if ok1 && ok2 { + reg.SetNumber(RA, numberArith(L, opcode, LNumber(v1), LNumber(v2))) + } else { + reg.Set(RA, objectArith(L, opcode, lhs, rhs)) + } + return 0 +} + +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) { + v += frhs + } + return LNumber(v) +} + +func numberArith(L *LState, opcode int, lhs, rhs LNumber) LNumber { + switch opcode { + case OP_ADD: + return lhs + rhs + case OP_SUB: + return lhs - rhs + case OP_MUL: + return lhs * rhs + case OP_DIV: + return lhs / rhs + case OP_MOD: + return luaModulo(lhs, rhs) + case OP_POW: + flhs := float64(lhs) + frhs := float64(rhs) + return LNumber(math.Pow(flhs, frhs)) + } + panic("should not reach here") + return LNumber(0) +} + +func objectArith(L *LState, opcode int, lhs, rhs LValue) LValue { + event := "" + switch opcode { + case OP_ADD: + event = "__add" + case OP_SUB: + event = "__sub" + case OP_MUL: + event = "__mul" + case OP_DIV: + event = "__div" + case OP_MOD: + event = "__mod" + case OP_POW: + event = "__pow" + } + op := L.metaOp2(lhs, rhs, event) + if op.Type() == LTFunction { + L.reg.Push(op) + L.reg.Push(lhs) + L.reg.Push(rhs) + L.Call(2, 1) + return L.reg.Pop() + } + if str, ok := lhs.(LString); ok { + if lnum, err := parseNumber(string(str)); err == nil { + lhs = lnum + } + } + if str, ok := rhs.(LString); ok { + if rnum, err := parseNumber(string(str)); err == nil { + rhs = rnum + } + } + if v1, ok1 := lhs.assertFloat64(); ok1 { + if v2, ok2 := rhs.assertFloat64(); ok2 { + return numberArith(L, opcode, LNumber(v1), LNumber(v2)) + } + } + L.RaiseError(fmt.Sprintf("cannot perform %v operation between %v and %v", + strings.TrimLeft(event, "_"), lhs.Type().String(), rhs.Type().String())) + + return LNil +} + +func stringConcat(L *LState, total, last int) LValue { + rhs := L.reg.Get(last) + total-- + for i := last - 1; total > 0; { + lhs := L.reg.Get(i) + if !(LVCanConvToString(lhs) && LVCanConvToString(rhs)) { + op := L.metaOp2(lhs, rhs, "__concat") + if op.Type() == LTFunction { + L.reg.Push(op) + L.reg.Push(lhs) + L.reg.Push(rhs) + L.Call(2, 1) + rhs = L.reg.Pop() + total-- + i-- + } else { + L.RaiseError("cannot perform concat operation between %v and %v", lhs.Type().String(), rhs.Type().String()) + return LNil + } + } else { + buf := make([]string, total+1) + buf[total] = LVAsString(rhs) + for total > 0 { + lhs = L.reg.Get(i) + if !LVCanConvToString(lhs) { + break + } + buf[total-1] = LVAsString(lhs) + i-- + total-- + } + rhs = LString(strings.Join(buf, "")) + } + } + return rhs +} + +func lessThan(L *LState, lhs, rhs LValue) bool { + // optimization for numbers + if v1, ok1 := lhs.assertFloat64(); ok1 { + if v2, ok2 := rhs.assertFloat64(); ok2 { + return v1 < v2 + } + L.RaiseError("attempt to compare %v with %v", lhs.Type().String(), rhs.Type().String()) + } + if lhs.Type() != rhs.Type() { + L.RaiseError("attempt to compare %v with %v", lhs.Type().String(), rhs.Type().String()) + return false + } + ret := false + switch lhs.Type() { + case LTString: + ret = strCmp(string(lhs.(LString)), string(rhs.(LString))) < 0 + default: + ret = objectRationalWithError(L, lhs, rhs, "__lt") + } + return ret +} + +func equals(L *LState, lhs, rhs LValue, raw bool) bool { + if lhs.Type() != rhs.Type() { + return false + } + + ret := false + switch lhs.Type() { + case LTNil: + ret = true + case LTNumber: + v1, _ := lhs.assertFloat64() + v2, _ := rhs.assertFloat64() + ret = v1 == v2 + case LTBool: + ret = bool(lhs.(LBool)) == bool(rhs.(LBool)) + case LTString: + ret = string(lhs.(LString)) == string(rhs.(LString)) + case LTUserData, LTTable: + if lhs == rhs { + ret = true + } else if !raw { + switch objectRational(L, lhs, rhs, "__eq") { + case 1: + ret = true + default: + ret = false + } + } + default: + ret = lhs == rhs + } + return ret +} + +func objectRationalWithError(L *LState, lhs, rhs LValue, event string) bool { + switch objectRational(L, lhs, rhs, event) { + case 1: + return true + case 0: + return false + } + L.RaiseError("attempt to compare %v with %v", lhs.Type().String(), rhs.Type().String()) + return false +} + +func objectRational(L *LState, lhs, rhs LValue, event string) int { + m1 := L.metaOp1(lhs, event) + m2 := L.metaOp1(rhs, event) + if m1.Type() == LTFunction && m1 == m2 { + L.reg.Push(m1) + L.reg.Push(lhs) + L.reg.Push(rhs) + L.Call(2, 1) + if LVAsBool(L.reg.Pop()) { + return 1 + } + return 0 + } + return -1 +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 72a23042266..4e297dfd98e 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -71,6 +71,12 @@ github.com/alecthomas/template github.com/alecthomas/template/parse # github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d github.com/alecthomas/units +# github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a +github.com/alicebob/gopher-json +# github.com/alicebob/miniredis v2.5.0+incompatible +## explicit +github.com/alicebob/miniredis +github.com/alicebob/miniredis/server # github.com/apache/thrift v0.13.0 github.com/apache/thrift/lib/go/thrift # github.com/armon/go-metrics v0.3.3 @@ -497,6 +503,9 @@ github.com/golangci/prealloc github.com/golangci/revgrep # github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 github.com/golangci/unconvert +# github.com/gomodule/redigo v2.0.0+incompatible +github.com/gomodule/redigo/internal +github.com/gomodule/redigo/redis # github.com/google/addlicense v0.0.0-20200622132530-df58acafd6d5 github.com/google/addlicense # github.com/google/btree v1.0.0 @@ -1148,6 +1157,11 @@ github.com/willf/bitset github.com/willf/bloom # github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 github.com/xiang90/probing +# github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da +github.com/yuin/gopher-lua +github.com/yuin/gopher-lua/ast +github.com/yuin/gopher-lua/parse +github.com/yuin/gopher-lua/pm # go.etcd.io/bbolt v1.3.5-0.20200615073812-232d8fc87f50 go.etcd.io/bbolt # go.etcd.io/etcd v0.5.0-alpha.5.0.20200520232829-54ba9589114f => go.etcd.io/etcd v0.5.0-alpha.5.0.20200520232829-54ba9589114f From ab372e170b23e076dc7af8e7c90a2b5ee7ab2b88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gonz=C3=A1lez=20Lopes?= Date: Thu, 19 Nov 2020 12:15:58 +0100 Subject: [PATCH 06/16] Add Redis caching MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add Redis client - Add Redis support - Add Redis file Signed-off-by: Daniel González Lopes --- tempodb/backend/redis/cache.go | 144 +++++++++++++++++++++++++++++++++ tempodb/config.go | 2 + tempodb/tempodb.go | 9 +++ 3 files changed, 155 insertions(+) create mode 100644 tempodb/backend/redis/cache.go diff --git a/tempodb/backend/redis/cache.go b/tempodb/backend/redis/cache.go new file mode 100644 index 00000000000..17969b2c618 --- /dev/null +++ b/tempodb/backend/redis/cache.go @@ -0,0 +1,144 @@ +package redis + +import ( + "context" + "strconv" + "time" + + "github.com/cortexproject/cortex/pkg/chunk/cache" + "github.com/go-kit/kit/log" + "github.com/grafana/tempo/tempodb/backend" + "github.com/grafana/tempo/tempodb/encoding" + + "github.com/google/uuid" +) + +const ( + typeBloom = "bloom" + typeIndex = "index" +) + +type Config struct { + ClientConfig cache.RedisConfig `yaml:",inline"` + + TTL time.Duration `yaml:"ttl"` +} + +type readerWriter struct { + nextReader backend.Reader + nextWriter backend.Writer + client *cache.RedisCache + logger log.Logger +} + +func New(nextReader backend.Reader, nextWriter backend.Writer, cfg *Config, logger log.Logger) (backend.Reader, backend.Writer, error) { + if cfg.ClientConfig.Timeout == 0 { + cfg.ClientConfig.Timeout = 100 * time.Millisecond + } + + client := cache.NewRedisClient(&cfg.ClientConfig) + + rw := &readerWriter{ + client: cache.NewRedisCache("tempo", client, logger), + nextReader: nextReader, + nextWriter: nextWriter, + logger: logger, + } + + return rw, rw, nil +} + +// Reader +func (r *readerWriter) Tenants(ctx context.Context) ([]string, error) { + return r.nextReader.Tenants(ctx) +} + +func (r *readerWriter) Blocks(ctx context.Context, tenantID string) ([]uuid.UUID, error) { + return r.nextReader.Blocks(ctx, tenantID) +} + +func (r *readerWriter) BlockMeta(ctx context.Context, blockID uuid.UUID, tenantID string) (*encoding.BlockMeta, error) { + return r.nextReader.BlockMeta(ctx, blockID, tenantID) +} + +func (r *readerWriter) Bloom(ctx context.Context, blockID uuid.UUID, tenantID string, shardNum int) ([]byte, error) { + key := bloomKey(blockID, tenantID, typeBloom, shardNum) + val := r.get(ctx, key) + if val != nil { + return val, nil + } + + val, err := r.nextReader.Bloom(ctx, blockID, tenantID, shardNum) + if err == nil { + r.set(ctx, key, val) + } + + return val, err +} + +func (r *readerWriter) Index(ctx context.Context, blockID uuid.UUID, tenantID string) ([]byte, error) { + key := key(blockID, tenantID, typeIndex) + val := r.get(ctx, key) + if val != nil { + return val, nil + } + + val, err := r.nextReader.Index(ctx, blockID, tenantID) + if err == nil { + r.set(ctx, key, val) + } + + return val, err +} + +func (r *readerWriter) Object(ctx context.Context, blockID uuid.UUID, tenantID string, start uint64, buffer []byte) error { + return r.nextReader.Object(ctx, blockID, tenantID, start, buffer) +} + +func (r *readerWriter) Shutdown() { + r.nextReader.Shutdown() + r.client.Stop() +} + +// Writer +func (r *readerWriter) Write(ctx context.Context, meta *encoding.BlockMeta, bBloom [][]byte, bIndex []byte, objectFilePath string) error { + for i, b := range bBloom { + r.set(ctx, bloomKey(meta.BlockID, meta.TenantID, typeBloom, i), b) + } + r.set(ctx, key(meta.BlockID, meta.TenantID, typeIndex), bIndex) + + return r.nextWriter.Write(ctx, meta, bBloom, bIndex, objectFilePath) +} + +func (r *readerWriter) WriteBlockMeta(ctx context.Context, tracker backend.AppendTracker, meta *encoding.BlockMeta, bBloom [][]byte, bIndex []byte) error { + for i, b := range bBloom { + r.set(ctx, bloomKey(meta.BlockID, meta.TenantID, typeBloom, i), b) + } + r.set(ctx, key(meta.BlockID, meta.TenantID, typeIndex), bIndex) + + return r.nextWriter.WriteBlockMeta(ctx, tracker, meta, bBloom, bIndex) +} + +func (r *readerWriter) AppendObject(ctx context.Context, tracker backend.AppendTracker, meta *encoding.BlockMeta, bObject []byte) (backend.AppendTracker, error) { + return r.nextWriter.AppendObject(ctx, tracker, meta, bObject) +} + +func (r *readerWriter) get(ctx context.Context, key string) []byte { + found, vals, _ := r.client.Fetch(ctx, []string{key}) + if len(found) > 0 { + return vals[0] + } + return nil +} + +func (r *readerWriter) set(ctx context.Context, key string, val []byte) { + r.client.Store(ctx, []string{key}, [][]byte{val}) +} + +func key(blockID uuid.UUID, tenantID string, t string) string { + return blockID.String() + ":" + tenantID + ":" + t +} + +func bloomKey(blockID uuid.UUID, tenantID string, t string, shardNum int) string { + return blockID.String() + ":" + tenantID + ":" + t + strconv.Itoa(shardNum) +} diff --git a/tempodb/config.go b/tempodb/config.go index d561de45016..1928c617e98 100644 --- a/tempodb/config.go +++ b/tempodb/config.go @@ -7,6 +7,7 @@ import ( "github.com/grafana/tempo/tempodb/backend/gcs" "github.com/grafana/tempo/tempodb/backend/local" "github.com/grafana/tempo/tempodb/backend/memcached" + "github.com/grafana/tempo/tempodb/backend/redis" "github.com/grafana/tempo/tempodb/backend/s3" "github.com/grafana/tempo/tempodb/pool" "github.com/grafana/tempo/tempodb/wal" @@ -22,6 +23,7 @@ type Config struct { Diskcache *diskcache.Config `yaml:"disk_cache"` Memcached *memcached.Config `yaml:"memcached"` + Redis *redis.Config `yaml:"redis"` BlocklistPoll time.Duration `yaml:"blocklist_poll"` } diff --git a/tempodb/tempodb.go b/tempodb/tempodb.go index fbd1800c4b4..63a5699fa17 100644 --- a/tempodb/tempodb.go +++ b/tempodb/tempodb.go @@ -26,6 +26,7 @@ import ( "github.com/grafana/tempo/tempodb/backend/gcs" "github.com/grafana/tempo/tempodb/backend/local" "github.com/grafana/tempo/tempodb/backend/memcached" + "github.com/grafana/tempo/tempodb/backend/redis" "github.com/grafana/tempo/tempodb/backend/s3" "github.com/grafana/tempo/tempodb/encoding" "github.com/grafana/tempo/tempodb/encoding/bloom" @@ -156,6 +157,14 @@ func New(cfg *Config, logger log.Logger) (Reader, Writer, Compactor, error) { } } + if cfg.Redis != nil { + r, w, err = redis.New(r, w, cfg.Redis, logger) + + if err != nil { + return nil, nil, nil, err + } + } + rw := &readerWriter{ c: c, compactedBlockLists: make(map[string][]*encoding.CompactedBlockMeta), From 97322810f0c63a5f31715ef3c87b790626405778 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gonz=C3=A1lez=20Lopes?= Date: Thu, 19 Nov 2020 12:20:06 +0100 Subject: [PATCH 07/16] Add Redis to docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniel González Lopes --- docs/tempo/website/configuration/_index.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/tempo/website/configuration/_index.md b/docs/tempo/website/configuration/_index.md index 612aa557bbd..63364643200 100644 --- a/docs/tempo/website/configuration/_index.md +++ b/docs/tempo/website/configuration/_index.md @@ -86,6 +86,9 @@ storage: host: memcached service: memcached-client timeout: 500ms + redis: # optional redis configuration + endpoint: redis + timeout: 500ms pool: # the worker pool is used primarily when finding traces by id, but is also used by other max_workers: 50 # total number of workers pulling jobs from the queue queue_depth: 2000 # length of job queue From a144a9cfc25c7457755a44b1c28b8abb0d31bcfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gonz=C3=A1lez=20Lopes?= Date: Thu, 19 Nov 2020 13:33:10 +0100 Subject: [PATCH 08/16] Add to changelog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniel González Lopes --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6e4c072244..1bc064411b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ * [ENHANCEMENT] Add tempodb_compaction_bytes_written metric. [#360](https://github.com/grafana/tempo/pull/360) * [ENHANCEMENT] Add tempodb_compaction_blocks_total metric. [#360](https://github.com/grafana/tempo/pull/360) * [ENHANCEMENT] Add support for S3 V2 signatures. [#352](https://github.com/grafana/tempo/pull/352) +* [ENHANCEMENT] Add support for Redis caching. [#354](https://github.com/grafana/tempo/pull/354) * [BUGFIX] Frequent errors logged by compactor regarding meta not found [#327](https://github.com/grafana/tempo/pull/327) * [BUGFIX] Fix distributors panicking on rollout [#343](https://github.com/grafana/tempo/pull/343) * [BUGFIX] Fix ingesters occassionally double flushing [#364](https://github.com/grafana/tempo/pull/364) From 3e7e6512c293c9271d89af3e1b3af4585e6d7999 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gonz=C3=A1lez=20Lopes?= Date: Thu, 19 Nov 2020 13:38:47 +0100 Subject: [PATCH 09/16] Add ttl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniel González Lopes --- tempodb/backend/redis/cache.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tempodb/backend/redis/cache.go b/tempodb/backend/redis/cache.go index 17969b2c618..c99b77f9428 100644 --- a/tempodb/backend/redis/cache.go +++ b/tempodb/backend/redis/cache.go @@ -35,6 +35,9 @@ func New(nextReader backend.Reader, nextWriter backend.Writer, cfg *Config, logg if cfg.ClientConfig.Timeout == 0 { cfg.ClientConfig.Timeout = 100 * time.Millisecond } + if cfg.ClientConfig.Expiration == 0 { + cfg.ClientConfig.Expiration = cfg.TTL + } client := cache.NewRedisClient(&cfg.ClientConfig) From a3b88c1b1fad716974744857d7470d9d68e8c8c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gonz=C3=A1lez=20Lopes?= Date: Mon, 23 Nov 2020 17:47:19 +0100 Subject: [PATCH 10/16] Add Redis tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniel González Lopes --- go.mod | 1 + tempodb/backend/redis/cache_test.go | 169 ++ .../github.com/alicebob/gopher-json/LICENSE | 24 + .../github.com/alicebob/gopher-json/README.md | 7 + vendor/github.com/alicebob/gopher-json/doc.go | 33 + .../github.com/alicebob/gopher-json/json.go | 189 ++ .../github.com/alicebob/miniredis/.gitignore | 1 + .../github.com/alicebob/miniredis/.travis.yml | 13 + vendor/github.com/alicebob/miniredis/LICENSE | 21 + vendor/github.com/alicebob/miniredis/Makefile | 12 + .../github.com/alicebob/miniredis/README.md | 336 +++ vendor/github.com/alicebob/miniredis/check.go | 68 + .../alicebob/miniredis/cmd_connection.go | 96 + .../alicebob/miniredis/cmd_generic.go | 479 ++++ .../github.com/alicebob/miniredis/cmd_hash.go | 571 +++++ .../github.com/alicebob/miniredis/cmd_list.go | 687 +++++ .../alicebob/miniredis/cmd_scripting.go | 220 ++ .../alicebob/miniredis/cmd_server.go | 104 + .../github.com/alicebob/miniredis/cmd_set.go | 639 +++++ .../alicebob/miniredis/cmd_sorted_set.go | 1399 +++++++++++ .../alicebob/miniredis/cmd_string.go | 1075 ++++++++ .../alicebob/miniredis/cmd_transactions.go | 152 ++ vendor/github.com/alicebob/miniredis/db.go | 551 ++++ .../github.com/alicebob/miniredis/direct.go | 549 ++++ vendor/github.com/alicebob/miniredis/keys.go | 65 + vendor/github.com/alicebob/miniredis/lua.go | 189 ++ .../alicebob/miniredis/miniredis.go | 373 +++ vendor/github.com/alicebob/miniredis/redis.go | 208 ++ .../alicebob/miniredis/server/Makefile | 9 + .../alicebob/miniredis/server/proto.go | 84 + .../alicebob/miniredis/server/server.go | 242 ++ .../alicebob/miniredis/sorted_set.go | 97 + vendor/github.com/gomodule/redigo/LICENSE | 175 ++ .../gomodule/redigo/internal/commandinfo.go | 54 + .../github.com/gomodule/redigo/redis/conn.go | 673 +++++ .../github.com/gomodule/redigo/redis/doc.go | 177 ++ .../github.com/gomodule/redigo/redis/go16.go | 27 + .../github.com/gomodule/redigo/redis/go17.go | 29 + .../github.com/gomodule/redigo/redis/go18.go | 9 + .../github.com/gomodule/redigo/redis/log.go | 134 + .../github.com/gomodule/redigo/redis/pool.go | 562 +++++ .../gomodule/redigo/redis/pool17.go | 35 + .../gomodule/redigo/redis/pubsub.go | 148 ++ .../github.com/gomodule/redigo/redis/redis.go | 117 + .../github.com/gomodule/redigo/redis/reply.go | 479 ++++ .../github.com/gomodule/redigo/redis/scan.go | 585 +++++ .../gomodule/redigo/redis/script.go | 91 + vendor/github.com/yuin/gopher-lua/.travis.yml | 18 + vendor/github.com/yuin/gopher-lua/LICENSE | 21 + vendor/github.com/yuin/gopher-lua/Makefile | 10 + vendor/github.com/yuin/gopher-lua/README.rst | 887 +++++++ vendor/github.com/yuin/gopher-lua/_state.go | 2081 +++++++++++++++ vendor/github.com/yuin/gopher-lua/_vm.go | 1033 ++++++++ vendor/github.com/yuin/gopher-lua/alloc.go | 79 + vendor/github.com/yuin/gopher-lua/ast/ast.go | 29 + vendor/github.com/yuin/gopher-lua/ast/expr.go | 137 + vendor/github.com/yuin/gopher-lua/ast/misc.go | 17 + vendor/github.com/yuin/gopher-lua/ast/stmt.go | 95 + .../github.com/yuin/gopher-lua/ast/token.go | 22 + vendor/github.com/yuin/gopher-lua/auxlib.go | 460 ++++ vendor/github.com/yuin/gopher-lua/baselib.go | 592 +++++ .../github.com/yuin/gopher-lua/channellib.go | 184 ++ vendor/github.com/yuin/gopher-lua/compile.go | 1672 ++++++++++++ vendor/github.com/yuin/gopher-lua/config.go | 36 + .../yuin/gopher-lua/coroutinelib.go | 112 + vendor/github.com/yuin/gopher-lua/debuglib.go | 173 ++ vendor/github.com/yuin/gopher-lua/function.go | 193 ++ vendor/github.com/yuin/gopher-lua/go.mod | 10 + vendor/github.com/yuin/gopher-lua/go.sum | 8 + vendor/github.com/yuin/gopher-lua/iolib.go | 746 ++++++ vendor/github.com/yuin/gopher-lua/linit.go | 54 + vendor/github.com/yuin/gopher-lua/loadlib.go | 125 + vendor/github.com/yuin/gopher-lua/mathlib.go | 231 ++ vendor/github.com/yuin/gopher-lua/opcode.go | 371 +++ vendor/github.com/yuin/gopher-lua/oslib.go | 221 ++ vendor/github.com/yuin/gopher-lua/package.go | 7 + .../github.com/yuin/gopher-lua/parse/Makefile | 4 + .../github.com/yuin/gopher-lua/parse/lexer.go | 539 ++++ .../yuin/gopher-lua/parse/parser.go | 1137 +++++++++ .../yuin/gopher-lua/parse/parser.go.y | 524 ++++ vendor/github.com/yuin/gopher-lua/pm/pm.go | 638 +++++ vendor/github.com/yuin/gopher-lua/state.go | 2236 +++++++++++++++++ .../github.com/yuin/gopher-lua/stringlib.go | 448 ++++ vendor/github.com/yuin/gopher-lua/table.go | 387 +++ vendor/github.com/yuin/gopher-lua/tablelib.go | 100 + vendor/github.com/yuin/gopher-lua/utils.go | 265 ++ vendor/github.com/yuin/gopher-lua/value.go | 247 ++ vendor/github.com/yuin/gopher-lua/vm.go | 1718 +++++++++++++ vendor/modules.txt | 14 + 89 files changed, 29839 insertions(+) create mode 100644 tempodb/backend/redis/cache_test.go create mode 100644 vendor/github.com/alicebob/gopher-json/LICENSE create mode 100644 vendor/github.com/alicebob/gopher-json/README.md create mode 100644 vendor/github.com/alicebob/gopher-json/doc.go create mode 100644 vendor/github.com/alicebob/gopher-json/json.go create mode 100644 vendor/github.com/alicebob/miniredis/.gitignore create mode 100644 vendor/github.com/alicebob/miniredis/.travis.yml create mode 100644 vendor/github.com/alicebob/miniredis/LICENSE create mode 100644 vendor/github.com/alicebob/miniredis/Makefile create mode 100644 vendor/github.com/alicebob/miniredis/README.md create mode 100644 vendor/github.com/alicebob/miniredis/check.go create mode 100644 vendor/github.com/alicebob/miniredis/cmd_connection.go create mode 100644 vendor/github.com/alicebob/miniredis/cmd_generic.go create mode 100644 vendor/github.com/alicebob/miniredis/cmd_hash.go create mode 100644 vendor/github.com/alicebob/miniredis/cmd_list.go create mode 100644 vendor/github.com/alicebob/miniredis/cmd_scripting.go create mode 100644 vendor/github.com/alicebob/miniredis/cmd_server.go create mode 100644 vendor/github.com/alicebob/miniredis/cmd_set.go create mode 100644 vendor/github.com/alicebob/miniredis/cmd_sorted_set.go create mode 100644 vendor/github.com/alicebob/miniredis/cmd_string.go create mode 100644 vendor/github.com/alicebob/miniredis/cmd_transactions.go create mode 100644 vendor/github.com/alicebob/miniredis/db.go create mode 100644 vendor/github.com/alicebob/miniredis/direct.go create mode 100644 vendor/github.com/alicebob/miniredis/keys.go create mode 100644 vendor/github.com/alicebob/miniredis/lua.go create mode 100644 vendor/github.com/alicebob/miniredis/miniredis.go create mode 100644 vendor/github.com/alicebob/miniredis/redis.go create mode 100644 vendor/github.com/alicebob/miniredis/server/Makefile create mode 100644 vendor/github.com/alicebob/miniredis/server/proto.go create mode 100644 vendor/github.com/alicebob/miniredis/server/server.go create mode 100644 vendor/github.com/alicebob/miniredis/sorted_set.go create mode 100644 vendor/github.com/gomodule/redigo/LICENSE create mode 100644 vendor/github.com/gomodule/redigo/internal/commandinfo.go create mode 100644 vendor/github.com/gomodule/redigo/redis/conn.go create mode 100644 vendor/github.com/gomodule/redigo/redis/doc.go create mode 100644 vendor/github.com/gomodule/redigo/redis/go16.go create mode 100644 vendor/github.com/gomodule/redigo/redis/go17.go create mode 100644 vendor/github.com/gomodule/redigo/redis/go18.go create mode 100644 vendor/github.com/gomodule/redigo/redis/log.go create mode 100644 vendor/github.com/gomodule/redigo/redis/pool.go create mode 100644 vendor/github.com/gomodule/redigo/redis/pool17.go create mode 100644 vendor/github.com/gomodule/redigo/redis/pubsub.go create mode 100644 vendor/github.com/gomodule/redigo/redis/redis.go create mode 100644 vendor/github.com/gomodule/redigo/redis/reply.go create mode 100644 vendor/github.com/gomodule/redigo/redis/scan.go create mode 100644 vendor/github.com/gomodule/redigo/redis/script.go create mode 100644 vendor/github.com/yuin/gopher-lua/.travis.yml create mode 100644 vendor/github.com/yuin/gopher-lua/LICENSE create mode 100644 vendor/github.com/yuin/gopher-lua/Makefile create mode 100644 vendor/github.com/yuin/gopher-lua/README.rst create mode 100644 vendor/github.com/yuin/gopher-lua/_state.go create mode 100644 vendor/github.com/yuin/gopher-lua/_vm.go create mode 100644 vendor/github.com/yuin/gopher-lua/alloc.go create mode 100644 vendor/github.com/yuin/gopher-lua/ast/ast.go create mode 100644 vendor/github.com/yuin/gopher-lua/ast/expr.go create mode 100644 vendor/github.com/yuin/gopher-lua/ast/misc.go create mode 100644 vendor/github.com/yuin/gopher-lua/ast/stmt.go create mode 100644 vendor/github.com/yuin/gopher-lua/ast/token.go create mode 100644 vendor/github.com/yuin/gopher-lua/auxlib.go create mode 100644 vendor/github.com/yuin/gopher-lua/baselib.go create mode 100644 vendor/github.com/yuin/gopher-lua/channellib.go create mode 100644 vendor/github.com/yuin/gopher-lua/compile.go create mode 100644 vendor/github.com/yuin/gopher-lua/config.go create mode 100644 vendor/github.com/yuin/gopher-lua/coroutinelib.go create mode 100644 vendor/github.com/yuin/gopher-lua/debuglib.go create mode 100644 vendor/github.com/yuin/gopher-lua/function.go create mode 100644 vendor/github.com/yuin/gopher-lua/go.mod create mode 100644 vendor/github.com/yuin/gopher-lua/go.sum create mode 100644 vendor/github.com/yuin/gopher-lua/iolib.go create mode 100644 vendor/github.com/yuin/gopher-lua/linit.go create mode 100644 vendor/github.com/yuin/gopher-lua/loadlib.go create mode 100644 vendor/github.com/yuin/gopher-lua/mathlib.go create mode 100644 vendor/github.com/yuin/gopher-lua/opcode.go create mode 100644 vendor/github.com/yuin/gopher-lua/oslib.go create mode 100644 vendor/github.com/yuin/gopher-lua/package.go create mode 100644 vendor/github.com/yuin/gopher-lua/parse/Makefile create mode 100644 vendor/github.com/yuin/gopher-lua/parse/lexer.go create mode 100644 vendor/github.com/yuin/gopher-lua/parse/parser.go create mode 100644 vendor/github.com/yuin/gopher-lua/parse/parser.go.y create mode 100644 vendor/github.com/yuin/gopher-lua/pm/pm.go create mode 100644 vendor/github.com/yuin/gopher-lua/state.go create mode 100644 vendor/github.com/yuin/gopher-lua/stringlib.go create mode 100644 vendor/github.com/yuin/gopher-lua/table.go create mode 100644 vendor/github.com/yuin/gopher-lua/tablelib.go create mode 100644 vendor/github.com/yuin/gopher-lua/utils.go create mode 100644 vendor/github.com/yuin/gopher-lua/value.go create mode 100644 vendor/github.com/yuin/gopher-lua/vm.go diff --git a/go.mod b/go.mod index c0e92cd9c95..dd92fcf5e33 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.15 require ( cloud.google.com/go/storage v1.6.0 contrib.go.opencensus.io/exporter/prometheus v0.2.0 + github.com/alicebob/miniredis v2.5.0+incompatible github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b github.com/cortexproject/cortex v1.4.0 github.com/go-kit/kit v0.10.0 diff --git a/tempodb/backend/redis/cache_test.go b/tempodb/backend/redis/cache_test.go new file mode 100644 index 00000000000..9e486e6e698 --- /dev/null +++ b/tempodb/backend/redis/cache_test.go @@ -0,0 +1,169 @@ +package redis + +import ( + "context" + "testing" + + "github.com/alicebob/miniredis" + "github.com/cortexproject/cortex/pkg/chunk/cache" + "github.com/go-kit/kit/log" + "github.com/google/uuid" + "github.com/grafana/tempo/tempodb/backend" + "github.com/grafana/tempo/tempodb/encoding" + "github.com/stretchr/testify/assert" +) + +type mockReader struct { + tenants []string + blocks []uuid.UUID + meta *encoding.BlockMeta + bloom []byte + index []byte + object []byte +} + +func (m *mockReader) Tenants(ctx context.Context) ([]string, error) { + return m.tenants, nil +} +func (m *mockReader) Blocks(ctx context.Context, tenantID string) ([]uuid.UUID, error) { + return m.blocks, nil +} +func (m *mockReader) BlockMeta(ctx context.Context, blockID uuid.UUID, tenantID string) (*encoding.BlockMeta, error) { + return m.meta, nil +} +func (m *mockReader) Bloom(ctx context.Context, blockID uuid.UUID, tenantID string, shardNum int) ([]byte, error) { + return m.bloom, nil +} +func (m *mockReader) Index(ctx context.Context, blockID uuid.UUID, tenantID string) ([]byte, error) { + return m.index, nil +} +func (m *mockReader) Object(ctx context.Context, blockID uuid.UUID, tenantID string, offset uint64, buffer []byte) error { + copy(buffer, m.object) + return nil +} +func (m *mockReader) Shutdown() {} + +type mockWriter struct { +} + +func (m *mockWriter) Write(ctx context.Context, meta *encoding.BlockMeta, bBloom [][]byte, bIndex []byte, objectFilePath string) error { + return nil +} +func (m *mockWriter) WriteBlockMeta(ctx context.Context, tracker backend.AppendTracker, meta *encoding.BlockMeta, bBloom [][]byte, bIndex []byte) error { + return nil +} +func (m *mockWriter) AppendObject(ctx context.Context, tracker backend.AppendTracker, meta *encoding.BlockMeta, bObject []byte) (backend.AppendTracker, error) { + return nil, nil +} + +func TestCache(t *testing.T) { + tenantID := "test" + blockID := uuid.New() + shardNum := 0 + + tests := []struct { + name string + readerTenants []string + readerBlocks []uuid.UUID + readerMeta *encoding.BlockMeta + readerBloom []byte + readerIndex []byte + readerObject []byte + expectedTenants []string + expectedBlocks []uuid.UUID + expectedMeta *encoding.BlockMeta + expectedBloom []byte + expectedIndex []byte + expectedObject []byte + }{ + { + name: "tenants passthrough", + expectedTenants: []string{"1"}, + readerTenants: []string{"1"}, + }, + { + name: "blocks passthrough", + expectedBlocks: []uuid.UUID{blockID}, + readerBlocks: []uuid.UUID{blockID}, + }, + { + name: "index", + expectedIndex: []byte{0x01}, + readerIndex: []byte{0x01}, + }, + { + name: "bloom", + expectedBloom: []byte{0x02}, + readerBloom: []byte{0x02}, + }, + { + name: "object", + expectedObject: []byte{0x03}, + readerObject: []byte{0x03}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockR := &mockReader{ + tenants: tt.readerTenants, + blocks: tt.readerBlocks, + meta: tt.readerMeta, + bloom: tt.readerBloom, + index: tt.readerIndex, + object: tt.readerObject, + } + mockW := &mockWriter{} + mr, _ := miniredis.Run() + + mockC := cache.NewRedisClient(&cache.RedisConfig{ + Endpoint: mr.Addr(), + }) + logger := log.NewNopLogger() + rw := &readerWriter{ + client: cache.NewRedisCache("tempo", mockC, logger), + nextReader: mockR, + nextWriter: mockW, + logger: logger, + } + + ctx := context.Background() + tenants, _ := rw.Tenants(ctx) + assert.Equal(t, tt.expectedTenants, tenants) + blocks, _ := rw.Blocks(ctx, tenantID) + assert.Equal(t, tt.expectedBlocks, blocks) + meta, _ := rw.BlockMeta(ctx, blockID, tenantID) + assert.Equal(t, tt.expectedMeta, meta) + bloom, _ := rw.Bloom(ctx, blockID, tenantID, shardNum) + assert.Equal(t, tt.expectedBloom, bloom) + index, _ := rw.Index(ctx, blockID, tenantID) + assert.Equal(t, tt.expectedIndex, index) + + if tt.expectedObject != nil { + object := make([]byte, 1) + _ = rw.Object(ctx, blockID, tenantID, 0, object) + assert.Equal(t, tt.expectedObject, object) + } + + // clear reader and re-request. things should be cached! + mockR.bloom = nil + mockR.index = nil + mockR.tenants = nil + mockR.blocks = nil + mockR.meta = nil + + bloom, _ = rw.Bloom(ctx, blockID, tenantID, shardNum) + assert.Equal(t, tt.expectedBloom, bloom) + index, _ = rw.Index(ctx, blockID, tenantID) + assert.Equal(t, tt.expectedIndex, index) + + // others should be nil + tenants, _ = rw.Tenants(ctx) + assert.Nil(t, tenants) + blocks, _ = rw.Blocks(ctx, tenantID) + assert.Nil(t, blocks) + meta, _ = rw.BlockMeta(ctx, blockID, tenantID) + assert.Nil(t, tt.expectedMeta, meta) + }) + } +} diff --git a/vendor/github.com/alicebob/gopher-json/LICENSE b/vendor/github.com/alicebob/gopher-json/LICENSE new file mode 100644 index 00000000000..68a49daad8f --- /dev/null +++ b/vendor/github.com/alicebob/gopher-json/LICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/vendor/github.com/alicebob/gopher-json/README.md b/vendor/github.com/alicebob/gopher-json/README.md new file mode 100644 index 00000000000..84fe1bdf949 --- /dev/null +++ b/vendor/github.com/alicebob/gopher-json/README.md @@ -0,0 +1,7 @@ +# 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 new file mode 100644 index 00000000000..b73aeafd530 --- /dev/null +++ b/vendor/github.com/alicebob/gopher-json/doc.go @@ -0,0 +1,33 @@ +// 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/gopher-json/json.go b/vendor/github.com/alicebob/gopher-json/json.go new file mode 100644 index 00000000000..11561333de9 --- /dev/null +++ b/vendor/github.com/alicebob/gopher-json/json.go @@ -0,0 +1,189 @@ +package json + +import ( + "encoding/json" + "errors" + + "github.com/yuin/gopher-lua" +) + +// Preload adds json to the given Lua state's package.preload table. After it +// has been preloaded, it can be loaded using require: +// +// local json = require("json") +func Preload(L *lua.LState) { + L.PreloadModule("json", Loader) +} + +// Loader is the module loader function. +func Loader(L *lua.LState) int { + t := L.NewTable() + L.SetFuncs(t, api) + L.Push(t) + return 1 +} + +var api = map[string]lua.LGFunction{ + "decode": apiDecode, + "encode": apiEncode, +} + +func apiDecode(L *lua.LState) int { + if L.GetTop() != 1 { + L.Error(lua.LString("bad argument #1 to decode"), 1) + return 0 + } + str := L.CheckString(1) + + value, err := Decode(L, []byte(str)) + if err != nil { + L.Push(lua.LNil) + L.Push(lua.LString(err.Error())) + return 2 + } + L.Push(value) + return 1 +} + +func apiEncode(L *lua.LState) int { + if L.GetTop() != 1 { + L.Error(lua.LString("bad argument #1 to encode"), 1) + return 0 + } + value := L.CheckAny(1) + + data, err := Encode(value) + if err != nil { + L.Push(lua.LNil) + L.Push(lua.LString(err.Error())) + return 2 + } + L.Push(lua.LString(string(data))) + return 1 +} + +var ( + errNested = errors.New("cannot encode recursively nested tables to JSON") + errSparseArray = errors.New("cannot encode sparse array") + errInvalidKeys = errors.New("cannot encode mixed or invalid key types") +) + +type invalidTypeError lua.LValueType + +func (i invalidTypeError) Error() string { + return `cannot encode ` + lua.LValueType(i).String() + ` to JSON` +} + +// Encode returns the JSON encoding of value. +func Encode(value lua.LValue) ([]byte, error) { + return json.Marshal(jsonValue{ + LValue: value, + visited: make(map[*lua.LTable]bool), + }) +} + +type jsonValue struct { + lua.LValue + visited map[*lua.LTable]bool +} + +func (j jsonValue) MarshalJSON() (data []byte, err error) { + switch converted := j.LValue.(type) { + case lua.LBool: + data, err = json.Marshal(bool(converted)) + case lua.LNumber: + data, err = json.Marshal(float64(converted)) + case *lua.LNilType: + data = []byte(`null`) + case lua.LString: + data, err = json.Marshal(string(converted)) + case *lua.LTable: + if j.visited[converted] { + return nil, errNested + } + j.visited[converted] = true + + key, value := converted.Next(lua.LNil) + + switch key.Type() { + case lua.LTNil: // empty table + data = []byte(`[]`) + case lua.LTNumber: + arr := make([]jsonValue, 0, converted.Len()) + expectedKey := lua.LNumber(1) + for key != lua.LNil { + if key.Type() != lua.LTNumber { + err = errInvalidKeys + return + } + if expectedKey != key { + err = errSparseArray + return + } + arr = append(arr, jsonValue{value, j.visited}) + expectedKey++ + key, value = converted.Next(key) + } + data, err = json.Marshal(arr) + case lua.LTString: + obj := make(map[string]jsonValue) + for key != lua.LNil { + if key.Type() != lua.LTString { + err = errInvalidKeys + return + } + obj[key.String()] = jsonValue{value, j.visited} + key, value = converted.Next(key) + } + data, err = json.Marshal(obj) + default: + err = errInvalidKeys + } + default: + err = invalidTypeError(j.LValue.Type()) + } + return +} + +// Decode converts the JSON encoded data to Lua values. +func Decode(L *lua.LState, data []byte) (lua.LValue, error) { + var value interface{} + err := json.Unmarshal(data, &value) + if err != nil { + return nil, err + } + return DecodeValue(L, value), nil +} + +// DecodeValue converts the value to a Lua value. +// +// This function only converts values that the encoding/json package decodes to. +// All other values will return lua.LNil. +func DecodeValue(L *lua.LState, value interface{}) lua.LValue { + switch converted := value.(type) { + case bool: + return lua.LBool(converted) + case float64: + return lua.LNumber(converted) + case string: + return lua.LString(converted) + case json.Number: + return lua.LString(converted) + case []interface{}: + arr := L.CreateTable(len(converted), 0) + for _, item := range converted { + arr.Append(DecodeValue(L, item)) + } + return arr + case map[string]interface{}: + tbl := L.CreateTable(0, len(converted)) + for key, item := range converted { + tbl.RawSetH(lua.LString(key), DecodeValue(L, item)) + } + return tbl + case nil: + return lua.LNil + } + + return lua.LNil +} diff --git a/vendor/github.com/alicebob/miniredis/.gitignore b/vendor/github.com/alicebob/miniredis/.gitignore new file mode 100644 index 00000000000..a6fadca4d6e --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/.gitignore @@ -0,0 +1 @@ +/integration/redis_src/ diff --git a/vendor/github.com/alicebob/miniredis/.travis.yml b/vendor/github.com/alicebob/miniredis/.travis.yml new file mode 100644 index 00000000000..d9122d17d30 --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/.travis.yml @@ -0,0 +1,13 @@ +language: go + +before_script: + - (cd ./integration && ./get_redis.sh) + +install: go get -t + +script: make test testrace int + +sudo: false + +go: + - 1.11 diff --git a/vendor/github.com/alicebob/miniredis/LICENSE b/vendor/github.com/alicebob/miniredis/LICENSE new file mode 100644 index 00000000000..bb02657caa2 --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Harmen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/alicebob/miniredis/Makefile b/vendor/github.com/alicebob/miniredis/Makefile new file mode 100644 index 00000000000..2aa4cd2c573 --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/Makefile @@ -0,0 +1,12 @@ +.PHONY: all test testrace int + +all: test + +test: + go test ./... + +testrace: + go test -race ./... + +int: + ${MAKE} -C integration all diff --git a/vendor/github.com/alicebob/miniredis/README.md b/vendor/github.com/alicebob/miniredis/README.md new file mode 100644 index 00000000000..bfeed831401 --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/README.md @@ -0,0 +1,336 @@ +# Miniredis + +Pure Go Redis test server, used in Go unittests. + + +## + +Sometimes you want to test code which uses Redis, without making it a full-blown +integration test. +Miniredis implements (parts of) the Redis server, to be used in unittests. It +enables a simple, cheap, in-memory, Redis replacement, with a real TCP interface. Think of it as the Redis version of `net/http/httptest`. + +It saves you from using mock code, and since the redis server lives in the +test process you can query for values directly, without going through the server +stack. + +There are no dependencies on external binaries, so you can easily integrate it in automated build processes. + +## Changelog + +### 2.5.0 + +Added ZPopMin and ZPopMax + +### v2.4.6 + +support for TIME (thanks @leon-barrett and @lirao) +support for ZREVRANGEBYLEX +fix for SINTER (thanks @robstein) +updates for latest redis + +### 2.4.4 + +Fixed nil Lua return value (#43) + +### 2.4.3 + +Fixed using Lua with authenticated redis. + +### 2.4.2 + +Changed redigo import path. + +### 2.4 + +Minor cleanups. Miniredis now requires Go >= 1.9 (only for the tests. If you don't run the tests you can use an older Go version). + +### 2.3.1 + +Lua changes: added `cjson` library, and `redis.sha1hex()`. + +### 2.3 + +Added the `EVAL`, `EVALSHA`, and `SCRIPT` commands. Uses a pure Go Lua interpreter. Please open an issue if there are problems with any Lua code. + +### 2.2 + +Introduced `StartAddr()`. + +### 2.1 + +Internal cleanups. No changes in functionality. + +### 2.0 + +2.0.0 improves TTLs to be `time.Duration` values. `.Expire()` is removed and +replaced by `.TTL()`, which returns the TTL as a `time.Duration`. +This should be the change needed to upgrade: + +1.0: + + m.Expire() == 4 + +2.0: + + m.TTL() == 4 * time.Second + +Furthermore, `.SetTime()` is added to help with `EXPIREAT` commands, and `.FastForward()` is introduced to test keys expiration. + + +## Commands + +Implemented commands: + + - Connection (complete) + - AUTH -- see RequireAuth() + - ECHO + - PING + - SELECT + - QUIT + - Key + - DEL + - EXISTS + - EXPIRE + - EXPIREAT + - KEYS + - MOVE + - PERSIST + - PEXPIRE + - PEXPIREAT + - PTTL + - RENAME + - RENAMENX + - RANDOMKEY -- call math.rand.Seed(...) once before using. + - TTL + - TYPE + - SCAN + - Transactions (complete) + - DISCARD + - EXEC + - MULTI + - UNWATCH + - WATCH + - Server + - DBSIZE + - FLUSHALL + - FLUSHDB + - TIME -- returns time.Now() or value set by SetTime() + - String keys (complete) + - APPEND + - BITCOUNT + - BITOP + - BITPOS + - DECR + - DECRBY + - GET + - GETBIT + - GETRANGE + - GETSET + - INCR + - INCRBY + - INCRBYFLOAT + - MGET + - MSET + - MSETNX + - PSETEX + - SET + - SETBIT + - SETEX + - SETNX + - SETRANGE + - STRLEN + - Hash keys (complete) + - HDEL + - HEXISTS + - HGET + - HGETALL + - HINCRBY + - HINCRBYFLOAT + - HKEYS + - HLEN + - HMGET + - HMSET + - HSET + - HSETNX + - HVALS + - HSCAN + - List keys (complete) + - BLPOP + - BRPOP + - BRPOPLPUSH + - LINDEX + - LINSERT + - LLEN + - LPOP + - LPUSH + - LPUSHX + - LRANGE + - LREM + - LSET + - LTRIM + - RPOP + - RPOPLPUSH + - RPUSH + - RPUSHX + - Set keys (complete) + - SADD + - SCARD + - SDIFF + - SDIFFSTORE + - SINTER + - SINTERSTORE + - SISMEMBER + - SMEMBERS + - SMOVE + - SPOP -- call math.rand.Seed(...) once before using. + - SRANDMEMBER -- call math.rand.Seed(...) once before using. + - SREM + - SUNION + - SUNIONSTORE + - SSCAN + - Sorted Set keys (complete) + - ZADD + - ZCARD + - ZCOUNT + - ZINCRBY + - ZINTERSTORE + - ZLEXCOUNT + - ZPOPMIN + - ZPOPMAX + - ZRANGE + - ZRANGEBYLEX + - ZRANGEBYSCORE + - ZRANK + - ZREM + - ZREMRANGEBYLEX + - ZREMRANGEBYRANK + - ZREMRANGEBYSCORE + - ZREVRANGE + - ZREVRANGEBYLEX + - ZREVRANGEBYSCORE + - ZREVRANK + - ZSCORE + - ZUNIONSTORE + - ZSCAN + - Scripting + - EVAL + - EVALSHA + - SCRIPT LOAD + - SCRIPT EXISTS + - SCRIPT FLUSH + +## TTLs, key expiration, and time + +Since miniredis is intended to be used in unittests TTLs don't decrease +automatically. You can use `TTL()` to get the TTL (as a time.Duration) of a +key. It will return 0 when no TTL is set. + +`m.FastForward(d)` can be used to decrement all TTLs. All TTLs which become <= +0 will be removed. + +EXPIREAT and PEXPIREAT values will be +converted to a duration. For that you can either set m.SetTime(t) to use that +time as the base for the (P)EXPIREAT conversion, or don't call SetTime(), in +which case time.Now() will be used. + +SetTime() also sets the value returned by TIME, which defaults to time.Now(). +It is not updated by FastForward, only by SetTime. + +## Example + +``` Go +func TestSomething(t *testing.T) { + s, err := miniredis.Run() + if err != nil { + panic(err) + } + defer s.Close() + + // Optionally set some keys your code expects: + s.Set("foo", "bar") + s.HSet("some", "other", "key") + + // Run your code and see if it behaves. + // An example using the redigo library from "github.com/gomodule/redigo/redis": + c, err := redis.Dial("tcp", s.Addr()) + _, err = c.Do("SET", "foo", "bar") + + // Optionally check values in redis... + if got, err := s.Get("foo"); err != nil || got != "bar" { + t.Error("'foo' has the wrong value") + } + // ... or use a helper for that: + s.CheckGet(t, "foo", "bar") + + // TTL and expiration: + s.Set("foo", "bar") + s.SetTTL("foo", 10*time.Second) + s.FastForward(11 * time.Second) + if s.Exists("foo") { + t.Fatal("'foo' should not have existed anymore") + } +} +``` + +## Not supported + +Commands which will probably not be implemented: + + - CLUSTER (all) + - ~~CLUSTER *~~ + - ~~READONLY~~ + - ~~READWRITE~~ + - GEO (all) -- unless someone needs these + - ~~GEOADD~~ + - ~~GEODIST~~ + - ~~GEOHASH~~ + - ~~GEOPOS~~ + - ~~GEORADIUS~~ + - ~~GEORADIUSBYMEMBER~~ + - HyperLogLog (all) -- unless someone needs these + - ~~PFADD~~ + - ~~PFCOUNT~~ + - ~~PFMERGE~~ + - Key + - ~~DUMP~~ + - ~~MIGRATE~~ + - ~~OBJECT~~ + - ~~RESTORE~~ + - ~~WAIT~~ + - Pub/Sub (all) + - ~~PSUBSCRIBE~~ + - ~~PUBLISH~~ + - ~~PUBSUB~~ + - ~~PUNSUBSCRIBE~~ + - ~~SUBSCRIBE~~ + - ~~UNSUBSCRIBE~~ + - Scripting + - ~~SCRIPT DEBUG~~ + - ~~SCRIPT KILL~~ + - Server + - ~~BGSAVE~~ + - ~~BGWRITEAOF~~ + - ~~CLIENT *~~ + - ~~COMMAND *~~ + - ~~CONFIG *~~ + - ~~DEBUG *~~ + - ~~INFO~~ + - ~~LASTSAVE~~ + - ~~MONITOR~~ + - ~~ROLE~~ + - ~~SAVE~~ + - ~~SHUTDOWN~~ + - ~~SLAVEOF~~ + - ~~SLOWLOG~~ + - ~~SYNC~~ + + +## &c. + +Tests are run against Redis 5.0.3. The [./integration](./integration/) subdir +compares miniredis against a real redis instance. + + +[![Build Status](https://travis-ci.org/alicebob/miniredis.svg?branch=master)](https://travis-ci.org/alicebob/miniredis) +[![GoDoc](https://godoc.org/github.com/alicebob/miniredis?status.svg)](https://godoc.org/github.com/alicebob/miniredis) diff --git a/vendor/github.com/alicebob/miniredis/check.go b/vendor/github.com/alicebob/miniredis/check.go new file mode 100644 index 00000000000..8b42b2e0fd6 --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/check.go @@ -0,0 +1,68 @@ +package miniredis + +// 'Fail' methods. + +import ( + "fmt" + "path/filepath" + "reflect" + "runtime" + "sort" +) + +// T is implemented by Testing.T +type T interface { + Fail() +} + +// CheckGet does not call Errorf() iff there is a string key with the +// expected value. Normal use case is `m.CheckGet(t, "username", "theking")`. +func (m *Miniredis) CheckGet(t T, key, expected string) { + found, err := m.Get(key) + if err != nil { + lError(t, "GET error, key %#v: %v", key, err) + return + } + if found != expected { + lError(t, "GET error, key %#v: Expected %#v, got %#v", key, expected, found) + return + } +} + +// CheckList does not call Errorf() iff there is a list key with the +// expected values. +// Normal use case is `m.CheckGet(t, "favorite_colors", "red", "green", "infrared")`. +func (m *Miniredis) CheckList(t T, key string, expected ...string) { + found, err := m.List(key) + if err != nil { + lError(t, "List error, key %#v: %v", key, err) + return + } + if !reflect.DeepEqual(expected, found) { + lError(t, "List error, key %#v: Expected %#v, got %#v", key, expected, found) + return + } +} + +// CheckSet does not call Errorf() iff there is a set key with the +// expected values. +// Normal use case is `m.CheckSet(t, "visited", "Rome", "Stockholm", "Dublin")`. +func (m *Miniredis) CheckSet(t T, key string, expected ...string) { + found, err := m.Members(key) + if err != nil { + lError(t, "Set error, key %#v: %v", key, err) + return + } + sort.Strings(expected) + if !reflect.DeepEqual(expected, found) { + lError(t, "Set error, key %#v: Expected %#v, got %#v", key, expected, found) + return + } +} + +func lError(t T, format string, args ...interface{}) { + _, file, line, _ := runtime.Caller(2) + prefix := fmt.Sprintf("%s:%d: ", filepath.Base(file), line) + fmt.Printf(prefix+format+"\n", args...) + t.Fail() +} diff --git a/vendor/github.com/alicebob/miniredis/cmd_connection.go b/vendor/github.com/alicebob/miniredis/cmd_connection.go new file mode 100644 index 00000000000..ca648f4bfb9 --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/cmd_connection.go @@ -0,0 +1,96 @@ +// Commands from https://redis.io/commands#connection + +package miniredis + +import ( + "strconv" + + "github.com/alicebob/miniredis/server" +) + +func commandsConnection(m *Miniredis) { + m.srv.Register("AUTH", m.cmdAuth) + m.srv.Register("ECHO", m.cmdEcho) + m.srv.Register("PING", m.cmdPing) + m.srv.Register("SELECT", m.cmdSelect) + m.srv.Register("QUIT", m.cmdQuit) +} + +// PING +func (m *Miniredis) cmdPing(c *server.Peer, cmd string, args []string) { + if !m.handleAuth(c) { + return + } + c.WriteInline("PONG") +} + +// AUTH +func (m *Miniredis) cmdAuth(c *server.Peer, cmd string, args []string) { + if len(args) != 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + pw := args[0] + + m.Lock() + defer m.Unlock() + if m.password == "" { + c.WriteError("ERR Client sent AUTH, but no password is set") + return + } + if m.password != pw { + c.WriteError("ERR invalid password") + return + } + + setAuthenticated(c) + c.WriteOK() +} + +// ECHO +func (m *Miniredis) cmdEcho(c *server.Peer, cmd string, args []string) { + if len(args) != 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + msg := args[0] + c.WriteBulk(msg) +} + +// SELECT +func (m *Miniredis) cmdSelect(c *server.Peer, cmd string, args []string) { + if len(args) != 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + id, err := strconv.Atoi(args[0]) + if err != nil { + id = 0 + } + + m.Lock() + defer m.Unlock() + + ctx := getCtx(c) + ctx.selectedDB = id + + c.WriteOK() +} + +// QUIT +func (m *Miniredis) cmdQuit(c *server.Peer, cmd string, args []string) { + // QUIT isn't transactionfied and accepts any arguments. + c.WriteOK() + c.Close() +} diff --git a/vendor/github.com/alicebob/miniredis/cmd_generic.go b/vendor/github.com/alicebob/miniredis/cmd_generic.go new file mode 100644 index 00000000000..fa394790877 --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/cmd_generic.go @@ -0,0 +1,479 @@ +// Commands from https://redis.io/commands#generic + +package miniredis + +import ( + "math/rand" + "strconv" + "strings" + "time" + + "github.com/alicebob/miniredis/server" +) + +// commandsGeneric handles EXPIRE, TTL, PERSIST, &c. +func commandsGeneric(m *Miniredis) { + m.srv.Register("DEL", 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("KEYS", m.cmdKeys) + // MIGRATE + m.srv.Register("MOVE", m.cmdMove) + // OBJECT + m.srv.Register("PERSIST", m.cmdPersist) + m.srv.Register("PEXPIRE", makeCmdExpire(m, false, time.Millisecond)) + m.srv.Register("PEXPIREAT", makeCmdExpire(m, true, time.Millisecond)) + m.srv.Register("PTTL", m.cmdPTTL) + m.srv.Register("RANDOMKEY", m.cmdRandomkey) + m.srv.Register("RENAME", m.cmdRename) + m.srv.Register("RENAMENX", m.cmdRenamenx) + // RESTORE + // SORT + m.srv.Register("TTL", m.cmdTTL) + m.srv.Register("TYPE", m.cmdType) + m.srv.Register("SCAN", m.cmdScan) +} + +// generic expire command for EXPIRE, PEXPIRE, EXPIREAT, PEXPIREAT +// d is the time unit. If unix is set it'll be seen as a unixtimestamp and +// 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 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + value := args[1] + i, err := strconv.Atoi(value) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + // Key must be present. + if _, ok := db.keys[key]; !ok { + c.WriteInt(0) + return + } + if unix { + var ts time.Time + switch d { + case time.Millisecond: + ts = time.Unix(int64(i/1000), 1000000*int64(i%1000)) + case time.Second: + ts = time.Unix(int64(i), 0) + default: + panic("invalid time unit (d). Fixme!") + } + now := m.now + if now.IsZero() { + now = time.Now().UTC() + } + db.ttl[key] = ts.Sub(now) + } else { + db.ttl[key] = time.Duration(i) * d + } + db.keyVersion[key]++ + db.checkTTL(key) + c.WriteInt(1) + }) + } +} + +// TTL +func (m *Miniredis) cmdTTL(c *server.Peer, cmd string, args []string) { + if len(args) != 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + key := args[0] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if _, ok := db.keys[key]; !ok { + // No such key + c.WriteInt(-2) + return + } + + v, ok := db.ttl[key] + if !ok { + // no expire value + c.WriteInt(-1) + return + } + c.WriteInt(int(v.Seconds())) + }) +} + +// PTTL +func (m *Miniredis) cmdPTTL(c *server.Peer, cmd string, args []string) { + if len(args) != 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + key := args[0] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if _, ok := db.keys[key]; !ok { + // no such key + c.WriteInt(-2) + return + } + + v, ok := db.ttl[key] + if !ok { + // no expire value + c.WriteInt(-1) + return + } + c.WriteInt(int(v.Nanoseconds() / 1000000)) + }) +} + +// PERSIST +func (m *Miniredis) cmdPersist(c *server.Peer, cmd string, args []string) { + if len(args) != 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + key := args[0] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if _, ok := db.keys[key]; !ok { + // no such key + c.WriteInt(0) + return + } + + if _, ok := db.ttl[key]; !ok { + // no expire value + c.WriteInt(0) + return + } + delete(db.ttl, key) + db.keyVersion[key]++ + c.WriteInt(1) + }) +} + +// DEL +func (m *Miniredis) cmdDel(c *server.Peer, cmd string, args []string) { + if !m.handleAuth(c) { + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + count := 0 + for _, key := range args { + if db.exists(key) { + count++ + } + db.del(key, true) // delete expire + } + c.WriteInt(count) + }) +} + +// TYPE +func (m *Miniredis) cmdType(c *server.Peer, cmd string, args []string) { + if len(args) != 1 { + setDirty(c) + c.WriteError("usage error") + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + t, ok := db.keys[key] + if !ok { + c.WriteInline("none") + return + } + + c.WriteInline(t) + }) +} + +// EXISTS +func (m *Miniredis) cmdExists(c *server.Peer, cmd string, args []string) { + if len(args) < 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + found := 0 + for _, k := range args { + if db.exists(k) { + found++ + } + } + c.WriteInt(found) + }) +} + +// MOVE +func (m *Miniredis) cmdMove(c *server.Peer, cmd string, args []string) { + if len(args) != 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + targetDB, err := strconv.Atoi(args[1]) + if err != nil { + targetDB = 0 + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + if ctx.selectedDB == targetDB { + c.WriteError("ERR source and destination objects are the same") + return + } + db := m.db(ctx.selectedDB) + targetDB := m.db(targetDB) + + if !db.move(key, targetDB) { + c.WriteInt(0) + return + } + c.WriteInt(1) + }) +} + +// KEYS +func (m *Miniredis) cmdKeys(c *server.Peer, cmd string, args []string) { + if len(args) != 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + keys := matchKeys(db.allKeys(), key) + c.WriteLen(len(keys)) + for _, s := range keys { + c.WriteBulk(s) + } + }) +} + +// RANDOMKEY +func (m *Miniredis) cmdRandomkey(c *server.Peer, cmd string, args []string) { + if len(args) != 0 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if len(db.keys) == 0 { + c.WriteNull() + return + } + nr := rand.Intn(len(db.keys)) + for k := range db.keys { + if nr == 0 { + c.WriteBulk(k) + return + } + nr-- + } + }) +} + +// RENAME +func (m *Miniredis) cmdRename(c *server.Peer, cmd string, args []string) { + if len(args) != 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + from, to := args[0], args[1] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if !db.exists(from) { + c.WriteError(msgKeyNotFound) + return + } + + db.rename(from, to) + c.WriteOK() + }) +} + +// RENAMENX +func (m *Miniredis) cmdRenamenx(c *server.Peer, cmd string, args []string) { + if len(args) != 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + from, to := args[0], args[1] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if !db.exists(from) { + c.WriteError(msgKeyNotFound) + return + } + + if db.exists(to) { + c.WriteInt(0) + return + } + + db.rename(from, 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 + } + + cursor, err := strconv.Atoi(args[0]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidCursor) + return + } + args = args[1:] + + // 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 + if len(args) < 2 { + setDirty(c) + c.WriteError(msgSyntaxError) + return + } + if _, err := strconv.Atoi(args[1]); err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + args = args[2:] + continue + } + if strings.ToLower(args[0]) == "match" { + if len(args) < 2 { + setDirty(c) + c.WriteError(msgSyntaxError) + return + } + withMatch = true + match, args = args[1], args[2:] + continue + } + setDirty(c) + c.WriteError(msgSyntaxError) + 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 + } + + keys := db.allKeys() + if withMatch { + keys = matchKeys(keys, match) + } + + c.WriteLen(2) + c.WriteBulk("0") // no next cursor + c.WriteLen(len(keys)) + for _, k := range keys { + c.WriteBulk(k) + } + }) +} diff --git a/vendor/github.com/alicebob/miniredis/cmd_hash.go b/vendor/github.com/alicebob/miniredis/cmd_hash.go new file mode 100644 index 00000000000..1c65ebece7e --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/cmd_hash.go @@ -0,0 +1,571 @@ +// Commands from https://redis.io/commands#hash + +package miniredis + +import ( + "strconv" + "strings" + + "github.com/alicebob/miniredis/server" +) + +// commandsHash handles all hash value operations. +func commandsHash(m *Miniredis) { + m.srv.Register("HDEL", m.cmdHdel) + m.srv.Register("HEXISTS", m.cmdHexists) + m.srv.Register("HGET", m.cmdHget) + m.srv.Register("HGETALL", m.cmdHgetall) + m.srv.Register("HINCRBY", m.cmdHincrby) + m.srv.Register("HINCRBYFLOAT", m.cmdHincrbyfloat) + m.srv.Register("HKEYS", m.cmdHkeys) + m.srv.Register("HLEN", m.cmdHlen) + m.srv.Register("HMGET", m.cmdHmget) + m.srv.Register("HMSET", m.cmdHmset) + m.srv.Register("HSET", m.cmdHset) + m.srv.Register("HSETNX", m.cmdHsetnx) + m.srv.Register("HVALS", m.cmdHvals) + m.srv.Register("HSCAN", m.cmdHscan) +} + +// HSET +func (m *Miniredis) cmdHset(c *server.Peer, cmd string, args []string) { + if len(args) != 3 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key, field, value := args[0], args[1], 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" { + c.WriteError(msgWrongType) + return + } + + if db.hashSet(key, field, value) { + c.WriteInt(0) + } else { + c.WriteInt(1) + } + }) +} + +// HSETNX +func (m *Miniredis) cmdHsetnx(c *server.Peer, cmd string, args []string) { + if len(args) != 3 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key, field, value := args[0], args[1], 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" { + c.WriteError(msgWrongType) + return + } + + if _, ok := db.hashKeys[key]; !ok { + db.hashKeys[key] = map[string]string{} + db.keys[key] = "hash" + } + _, ok := db.hashKeys[key][field] + if ok { + c.WriteInt(0) + return + } + db.hashKeys[key][field] = value + db.keyVersion[key]++ + c.WriteInt(1) + }) +} + +// HMSET +func (m *Miniredis) cmdHmset(c *server.Peer, cmd string, args []string) { + if len(args) < 3 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key, args := args[0], args[1:] + if len(args)%2 != 0 { + setDirty(c) + // non-default error message + c.WriteError("ERR wrong number of arguments for HMSET") + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if t, ok := db.keys[key]; ok && t != "hash" { + c.WriteError(msgWrongType) + return + } + + for len(args) > 0 { + field, value := args[0], args[1] + args = args[2:] + db.hashSet(key, field, value) + } + c.WriteOK() + }) +} + +// HGET +func (m *Miniredis) cmdHget(c *server.Peer, cmd string, args []string) { + if len(args) != 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key, field := args[0], args[1] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + t, ok := db.keys[key] + if !ok { + c.WriteNull() + return + } + if t != "hash" { + c.WriteError(msgWrongType) + return + } + value, ok := db.hashKeys[key][field] + if !ok { + c.WriteNull() + return + } + c.WriteBulk(value) + }) +} + +// HDEL +func (m *Miniredis) cmdHdel(c *server.Peer, cmd string, args []string) { + if len(args) < 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key, fields := args[0], args[1:] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + t, ok := db.keys[key] + if !ok { + // No key is zero deleted + c.WriteInt(0) + return + } + if t != "hash" { + c.WriteError(msgWrongType) + return + } + + deleted := 0 + for _, f := range fields { + _, ok := db.hashKeys[key][f] + if !ok { + continue + } + delete(db.hashKeys[key], f) + deleted++ + } + c.WriteInt(deleted) + + // Nothing left. Remove the whole key. + if len(db.hashKeys[key]) == 0 { + db.del(key, true) + } + }) +} + +// HEXISTS +func (m *Miniredis) cmdHexists(c *server.Peer, cmd string, args []string) { + if len(args) != 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key, field := args[0], args[1] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + t, ok := db.keys[key] + if !ok { + c.WriteInt(0) + return + } + if t != "hash" { + c.WriteError(msgWrongType) + return + } + + if _, ok := db.hashKeys[key][field]; !ok { + c.WriteInt(0) + return + } + c.WriteInt(1) + }) +} + +// HGETALL +func (m *Miniredis) cmdHgetall(c *server.Peer, cmd string, args []string) { + if len(args) != 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + t, ok := db.keys[key] + if !ok { + c.WriteLen(0) + return + } + if t != "hash" { + c.WriteError(msgWrongType) + return + } + + c.WriteLen(len(db.hashKeys[key]) * 2) + for _, k := range db.hashFields(key) { + c.WriteBulk(k) + c.WriteBulk(db.hashGet(key, k)) + } + }) +} + +// HKEYS +func (m *Miniredis) cmdHkeys(c *server.Peer, cmd string, args []string) { + if len(args) != 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if !db.exists(key) { + c.WriteLen(0) + return + } + if db.t(key) != "hash" { + c.WriteError(msgWrongType) + return + } + + fields := db.hashFields(key) + c.WriteLen(len(fields)) + for _, f := range fields { + c.WriteBulk(f) + } + }) +} + +// HVALS +func (m *Miniredis) cmdHvals(c *server.Peer, cmd string, args []string) { + if len(args) != 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + t, ok := db.keys[key] + if !ok { + c.WriteLen(0) + return + } + if t != "hash" { + c.WriteError(msgWrongType) + return + } + + c.WriteLen(len(db.hashKeys[key])) + for _, v := range db.hashKeys[key] { + c.WriteBulk(v) + } + }) +} + +// HLEN +func (m *Miniredis) cmdHlen(c *server.Peer, cmd string, args []string) { + if len(args) != 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + t, ok := db.keys[key] + if !ok { + c.WriteInt(0) + return + } + if t != "hash" { + c.WriteError(msgWrongType) + return + } + + c.WriteInt(len(db.hashKeys[key])) + }) +} + +// HMGET +func (m *Miniredis) cmdHmget(c *server.Peer, cmd string, args []string) { + if len(args) < 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if t, ok := db.keys[key]; ok && t != "hash" { + c.WriteError(msgWrongType) + return + } + + f, ok := db.hashKeys[key] + if !ok { + f = map[string]string{} + } + + c.WriteLen(len(args) - 1) + for _, k := range args[1:] { + v, ok := f[k] + if !ok { + c.WriteNull() + continue + } + c.WriteBulk(v) + } + }) +} + +// HINCRBY +func (m *Miniredis) cmdHincrby(c *server.Peer, cmd string, args []string) { + if len(args) != 3 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key, field, deltas := args[0], args[1], args[2] + + delta, err := strconv.Atoi(deltas) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if t, ok := db.keys[key]; ok && t != "hash" { + c.WriteError(msgWrongType) + return + } + + v, err := db.hashIncr(key, field, delta) + if err != nil { + c.WriteError(err.Error()) + return + } + c.WriteInt(v) + }) +} + +// HINCRBYFLOAT +func (m *Miniredis) cmdHincrbyfloat(c *server.Peer, cmd string, args []string) { + if len(args) != 3 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key, field, deltas := args[0], args[1], args[2] + + delta, err := strconv.ParseFloat(deltas, 64) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidFloat) + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if t, ok := db.keys[key]; ok && t != "hash" { + c.WriteError(msgWrongType) + return + } + + v, err := db.hashIncrfloat(key, field, delta) + if err != nil { + c.WriteError(err.Error()) + return + } + c.WriteBulk(formatFloat(v)) + }) +} + +// HSCAN +func (m *Miniredis) cmdHscan(c *server.Peer, cmd string, args []string) { + if len(args) < 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + cursor, err := strconv.Atoi(args[1]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidCursor) + 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 + if len(args) < 2 { + setDirty(c) + c.WriteError(msgSyntaxError) + return + } + _, err := strconv.Atoi(args[1]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + args = args[2:] + continue + } + if strings.ToLower(args[0]) == "match" { + if len(args) < 2 { + setDirty(c) + c.WriteError(msgSyntaxError) + return + } + withMatch = true + match, args = args[1], args[2:] + continue + } + setDirty(c) + c.WriteError(msgSyntaxError) + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + // 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 + } + if db.exists(key) && db.t(key) != "hash" { + c.WriteError(ErrWrongType.Error()) + return + } + + members := db.hashFields(key) + if withMatch { + members = matchKeys(members, match) + } + + c.WriteLen(2) + c.WriteBulk("0") // no next cursor + // HSCAN gives key, values. + c.WriteLen(len(members) * 2) + for _, k := range members { + c.WriteBulk(k) + c.WriteBulk(db.hashGet(key, k)) + } + }) +} diff --git a/vendor/github.com/alicebob/miniredis/cmd_list.go b/vendor/github.com/alicebob/miniredis/cmd_list.go new file mode 100644 index 00000000000..ae543dc6c03 --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/cmd_list.go @@ -0,0 +1,687 @@ +// Commands from https://redis.io/commands#list + +package miniredis + +import ( + "strconv" + "strings" + "time" + + "github.com/alicebob/miniredis/server" +) + +type leftright int + +const ( + left leftright = iota + right +) + +// commandsList handles list commands (mostly L*) +func commandsList(m *Miniredis) { + m.srv.Register("BLPOP", m.cmdBlpop) + m.srv.Register("BRPOP", m.cmdBrpop) + m.srv.Register("BRPOPLPUSH", m.cmdBrpoplpush) + m.srv.Register("LINDEX", m.cmdLindex) + m.srv.Register("LINSERT", m.cmdLinsert) + m.srv.Register("LLEN", m.cmdLlen) + m.srv.Register("LPOP", m.cmdLpop) + m.srv.Register("LPUSH", m.cmdLpush) + m.srv.Register("LPUSHX", m.cmdLpushx) + m.srv.Register("LRANGE", m.cmdLrange) + m.srv.Register("LREM", m.cmdLrem) + m.srv.Register("LSET", m.cmdLset) + m.srv.Register("LTRIM", m.cmdLtrim) + m.srv.Register("RPOP", m.cmdRpop) + m.srv.Register("RPOPLPUSH", m.cmdRpoplpush) + m.srv.Register("RPUSH", m.cmdRpush) + m.srv.Register("RPUSHX", m.cmdRpushx) +} + +// BLPOP +func (m *Miniredis) cmdBlpop(c *server.Peer, cmd string, args []string) { + m.cmdBXpop(c, cmd, args, left) +} + +// BRPOP +func (m *Miniredis) cmdBrpop(c *server.Peer, cmd string, args []string) { + m.cmdBXpop(c, cmd, args, right) +} + +func (m *Miniredis) cmdBXpop(c *server.Peer, cmd string, args []string, lr leftright) { + if len(args) < 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + 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 + } + if timeout < 0 { + setDirty(c) + c.WriteError(msgNegTimeout) + return + } + + blocking( + m, + c, + time.Duration(timeout)*time.Second, + func(c *server.Peer, ctx *connCtx) bool { + db := m.db(ctx.selectedDB) + for _, key := range keys { + if !db.exists(key) { + continue + } + if db.t(key) != "list" { + c.WriteError(msgWrongType) + return true + } + + if len(db.listKeys[key]) == 0 { + continue + } + c.WriteLen(2) + c.WriteBulk(key) + var v string + switch lr { + case left: + v = db.listLpop(key) + case right: + v = db.listPop(key) + } + c.WriteBulk(v) + return true + } + return false + }, + func(c *server.Peer) { + // timeout + c.WriteNull() + }, + ) +} + +// LINDEX +func (m *Miniredis) cmdLindex(c *server.Peer, cmd string, args []string) { + if len(args) != 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key, offsets := args[0], args[1] + + offset, err := strconv.Atoi(offsets) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + t, ok := db.keys[key] + if !ok { + // No such key + c.WriteNull() + return + } + if t != "list" { + c.WriteError(msgWrongType) + return + } + + l := db.listKeys[key] + if offset < 0 { + offset = len(l) + offset + } + if offset < 0 || offset > len(l)-1 { + c.WriteNull() + return + } + c.WriteBulk(l[offset]) + }) +} + +// LINSERT +func (m *Miniredis) cmdLinsert(c *server.Peer, cmd string, args []string) { + if len(args) != 4 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + where := 0 + switch strings.ToLower(args[1]) { + case "before": + where = -1 + case "after": + where = +1 + default: + setDirty(c) + c.WriteError(msgSyntaxError) + return + } + pivot := args[2] + value := args[3] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + t, ok := db.keys[key] + if !ok { + // No such key + c.WriteInt(0) + return + } + if t != "list" { + c.WriteError(msgWrongType) + return + } + + l := db.listKeys[key] + for i, el := range l { + if el != pivot { + continue + } + + if where < 0 { + l = append(l[:i], append(listKey{value}, l[i:]...)...) + } else { + if i == len(l)-1 { + l = append(l, value) + } else { + l = append(l[:i+1], append(listKey{value}, l[i+1:]...)...) + } + } + db.listKeys[key] = l + db.keyVersion[key]++ + c.WriteInt(len(l)) + return + } + c.WriteInt(-1) + }) +} + +// LLEN +func (m *Miniredis) cmdLlen(c *server.Peer, cmd string, args []string) { + if len(args) != 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + t, ok := db.keys[key] + if !ok { + // No such key. That's zero length. + c.WriteInt(0) + return + } + if t != "list" { + c.WriteError(msgWrongType) + return + } + + c.WriteInt(len(db.listKeys[key])) + }) +} + +// LPOP +func (m *Miniredis) cmdLpop(c *server.Peer, cmd string, args []string) { + m.cmdXpop(c, cmd, args, left) +} + +// RPOP +func (m *Miniredis) cmdRpop(c *server.Peer, cmd string, args []string) { + m.cmdXpop(c, cmd, args, right) +} + +func (m *Miniredis) cmdXpop(c *server.Peer, cmd string, args []string, lr leftright) { + if len(args) != 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if !db.exists(key) { + // non-existing key is fine + c.WriteNull() + return + } + if db.t(key) != "list" { + c.WriteError(msgWrongType) + return + } + + var elem string + switch lr { + case left: + elem = db.listLpop(key) + case right: + elem = db.listPop(key) + } + c.WriteBulk(elem) + }) +} + +// LPUSH +func (m *Miniredis) cmdLpush(c *server.Peer, cmd string, args []string) { + m.cmdXpush(c, cmd, args, left) +} + +// RPUSH +func (m *Miniredis) cmdRpush(c *server.Peer, cmd string, args []string) { + m.cmdXpush(c, cmd, args, right) +} + +func (m *Miniredis) cmdXpush(c *server.Peer, cmd string, args []string, lr leftright) { + if len(args) < 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key, args := args[0], args[1:] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if db.exists(key) && db.t(key) != "list" { + c.WriteError(msgWrongType) + return + } + + var newLen int + for _, value := range args { + switch lr { + case left: + newLen = db.listLpush(key, value) + case right: + newLen = db.listPush(key, value) + } + } + c.WriteInt(newLen) + }) +} + +// LPUSHX +func (m *Miniredis) cmdLpushx(c *server.Peer, cmd string, args []string) { + m.cmdXpushx(c, cmd, args, left) +} + +// RPUSHX +func (m *Miniredis) cmdRpushx(c *server.Peer, cmd string, args []string) { + m.cmdXpushx(c, cmd, args, right) +} + +func (m *Miniredis) cmdXpushx(c *server.Peer, cmd string, args []string, lr leftright) { + if len(args) < 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key, args := args[0], args[1:] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if !db.exists(key) { + c.WriteInt(0) + return + } + if db.t(key) != "list" { + c.WriteError(msgWrongType) + return + } + + var newLen int + for _, value := range args { + switch lr { + case left: + newLen = db.listLpush(key, value) + case right: + newLen = db.listPush(key, value) + } + } + c.WriteInt(newLen) + }) +} + +// LRANGE +func (m *Miniredis) cmdLrange(c *server.Peer, cmd string, args []string) { + if len(args) != 3 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + start, err := strconv.Atoi(args[1]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + end, err := strconv.Atoi(args[2]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if t, ok := db.keys[key]; ok && t != "list" { + c.WriteError(msgWrongType) + return + } + + l := db.listKeys[key] + if len(l) == 0 { + c.WriteLen(0) + return + } + + rs, re := redisRange(len(l), start, end, false) + c.WriteLen(re - rs) + for _, el := range l[rs:re] { + c.WriteBulk(el) + } + }) +} + +// LREM +func (m *Miniredis) cmdLrem(c *server.Peer, cmd string, args []string) { + if len(args) != 3 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + count, err := strconv.Atoi(args[1]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + value := args[2] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if !db.exists(key) { + c.WriteInt(0) + return + } + if db.t(key) != "list" { + c.WriteError(msgWrongType) + return + } + + l := db.listKeys[key] + if count < 0 { + reverseSlice(l) + } + deleted := 0 + newL := []string{} + toDelete := len(l) + if count < 0 { + toDelete = -count + } + if count > 0 { + toDelete = count + } + for _, el := range l { + if el == value { + if toDelete > 0 { + deleted++ + toDelete-- + continue + } + } + newL = append(newL, el) + } + if count < 0 { + reverseSlice(newL) + } + if len(newL) == 0 { + db.del(key, true) + } else { + db.listKeys[key] = newL + db.keyVersion[key]++ + } + + c.WriteInt(deleted) + }) +} + +// LSET +func (m *Miniredis) cmdLset(c *server.Peer, cmd string, args []string) { + if len(args) != 3 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + index, err := strconv.Atoi(args[1]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + value := args[2] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if !db.exists(key) { + c.WriteError(msgKeyNotFound) + return + } + if db.t(key) != "list" { + c.WriteError(msgWrongType) + return + } + + l := db.listKeys[key] + if index < 0 { + index = len(l) + index + } + if index < 0 || index > len(l)-1 { + c.WriteError(msgOutOfRange) + return + } + l[index] = value + db.keyVersion[key]++ + + c.WriteOK() + }) +} + +// LTRIM +func (m *Miniredis) cmdLtrim(c *server.Peer, cmd string, args []string) { + if len(args) != 3 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + start, err := strconv.Atoi(args[1]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + end, err := strconv.Atoi(args[2]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + t, ok := db.keys[key] + if !ok { + c.WriteOK() + return + } + if t != "list" { + c.WriteError(msgWrongType) + return + } + + l := db.listKeys[key] + rs, re := redisRange(len(l), start, end, false) + l = l[rs:re] + if len(l) == 0 { + db.del(key, true) + } else { + db.listKeys[key] = l + db.keyVersion[key]++ + } + c.WriteOK() + }) +} + +// RPOPLPUSH +func (m *Miniredis) cmdRpoplpush(c *server.Peer, cmd string, args []string) { + if len(args) != 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + src, dst := args[0], args[1] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if !db.exists(src) { + c.WriteNull() + return + } + if db.t(src) != "list" || (db.exists(dst) && db.t(dst) != "list") { + c.WriteError(msgWrongType) + return + } + elem := db.listPop(src) + db.listLpush(dst, elem) + c.WriteBulk(elem) + }) +} + +// BRPOPLPUSH +func (m *Miniredis) cmdBrpoplpush(c *server.Peer, cmd string, args []string) { + if len(args) != 3 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + src := args[0] + dst := args[1] + timeout, err := strconv.Atoi(args[2]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidTimeout) + return + } + if timeout < 0 { + setDirty(c) + c.WriteError(msgNegTimeout) + return + } + + blocking( + m, + c, + time.Duration(timeout)*time.Second, + func(c *server.Peer, ctx *connCtx) bool { + db := m.db(ctx.selectedDB) + + if !db.exists(src) { + return false + } + if db.t(src) != "list" || (db.exists(dst) && db.t(dst) != "list") { + c.WriteError(msgWrongType) + return true + } + if len(db.listKeys[src]) == 0 { + return false + } + elem := db.listPop(src) + db.listLpush(dst, elem) + c.WriteBulk(elem) + return true + }, + func(c *server.Peer) { + // timeout + c.WriteNull() + }, + ) +} diff --git a/vendor/github.com/alicebob/miniredis/cmd_scripting.go b/vendor/github.com/alicebob/miniredis/cmd_scripting.go new file mode 100644 index 00000000000..296e61b9c69 --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/cmd_scripting.go @@ -0,0 +1,220 @@ +package miniredis + +import ( + "crypto/sha1" + "encoding/hex" + "fmt" + "io" + "strconv" + "strings" + + luajson "github.com/alicebob/gopher-json" + "github.com/yuin/gopher-lua" + "github.com/yuin/gopher-lua/parse" + + "github.com/alicebob/miniredis/server" +) + +func commandsScripting(m *Miniredis) { + m.srv.Register("EVAL", m.cmdEval) + m.srv.Register("EVALSHA", m.cmdEvalsha) + m.srv.Register("SCRIPT", m.cmdScript) +} + +// Execute lua. Needs to run m.Lock()ed, from within withTx(). +func (m *Miniredis) runLuaScript(c *server.Peer, script string, args []string) { + l := lua.NewState(lua.Options{SkipOpenLibs: true}) + defer l.Close() + + // Taken from the go-lua manual + for _, pair := range []struct { + n string + f lua.LGFunction + }{ + {lua.LoadLibName, lua.OpenPackage}, + {lua.BaseLibName, lua.OpenBase}, + {lua.CoroutineLibName, lua.OpenCoroutine}, + {lua.TabLibName, lua.OpenTable}, + {lua.StringLibName, lua.OpenString}, + {lua.MathLibName, lua.OpenMath}, + } { + if err := l.CallByParam(lua.P{ + Fn: l.NewFunction(pair.f), + NRet: 0, + Protect: true, + }, lua.LString(pair.n)); err != nil { + panic(err) + } + } + + luajson.Preload(l) + requireGlobal(l, "cjson", "json") + + m.Unlock() + conn := m.redigo() + m.Lock() + defer conn.Close() + + // set global variable KEYS + keysTable := l.NewTable() + keysS, args := args[0], args[1:] + keysLen, err := strconv.Atoi(keysS) + if err != nil { + c.WriteError(msgInvalidInt) + return + } + if keysLen < 0 { + c.WriteError(msgNegativeKeysNumber) + return + } + if keysLen > len(args) { + c.WriteError(msgInvalidKeysNumber) + return + } + keys, args := args[:keysLen], args[keysLen:] + for i, k := range keys { + l.RawSet(keysTable, lua.LNumber(i+1), lua.LString(k)) + } + l.SetGlobal("KEYS", keysTable) + + argvTable := l.NewTable() + for i, a := range args { + l.RawSet(argvTable, lua.LNumber(i+1), lua.LString(a)) + } + l.SetGlobal("ARGV", argvTable) + + redisFuncs := mkLuaFuncs(conn) + // Register command handlers + l.Push(l.NewFunction(func(l *lua.LState) int { + mod := l.RegisterModule("redis", redisFuncs).(*lua.LTable) + l.Push(mod) + return 1 + })) + + l.Push(lua.LString("redis")) + l.Call(1, 0) + + m.Unlock() // This runs in a transaction, but can access our db recursively + defer m.Lock() + if err := l.DoString(script); err != nil { + c.WriteError(errLuaParseError(err)) + return + } + + luaToRedis(l, c, l.Get(1)) +} + +func (m *Miniredis) cmdEval(c *server.Peer, cmd string, args []string) { + if len(args) < 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + script, args := args[0], args[1:] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + m.runLuaScript(c, script, args) + }) +} + +func (m *Miniredis) cmdEvalsha(c *server.Peer, cmd string, args []string) { + if len(args) < 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + sha, args := args[0], args[1:] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + script, ok := m.scripts[sha] + if !ok { + c.WriteError(msgNoScriptFound) + return + } + + m.runLuaScript(c, script, args) + }) +} + +func (m *Miniredis) cmdScript(c *server.Peer, cmd string, args []string) { + if len(args) < 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + subcmd, args := args[0], args[1:] + + 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 + } + script := args[0] + + if _, err := parse.Parse(strings.NewReader(script), "user_script"); err != nil { + c.WriteError(errLuaParseError(err)) + return + } + sha := sha1Hex(script) + m.scripts[sha] = script + c.WriteBulk(sha) + + case "exists": + c.WriteLen(len(args)) + for _, arg := range args { + if _, ok := m.scripts[arg]; ok { + c.WriteInt(1) + } else { + c.WriteInt(0) + } + } + + case "flush": + if len(args) != 0 { + c.WriteError(fmt.Sprintf(msgFScriptUsage, "FLUSH")) + return + } + + m.scripts = map[string]string{} + c.WriteOK() + + default: + c.WriteError(fmt.Sprintf(msgFScriptUsage, strings.ToUpper(subcmd))) + } + }) +} + +func sha1Hex(s string) string { + h := sha1.New() + io.WriteString(h, s) + return hex.EncodeToString(h.Sum(nil)) +} + +// requireGlobal imports module modName into the global namespace with the +// identifier id. panics if an error results from the function execution +func requireGlobal(l *lua.LState, id, modName string) { + if err := l.CallByParam(lua.P{ + Fn: l.GetGlobal("require"), + NRet: 1, + Protect: true, + }, lua.LString(modName)); err != nil { + panic(err) + } + mod := l.Get(-1) + l.Pop(1) + + l.SetGlobal(id, mod) +} diff --git a/vendor/github.com/alicebob/miniredis/cmd_server.go b/vendor/github.com/alicebob/miniredis/cmd_server.go new file mode 100644 index 00000000000..c021644cdbb --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/cmd_server.go @@ -0,0 +1,104 @@ +// Commands from https://redis.io/commands#server + +package miniredis + +import ( + "strconv" + "strings" + "time" + + "github.com/alicebob/miniredis/server" +) + +func commandsServer(m *Miniredis) { + m.srv.Register("DBSIZE", m.cmdDbsize) + m.srv.Register("FLUSHALL", m.cmdFlushall) + m.srv.Register("FLUSHDB", m.cmdFlushdb) + m.srv.Register("TIME", m.cmdTime) +} + +// DBSIZE +func (m *Miniredis) cmdDbsize(c *server.Peer, cmd string, args []string) { + if len(args) > 0 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + c.WriteInt(len(db.keys)) + }) +} + +// FLUSHALL +func (m *Miniredis) cmdFlushall(c *server.Peer, cmd string, args []string) { + if len(args) > 0 && strings.ToLower(args[0]) == "async" { + args = args[1:] + } + if len(args) > 0 { + setDirty(c) + c.WriteError(msgSyntaxError) + return + } + + if !m.handleAuth(c) { + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + m.flushAll() + c.WriteOK() + }) +} + +// FLUSHDB +func (m *Miniredis) cmdFlushdb(c *server.Peer, cmd string, args []string) { + if len(args) > 0 && strings.ToLower(args[0]) == "async" { + args = args[1:] + } + if len(args) > 0 { + setDirty(c) + c.WriteError(msgSyntaxError) + return + } + + if !m.handleAuth(c) { + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + m.db(ctx.selectedDB).flush() + c.WriteOK() + }) +} + +// TIME: time values are returned in string format instead of int +func (m *Miniredis) cmdTime(c *server.Peer, cmd string, args []string) { + if len(args) > 0 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + now := m.now + if now.IsZero() { + now = time.Now() + } + nanos := now.UnixNano() + seconds := nanos / 1000000000 + microseconds := (nanos / 1000) % 1000000 + + c.WriteLen(2) + c.WriteBulk(strconv.FormatInt(seconds, 10)) + c.WriteBulk(strconv.FormatInt(microseconds, 10)) + }) +} diff --git a/vendor/github.com/alicebob/miniredis/cmd_set.go b/vendor/github.com/alicebob/miniredis/cmd_set.go new file mode 100644 index 00000000000..2220cf55d7a --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/cmd_set.go @@ -0,0 +1,639 @@ +// Commands from https://redis.io/commands#set + +package miniredis + +import ( + "math/rand" + "strconv" + "strings" + + "github.com/alicebob/miniredis/server" +) + +// commandsSet handles all set value operations. +func commandsSet(m *Miniredis) { + m.srv.Register("SADD", m.cmdSadd) + m.srv.Register("SCARD", m.cmdScard) + m.srv.Register("SDIFF", m.cmdSdiff) + m.srv.Register("SDIFFSTORE", m.cmdSdiffstore) + 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("SMOVE", m.cmdSmove) + m.srv.Register("SPOP", m.cmdSpop) + m.srv.Register("SRANDMEMBER", m.cmdSrandmember) + m.srv.Register("SREM", m.cmdSrem) + m.srv.Register("SUNION", m.cmdSunion) + m.srv.Register("SUNIONSTORE", m.cmdSunionstore) + m.srv.Register("SSCAN", m.cmdSscan) +} + +// SADD +func (m *Miniredis) cmdSadd(c *server.Peer, cmd string, args []string) { + if len(args) < 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key, elems := args[0], args[1:] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if db.exists(key) && db.t(key) != "set" { + c.WriteError(ErrWrongType.Error()) + return + } + + added := db.setAdd(key, elems...) + c.WriteInt(added) + }) +} + +// SCARD +func (m *Miniredis) cmdScard(c *server.Peer, cmd string, args []string) { + if len(args) != 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if !db.exists(key) { + c.WriteInt(0) + return + } + + if db.t(key) != "set" { + c.WriteError(ErrWrongType.Error()) + return + } + + members := db.setMembers(key) + c.WriteInt(len(members)) + }) +} + +// SDIFF +func (m *Miniredis) cmdSdiff(c *server.Peer, cmd string, args []string) { + if len(args) < 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + keys := args + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + set, err := db.setDiff(keys) + if err != nil { + c.WriteError(err.Error()) + return + } + + c.WriteLen(len(set)) + for k := range set { + c.WriteBulk(k) + } + }) +} + +// SDIFFSTORE +func (m *Miniredis) cmdSdiffstore(c *server.Peer, cmd string, args []string) { + if len(args) < 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + dest, keys := args[0], args[1:] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + set, err := db.setDiff(keys) + if err != nil { + c.WriteError(err.Error()) + return + } + + db.del(dest, true) + db.setSet(dest, set) + c.WriteInt(len(set)) + }) +} + +// SINTER +func (m *Miniredis) cmdSinter(c *server.Peer, cmd string, args []string) { + if len(args) < 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + keys := args + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + set, err := db.setInter(keys) + if err != nil { + c.WriteError(err.Error()) + return + } + + c.WriteLen(len(set)) + for k := range set { + c.WriteBulk(k) + } + }) +} + +// SINTERSTORE +func (m *Miniredis) cmdSinterstore(c *server.Peer, cmd string, args []string) { + if len(args) < 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + dest, keys := args[0], args[1:] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + set, err := db.setInter(keys) + if err != nil { + c.WriteError(err.Error()) + return + } + + db.del(dest, true) + db.setSet(dest, set) + c.WriteInt(len(set)) + }) +} + +// SISMEMBER +func (m *Miniredis) cmdSismember(c *server.Peer, cmd string, args []string) { + if len(args) != 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key, value := args[0], args[1] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if !db.exists(key) { + c.WriteInt(0) + return + } + + if db.t(key) != "set" { + c.WriteError(ErrWrongType.Error()) + return + } + + if db.setIsMember(key, value) { + c.WriteInt(1) + return + } + c.WriteInt(0) + }) +} + +// SMEMBERS +func (m *Miniredis) cmdSmembers(c *server.Peer, cmd string, args []string) { + if len(args) != 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if !db.exists(key) { + c.WriteLen(0) + return + } + + if db.t(key) != "set" { + c.WriteError(ErrWrongType.Error()) + return + } + + members := db.setMembers(key) + + c.WriteLen(len(members)) + for _, elem := range members { + c.WriteBulk(elem) + } + }) +} + +// SMOVE +func (m *Miniredis) cmdSmove(c *server.Peer, cmd string, args []string) { + if len(args) != 3 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + src, dst, member := args[0], args[1], args[2] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if !db.exists(src) { + c.WriteInt(0) + return + } + + if db.t(src) != "set" { + c.WriteError(ErrWrongType.Error()) + return + } + + if db.exists(dst) && db.t(dst) != "set" { + c.WriteError(ErrWrongType.Error()) + return + } + + if !db.setIsMember(src, member) { + c.WriteInt(0) + return + } + db.setRem(src, member) + db.setAdd(dst, member) + c.WriteInt(1) + }) +} + +// SPOP +func (m *Miniredis) cmdSpop(c *server.Peer, cmd string, args []string) { + if len(args) == 0 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key, args := args[0], args[1:] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + withCount := false + count := 1 + if len(args) > 0 { + v, err := strconv.Atoi(args[0]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + count = v + withCount = true + args = args[1:] + } + if len(args) > 0 { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + + if !db.exists(key) { + if !withCount { + c.WriteNull() + return + } + c.WriteLen(0) + return + } + + if db.t(key) != "set" { + c.WriteError(ErrWrongType.Error()) + return + } + + var deleted []string + for i := 0; i < count; i++ { + members := db.setMembers(key) + if len(members) == 0 { + break + } + member := members[rand.Intn(len(members))] + db.setRem(key, member) + deleted = append(deleted, member) + } + // without `count` return a single value... + if !withCount { + if len(deleted) == 0 { + c.WriteNull() + return + } + c.WriteBulk(deleted[0]) + return + } + // ... with `count` return a list + c.WriteLen(len(deleted)) + for _, v := range deleted { + c.WriteBulk(v) + } + }) +} + +// SRANDMEMBER +func (m *Miniredis) cmdSrandmember(c *server.Peer, cmd string, args []string) { + if len(args) < 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if len(args) > 2 { + setDirty(c) + c.WriteError(msgSyntaxError) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + count := 0 + withCount := false + if len(args) == 2 { + var err error + count, err = strconv.Atoi(args[1]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + withCount = true + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if !db.exists(key) { + c.WriteNull() + return + } + + if db.t(key) != "set" { + c.WriteError(ErrWrongType.Error()) + return + } + + members := db.setMembers(key) + if count < 0 { + // Non-unique elements is allowed with negative count. + c.WriteLen(-count) + for count != 0 { + member := members[rand.Intn(len(members))] + c.WriteBulk(member) + count++ + } + return + } + + // Must be unique elements. + shuffle(members) + if count > len(members) { + count = len(members) + } + if !withCount { + c.WriteBulk(members[0]) + return + } + c.WriteLen(count) + for i := range make([]struct{}, count) { + c.WriteBulk(members[i]) + } + }) +} + +// SREM +func (m *Miniredis) cmdSrem(c *server.Peer, cmd string, args []string) { + if len(args) < 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key, fields := args[0], args[1:] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if !db.exists(key) { + c.WriteInt(0) + return + } + + if db.t(key) != "set" { + c.WriteError(ErrWrongType.Error()) + return + } + + c.WriteInt(db.setRem(key, fields...)) + }) +} + +// SUNION +func (m *Miniredis) cmdSunion(c *server.Peer, cmd string, args []string) { + if len(args) < 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + keys := args + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + set, err := db.setUnion(keys) + if err != nil { + c.WriteError(err.Error()) + return + } + + c.WriteLen(len(set)) + for k := range set { + c.WriteBulk(k) + } + }) +} + +// SUNIONSTORE +func (m *Miniredis) cmdSunionstore(c *server.Peer, cmd string, args []string) { + if len(args) < 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + dest, keys := args[0], args[1:] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + set, err := db.setUnion(keys) + if err != nil { + c.WriteError(err.Error()) + return + } + + db.del(dest, true) + db.setSet(dest, set) + c.WriteInt(len(set)) + }) +} + +// SSCAN +func (m *Miniredis) cmdSscan(c *server.Peer, cmd string, args []string) { + if len(args) < 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + cursor, err := strconv.Atoi(args[1]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidCursor) + 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 { + setDirty(c) + c.WriteError(msgSyntaxError) + return + } + _, err := strconv.Atoi(args[1]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + // We do nothing with count. + args = args[2:] + continue + } + if strings.ToLower(args[0]) == "match" { + if len(args) < 2 { + setDirty(c) + c.WriteError(msgSyntaxError) + return + } + withMatch = true + match = args[1] + args = args[2:] + continue + } + setDirty(c) + c.WriteError(msgSyntaxError) + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + // 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 + } + if db.exists(key) && db.t(key) != "set" { + c.WriteError(ErrWrongType.Error()) + return + } + + members := db.setMembers(key) + if withMatch { + members = matchKeys(members, match) + } + + c.WriteLen(2) + c.WriteBulk("0") // no next cursor + c.WriteLen(len(members)) + for _, k := range members { + c.WriteBulk(k) + } + }) +} + +// shuffle shuffles a string. Kinda. +func shuffle(m []string) { + for _ = range m { + i := rand.Intn(len(m)) + j := rand.Intn(len(m)) + m[i], m[j] = m[j], m[i] + } +} diff --git a/vendor/github.com/alicebob/miniredis/cmd_sorted_set.go b/vendor/github.com/alicebob/miniredis/cmd_sorted_set.go new file mode 100644 index 00000000000..5252b015f09 --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/cmd_sorted_set.go @@ -0,0 +1,1399 @@ +// Commands from https://redis.io/commands#sorted_set + +package miniredis + +import ( + "errors" + "sort" + "strconv" + "strings" + + "github.com/alicebob/miniredis/server" +) + +var ( + errInvalidRangeItem = errors.New(msgInvalidRangeItem) +) + +// commandsSortedSet handles all sorted set operations. +func commandsSortedSet(m *Miniredis) { + m.srv.Register("ZADD", m.cmdZadd) + 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("ZLEXCOUNT", m.cmdZlexcount) + m.srv.Register("ZRANGE", m.makeCmdZrange(false)) + m.srv.Register("ZRANGEBYLEX", m.makeCmdZrangebylex(false)) + m.srv.Register("ZRANGEBYSCORE", m.makeCmdZrangebyscore(false)) + m.srv.Register("ZRANK", m.makeCmdZrank(false)) + m.srv.Register("ZREM", m.cmdZrem) + m.srv.Register("ZREMRANGEBYLEX", m.cmdZremrangebylex) + m.srv.Register("ZREMRANGEBYRANK", m.cmdZremrangebyrank) + m.srv.Register("ZREMRANGEBYSCORE", m.cmdZremrangebyscore) + m.srv.Register("ZREVRANGE", m.makeCmdZrange(true)) + m.srv.Register("ZREVRANGEBYLEX", m.makeCmdZrangebylex(true)) + m.srv.Register("ZREVRANGEBYSCORE", m.makeCmdZrangebyscore(true)) + m.srv.Register("ZREVRANK", m.makeCmdZrank(true)) + m.srv.Register("ZSCORE", m.cmdZscore) + m.srv.Register("ZUNIONSTORE", m.cmdZunionstore) + m.srv.Register("ZSCAN", m.cmdZscan) + m.srv.Register("ZPOPMAX", m.cmdZpopmax(true)) + m.srv.Register("ZPOPMIN", m.cmdZpopmax(false)) +} + +// ZADD +func (m *Miniredis) cmdZadd(c *server.Peer, cmd string, args []string) { + if len(args) < 3 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key, args := args[0], args[1:] + var ( + nx = false + xx = false + ch = false + incr = false + elems = map[string]float64{} + ) + +outer: + for len(args) > 0 { + switch strings.ToUpper(args[0]) { + case "NX": + nx = true + args = args[1:] + continue + case "XX": + xx = true + args = args[1:] + continue + case "CH": + ch = true + args = args[1:] + continue + case "INCR": + incr = true + args = args[1:] + continue + default: + break outer + } + } + + if len(args) == 0 || len(args)%2 != 0 { + setDirty(c) + c.WriteError(msgSyntaxError) + return + } + for len(args) > 0 { + score, err := strconv.ParseFloat(args[0], 64) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidFloat) + return + } + elems[args[1]] = score + args = args[2:] + } + + if xx && nx { + setDirty(c) + c.WriteError(msgXXandNX) + return + } + + if incr && len(elems) > 1 { + setDirty(c) + c.WriteError(msgSingleElementPair) + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if db.exists(key) && db.t(key) != "zset" { + c.WriteError(ErrWrongType.Error()) + return + } + + if incr { + for member, delta := range elems { + if nx && db.ssetExists(key, member) { + c.WriteNull() + return + } + if xx && !db.ssetExists(key, member) { + c.WriteNull() + return + } + newScore := db.ssetIncrby(key, member, delta) + c.WriteBulk(formatFloat(newScore)) + } + return + } + + res := 0 + for member, score := range elems { + if nx && db.ssetExists(key, member) { + continue + } + if xx && !db.ssetExists(key, member) { + continue + } + old := db.ssetScore(key, member) + if db.ssetAdd(key, score, member) { + res++ + } else { + if ch && old != score { + // if 'CH' is specified, only count changed keys + res++ + } + } + } + c.WriteInt(res) + }) +} + +// ZCARD +func (m *Miniredis) cmdZcard(c *server.Peer, cmd string, args []string) { + if len(args) != 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if !db.exists(key) { + c.WriteInt(0) + return + } + + if db.t(key) != "zset" { + c.WriteError(ErrWrongType.Error()) + return + } + + c.WriteInt(db.ssetCard(key)) + }) +} + +// ZCOUNT +func (m *Miniredis) cmdZcount(c *server.Peer, cmd string, args []string) { + if len(args) != 3 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + min, minIncl, err := parseFloatRange(args[1]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidMinMax) + return + } + max, maxIncl, err := parseFloatRange(args[2]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidMinMax) + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if !db.exists(key) { + c.WriteInt(0) + return + } + + if db.t(key) != "zset" { + c.WriteError(ErrWrongType.Error()) + return + } + + members := db.ssetElements(key) + members = withSSRange(members, min, minIncl, max, maxIncl) + c.WriteInt(len(members)) + }) +} + +// ZINCRBY +func (m *Miniredis) cmdZincrby(c *server.Peer, cmd string, args []string) { + if len(args) != 3 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + delta, err := strconv.ParseFloat(args[1], 64) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidFloat) + return + } + 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" { + c.WriteError(msgWrongType) + return + } + newScore := db.ssetIncrby(key, member, delta) + c.WriteBulk(formatFloat(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 + } + + 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:] + + 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 { + setDirty(c) + c.WriteError("ERR weight value is not a float") + 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": + 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 + } + if db.t(key) != "zset" { + c.WriteError(msgWrongType) + return + } + for _, el := range db.ssetElements(key) { + score := el.score + if withWeights { + score *= weights[i] + } + counts[el.member]++ + old, ok := sset[el.member] + if !ok { + sset[el.member] = score + continue + } + switch aggregate { + default: + panic("Invalid aggregate") + case "sum": + sset[el.member] += score + case "min": + if score < old { + sset[el.member] = score + } + case "max": + if score > old { + sset[el.member] = score + } + } + } + } + for key, count := range counts { + if count != numKeys { + delete(sset, key) + } + } + db.ssetSet(destination, sset) + c.WriteInt(len(sset)) + }) +} + +// ZLEXCOUNT +func (m *Miniredis) cmdZlexcount(c *server.Peer, cmd string, args []string) { + if len(args) != 3 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + min, minIncl, err := parseLexrange(args[1]) + if err != nil { + setDirty(c) + c.WriteError(err.Error()) + return + } + max, maxIncl, err := parseLexrange(args[2]) + if err != nil { + setDirty(c) + c.WriteError(err.Error()) + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if !db.exists(key) { + c.WriteInt(0) + return + } + + if db.t(key) != "zset" { + c.WriteError(ErrWrongType.Error()) + return + } + + members := db.ssetMembers(key) + // Just key sort. If scores are not the same we don't care. + sort.Strings(members) + members = withLexRange(members, min, minIncl, max, maxIncl) + + c.WriteInt(len(members)) + }) +} + +// ZRANGE and ZREVRANGE +func (m *Miniredis) makeCmdZrange(reverse bool) server.Cmd { + return func(c *server.Peer, cmd string, args []string) { + if len(args) < 3 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + start, err := strconv.Atoi(args[1]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + end, err := strconv.Atoi(args[2]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + + withScores := false + if len(args) > 4 { + c.WriteError(msgSyntaxError) + return + } + if len(args) == 4 { + if strings.ToLower(args[3]) != "withscores" { + setDirty(c) + c.WriteError(msgSyntaxError) + return + } + withScores = true + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if !db.exists(key) { + c.WriteLen(0) + return + } + + if db.t(key) != "zset" { + c.WriteError(ErrWrongType.Error()) + return + } + + members := db.ssetMembers(key) + if reverse { + reverseSlice(members) + } + rs, re := redisRange(len(members), start, end, false) + if withScores { + c.WriteLen((re - rs) * 2) + } else { + c.WriteLen(re - rs) + } + for _, el := range members[rs:re] { + c.WriteBulk(el) + if withScores { + c.WriteBulk(formatFloat(db.ssetScore(key, el))) + } + } + }) + } +} + +// ZRANGEBYLEX and ZREVRANGEBYLEX +func (m *Miniredis) makeCmdZrangebylex(reverse bool) server.Cmd { + return func(c *server.Peer, cmd string, args []string) { + if len(args) < 3 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + min, minIncl, err := parseLexrange(args[1]) + if err != nil { + setDirty(c) + c.WriteError(err.Error()) + return + } + max, maxIncl, err := parseLexrange(args[2]) + if err != nil { + setDirty(c) + c.WriteError(err.Error()) + return + } + args = args[3:] + + withLimit := false + limitStart := 0 + limitEnd := 0 + for len(args) > 0 { + if strings.ToLower(args[0]) == "limit" { + withLimit = true + args = args[1:] + if len(args) < 2 { + c.WriteError(msgSyntaxError) + return + } + limitStart, err = strconv.Atoi(args[0]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + limitEnd, err = strconv.Atoi(args[1]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + args = args[2:] + continue + } + // Syntax error + setDirty(c) + c.WriteError(msgSyntaxError) + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if !db.exists(key) { + c.WriteLen(0) + return + } + + if db.t(key) != "zset" { + c.WriteError(ErrWrongType.Error()) + return + } + + members := db.ssetMembers(key) + // Just key sort. If scores are not the same we don't care. + sort.Strings(members) + if reverse { + min, max = max, min + minIncl, maxIncl = maxIncl, minIncl + } + members = withLexRange(members, min, minIncl, max, maxIncl) + if reverse { + reverseSlice(members) + } + + // Apply LIMIT ranges. That's . Unlike RANGE. + if withLimit { + if limitStart < 0 { + members = nil + } else { + if limitStart < len(members) { + members = members[limitStart:] + } else { + // out of range + members = nil + } + if limitEnd >= 0 { + if len(members) > limitEnd { + members = members[:limitEnd] + } + } + } + } + + c.WriteLen(len(members)) + for _, el := range members { + c.WriteBulk(el) + } + }) + } +} + +// ZRANGEBYSCORE and ZREVRANGEBYSCORE +func (m *Miniredis) makeCmdZrangebyscore(reverse bool) server.Cmd { + return func(c *server.Peer, cmd string, args []string) { + if len(args) < 3 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + min, minIncl, err := parseFloatRange(args[1]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidMinMax) + return + } + max, maxIncl, err := parseFloatRange(args[2]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidMinMax) + return + } + args = args[3:] + + withScores := false + withLimit := false + limitStart := 0 + limitEnd := 0 + for len(args) > 0 { + if strings.ToLower(args[0]) == "limit" { + withLimit = true + args = args[1:] + if len(args) < 2 { + c.WriteError(msgSyntaxError) + return + } + limitStart, err = strconv.Atoi(args[0]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + limitEnd, err = strconv.Atoi(args[1]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + args = args[2:] + continue + } + if strings.ToLower(args[0]) == "withscores" { + withScores = true + args = args[1:] + continue + } + setDirty(c) + c.WriteError(msgSyntaxError) + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if !db.exists(key) { + c.WriteLen(0) + return + } + + if db.t(key) != "zset" { + c.WriteError(ErrWrongType.Error()) + return + } + + members := db.ssetElements(key) + if reverse { + min, max = max, min + minIncl, maxIncl = maxIncl, minIncl + } + members = withSSRange(members, min, minIncl, max, maxIncl) + if reverse { + reverseElems(members) + } + + // Apply LIMIT ranges. That's . Unlike RANGE. + if withLimit { + if limitStart < 0 { + members = ssElems{} + } else { + if limitStart < len(members) { + members = members[limitStart:] + } else { + // out of range + members = ssElems{} + } + if limitEnd >= 0 { + if len(members) > limitEnd { + members = members[:limitEnd] + } + } + } + } + + if withScores { + c.WriteLen(len(members) * 2) + } else { + c.WriteLen(len(members)) + } + for _, el := range members { + c.WriteBulk(el.member) + if withScores { + c.WriteBulk(formatFloat(el.score)) + } + } + }) + } +} + +// ZRANK and ZREVRANK +func (m *Miniredis) makeCmdZrank(reverse bool) server.Cmd { + return func(c *server.Peer, cmd string, args []string) { + if len(args) != 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key, member := args[0], args[1] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if !db.exists(key) { + c.WriteNull() + return + } + + if db.t(key) != "zset" { + c.WriteError(ErrWrongType.Error()) + return + } + + direction := asc + if reverse { + direction = desc + } + rank, ok := db.ssetRank(key, member, direction) + if !ok { + c.WriteNull() + return + } + c.WriteInt(rank) + }) + } +} + +// ZREM +func (m *Miniredis) cmdZrem(c *server.Peer, cmd string, args []string) { + if len(args) < 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + 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.WriteInt(0) + return + } + + if db.t(key) != "zset" { + c.WriteError(ErrWrongType.Error()) + return + } + + deleted := 0 + for _, member := range members { + if db.ssetRem(key, member) { + deleted++ + } + } + c.WriteInt(deleted) + }) +} + +// ZREMRANGEBYLEX +func (m *Miniredis) cmdZremrangebylex(c *server.Peer, cmd string, args []string) { + if len(args) != 3 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + min, minIncl, err := parseLexrange(args[1]) + if err != nil { + setDirty(c) + c.WriteError(err.Error()) + return + } + max, maxIncl, err := parseLexrange(args[2]) + if err != nil { + setDirty(c) + c.WriteError(err.Error()) + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if !db.exists(key) { + c.WriteInt(0) + return + } + + if db.t(key) != "zset" { + c.WriteError(ErrWrongType.Error()) + return + } + + members := db.ssetMembers(key) + // Just key sort. If scores are not the same we don't care. + sort.Strings(members) + members = withLexRange(members, min, minIncl, max, maxIncl) + + for _, el := range members { + db.ssetRem(key, el) + } + c.WriteInt(len(members)) + }) +} + +// ZREMRANGEBYRANK +func (m *Miniredis) cmdZremrangebyrank(c *server.Peer, cmd string, args []string) { + if len(args) != 3 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + start, err := strconv.Atoi(args[1]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + end, err := strconv.Atoi(args[2]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if !db.exists(key) { + c.WriteInt(0) + return + } + + if db.t(key) != "zset" { + c.WriteError(ErrWrongType.Error()) + return + } + + members := db.ssetMembers(key) + rs, re := redisRange(len(members), start, end, false) + for _, el := range members[rs:re] { + db.ssetRem(key, el) + } + c.WriteInt(re - rs) + }) +} + +// ZREMRANGEBYSCORE +func (m *Miniredis) cmdZremrangebyscore(c *server.Peer, cmd string, args []string) { + if len(args) != 3 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + min, minIncl, err := parseFloatRange(args[1]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidMinMax) + return + } + max, maxIncl, err := parseFloatRange(args[2]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidMinMax) + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if !db.exists(key) { + c.WriteInt(0) + return + } + + if db.t(key) != "zset" { + c.WriteError(ErrWrongType.Error()) + return + } + + members := db.ssetElements(key) + members = withSSRange(members, min, minIncl, max, maxIncl) + + for _, el := range members { + db.ssetRem(key, el.member) + } + c.WriteInt(len(members)) + }) +} + +// ZSCORE +func (m *Miniredis) cmdZscore(c *server.Peer, cmd string, args []string) { + if len(args) != 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key, member := args[0], args[1] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if !db.exists(key) { + c.WriteNull() + return + } + + if db.t(key) != "zset" { + c.WriteError(ErrWrongType.Error()) + return + } + + if !db.ssetExists(key, member) { + c.WriteNull() + return + } + + c.WriteBulk(formatFloat(db.ssetScore(key, member))) + }) +} + +// parseFloatRange handles ZRANGEBYSCORE floats. They are inclusive unless the +// string starts with '(' +func parseFloatRange(s string) (float64, bool, error) { + if len(s) == 0 { + return 0, false, nil + } + inclusive := true + if s[0] == '(' { + s = s[1:] + inclusive = false + } + f, err := strconv.ParseFloat(s, 64) + return f, inclusive, err +} + +// parseLexrange handles ZRANGEBYLEX ranges. They start with '[', '(', or are +// '+' or '-'. +// Returns range, inclusive, error. +// On '+' or '-' that's just returned. +func parseLexrange(s string) (string, bool, error) { + if len(s) == 0 { + return "", false, errInvalidRangeItem + } + if s == "+" || s == "-" { + return s, false, nil + } + switch s[0] { + case '(': + return s[1:], false, nil + case '[': + return s[1:], true, nil + default: + return "", false, errInvalidRangeItem + } +} + +// withSSRange limits a list of sorted set elements by the ZRANGEBYSCORE range +// logic. +func withSSRange(members ssElems, min float64, minIncl bool, max float64, maxIncl bool) ssElems { + gt := func(a, b float64) bool { return a > b } + gteq := func(a, b float64) bool { return a >= b } + + mincmp := gt + if minIncl { + mincmp = gteq + } + for i, m := range members { + if mincmp(m.score, min) { + members = members[i:] + goto checkmax + } + } + // all elements were smaller + return nil + +checkmax: + maxcmp := gteq + if maxIncl { + maxcmp = gt + } + for i, m := range members { + if maxcmp(m.score, max) { + members = members[:i] + break + } + } + + return members +} + +// withLexRange limits a list of sorted set elements. +func withLexRange(members []string, min string, minIncl bool, max string, maxIncl bool) []string { + if max == "-" || min == "+" { + return nil + } + if min != "-" { + if minIncl { + for i, m := range members { + if m >= min { + members = members[i:] + break + } + } + } else { + // Excluding min + for i, m := range members { + if m > min { + members = members[i:] + break + } + } + } + } + if max != "+" { + if maxIncl { + for i, m := range members { + if m > max { + members = members[:i] + break + } + } + } else { + // Excluding max + for i, m := range members { + if m >= max { + members = members[:i] + break + } + } + } + } + return members +} + +// ZUNIONSTORE +func (m *Miniredis) cmdZunionstore(c *server.Peer, cmd string, args []string) { + if len(args) < 3 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + 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:] + + 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 { + setDirty(c) + c.WriteError("ERR weight value is not a float") + 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 { + default: + setDirty(c) + c.WriteError(msgSyntaxError) + return + case "sum", "min", "max": + } + args = args[2:] + default: + setDirty(c) + c.WriteError(msgSyntaxError) + return + } + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + deleteDest := true + for _, key := range keys { + if destination == key { + deleteDest = false + } + } + if deleteDest { + db.del(destination, true) + } + + sset := sortedSet{} + for i, key := range keys { + if !db.exists(key) { + continue + } + if db.t(key) != "zset" { + c.WriteError(msgWrongType) + return + } + for _, el := range db.ssetElements(key) { + score := el.score + if withWeights { + score *= weights[i] + } + old, ok := sset[el.member] + if !ok { + sset[el.member] = score + continue + } + switch aggregate { + default: + panic("Invalid aggregate") + case "sum": + sset[el.member] += score + case "min": + if score < old { + sset[el.member] = score + } + case "max": + if score > old { + sset[el.member] = score + } + } + } + } + db.ssetSet(destination, sset) + c.WriteInt(sset.card()) + }) +} + +// ZSCAN +func (m *Miniredis) cmdZscan(c *server.Peer, cmd string, args []string) { + if len(args) < 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + cursor, err := strconv.Atoi(args[1]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidCursor) + 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 { + setDirty(c) + c.WriteError(msgSyntaxError) + return + } + _, err := strconv.Atoi(args[1]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + // We do nothing with count. + args = args[2:] + continue + } + if strings.ToLower(args[0]) == "match" { + if len(args) < 2 { + setDirty(c) + c.WriteError(msgSyntaxError) + return + } + withMatch = true + match = args[1] + args = args[2:] + continue + } + setDirty(c) + c.WriteError(msgSyntaxError) + 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 + } + if db.exists(key) && db.t(key) != "zset" { + c.WriteError(ErrWrongType.Error()) + return + } + + members := db.ssetMembers(key) + if withMatch { + members = matchKeys(members, match) + } + + c.WriteLen(2) + c.WriteBulk("0") // no next cursor + // HSCAN gives key, values. + c.WriteLen(len(members) * 2) + for _, k := range members { + c.WriteBulk(k) + c.WriteBulk(formatFloat(db.ssetScore(key, k))) + } + }) +} + +// ZPOPMAX and ZPOPMIN +func (m *Miniredis) cmdZpopmax(reverse bool) 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 + } + + key := args[0] + count := 1 + var err error + if len(args) > 1 { + count, err = strconv.Atoi(args[1]) + + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + } + + withScores := true + if len(args) > 2 { + c.WriteError(msgSyntaxError) + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if !db.exists(key) { + c.WriteLen(0) + return + } + + if db.t(key) != "zset" { + c.WriteError(ErrWrongType.Error()) + return + } + + members := db.ssetMembers(key) + if reverse { + reverseSlice(members) + } + rs, re := redisRange(len(members), 0, count-1, false) + if withScores { + c.WriteLen((re - rs) * 2) + } else { + c.WriteLen(re - rs) + } + for _, el := range members[rs:re] { + c.WriteBulk(el) + if withScores { + c.WriteBulk(formatFloat(db.ssetScore(key, el))) + } + db.ssetRem(key, el) + } + }) + } +} diff --git a/vendor/github.com/alicebob/miniredis/cmd_string.go b/vendor/github.com/alicebob/miniredis/cmd_string.go new file mode 100644 index 00000000000..930da99210f --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/cmd_string.go @@ -0,0 +1,1075 @@ +// Commands from https://redis.io/commands#string + +package miniredis + +import ( + "strconv" + "strings" + "time" + + "github.com/alicebob/miniredis/server" +) + +// commandsString handles all string value operations. +func commandsString(m *Miniredis) { + m.srv.Register("APPEND", m.cmdAppend) + m.srv.Register("BITCOUNT", m.cmdBitcount) + m.srv.Register("BITOP", m.cmdBitop) + m.srv.Register("BITPOS", m.cmdBitpos) + m.srv.Register("DECRBY", m.cmdDecrby) + m.srv.Register("DECR", m.cmdDecr) + m.srv.Register("GETBIT", m.cmdGetbit) + m.srv.Register("GET", m.cmdGet) + m.srv.Register("GETRANGE", m.cmdGetrange) + m.srv.Register("GETSET", m.cmdGetset) + m.srv.Register("INCRBYFLOAT", m.cmdIncrbyfloat) + m.srv.Register("INCRBY", m.cmdIncrby) + m.srv.Register("INCR", m.cmdIncr) + m.srv.Register("MGET", m.cmdMget) + m.srv.Register("MSET", m.cmdMset) + m.srv.Register("MSETNX", m.cmdMsetnx) + m.srv.Register("PSETEX", m.cmdPsetex) + m.srv.Register("SETBIT", m.cmdSetbit) + m.srv.Register("SETEX", m.cmdSetex) + m.srv.Register("SET", m.cmdSet) + m.srv.Register("SETNX", m.cmdSetnx) + m.srv.Register("SETRANGE", m.cmdSetrange) + m.srv.Register("STRLEN", m.cmdStrlen) +} + +// SET +func (m *Miniredis) cmdSet(c *server.Peer, cmd string, args []string) { + if len(args) < 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + var ( + nx = false // set iff not exists + xx = false // set iff exists + ttl time.Duration + ) + + key, value, args := args[0], args[1], args[2:] + for len(args) > 0 { + timeUnit := time.Second + switch strings.ToUpper(args[0]) { + case "NX": + nx = true + args = args[1:] + continue + case "XX": + xx = true + args = args[1:] + continue + case "PX": + timeUnit = time.Millisecond + fallthrough + case "EX": + if len(args) < 2 { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + expire, err := strconv.Atoi(args[1]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + ttl = time.Duration(expire) * timeUnit + if ttl <= 0 { + setDirty(c) + c.WriteError(msgInvalidSETime) + return + } + + args = args[2:] + continue + default: + setDirty(c) + c.WriteError(msgSyntaxError) + return + } + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if nx { + if db.exists(key) { + c.WriteNull() + return + } + } + if xx { + if !db.exists(key) { + c.WriteNull() + return + } + } + + db.del(key, true) // be sure to remove existing values of other type keys. + // a vanilla SET clears the expire + db.stringSet(key, value) + if ttl != 0 { + db.ttl[key] = ttl + } + c.WriteOK() + }) +} + +// SETEX +func (m *Miniredis) cmdSetex(c *server.Peer, cmd string, args []string) { + if len(args) != 3 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + ttl, err := strconv.Atoi(args[1]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + if ttl <= 0 { + setDirty(c) + c.WriteError(msgInvalidSETEXTime) + return + } + 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.Second + c.WriteOK() + }) +} + +// PSETEX +func (m *Miniredis) cmdPsetex(c *server.Peer, cmd string, args []string) { + if len(args) != 3 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + ttl, err := strconv.Atoi(args[1]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + if ttl <= 0 { + setDirty(c) + c.WriteError(msgInvalidPSETEXTime) + return + } + 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 + c.WriteOK() + }) +} + +// SETNX +func (m *Miniredis) cmdSetnx(c *server.Peer, cmd string, args []string) { + if len(args) != 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key, value := args[0], args[1] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if _, ok := db.keys[key]; ok { + c.WriteInt(0) + return + } + + db.stringSet(key, value) + c.WriteInt(1) + }) +} + +// MSET +func (m *Miniredis) cmdMset(c *server.Peer, cmd string, args []string) { + if len(args) < 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + if len(args)%2 != 0 { + setDirty(c) + // non-default error message + c.WriteError("ERR wrong number of arguments for MSET") + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + for len(args) > 0 { + key, value := args[0], args[1] + args = args[2:] + + db.del(key, true) // clear TTL + db.stringSet(key, value) + } + c.WriteOK() + }) +} + +// MSETNX +func (m *Miniredis) cmdMsetnx(c *server.Peer, cmd string, args []string) { + if len(args) < 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + if len(args)%2 != 0 { + setDirty(c) + // non-default error message (yes, with 'MSET'). + c.WriteError("ERR wrong number of arguments for MSET") + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + keys := map[string]string{} + existing := false + for len(args) > 0 { + key := args[0] + value := args[1] + args = args[2:] + keys[key] = value + if _, ok := db.keys[key]; ok { + existing = true + } + } + + res := 0 + if !existing { + res = 1 + for k, v := range keys { + // Nothing to delete. That's the whole point. + db.stringSet(k, v) + } + } + c.WriteInt(res) + }) +} + +// GET +func (m *Miniredis) cmdGet(c *server.Peer, cmd string, args []string) { + if len(args) != 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if !db.exists(key) { + c.WriteNull() + return + } + if db.t(key) != "string" { + c.WriteError(msgWrongType) + return + } + + c.WriteBulk(db.stringGet(key)) + }) +} + +// GETSET +func (m *Miniredis) cmdGetset(c *server.Peer, cmd string, args []string) { + if len(args) != 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key, value := args[0], args[1] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if t, ok := db.keys[key]; ok && t != "string" { + c.WriteError(msgWrongType) + return + } + + old, ok := db.stringKeys[key] + db.stringSet(key, value) + // a GETSET clears the ttl + delete(db.ttl, key) + + if !ok { + c.WriteNull() + return + } + c.WriteBulk(old) + }) +} + +// MGET +func (m *Miniredis) cmdMget(c *server.Peer, cmd string, args []string) { + if len(args) < 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + c.WriteLen(len(args)) + for _, k := range args { + if t, ok := db.keys[k]; !ok || t != "string" { + c.WriteNull() + continue + } + v, ok := db.stringKeys[k] + if !ok { + // Should not happen, we just checked keys[] + c.WriteNull() + continue + } + c.WriteBulk(v) + } + }) +} + +// INCR +func (m *Miniredis) cmdIncr(c *server.Peer, cmd string, args []string) { + if len(args) != 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + key := args[0] + if t, ok := db.keys[key]; ok && t != "string" { + c.WriteError(msgWrongType) + return + } + v, err := db.stringIncr(key, +1) + if err != nil { + c.WriteError(err.Error()) + return + } + // Don't touch TTL + c.WriteInt(v) + }) +} + +// INCRBY +func (m *Miniredis) cmdIncrby(c *server.Peer, cmd string, args []string) { + if len(args) != 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + delta, err := strconv.Atoi(args[1]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if t, ok := db.keys[key]; ok && t != "string" { + c.WriteError(msgWrongType) + return + } + + v, err := db.stringIncr(key, delta) + if err != nil { + c.WriteError(err.Error()) + return + } + // Don't touch TTL + c.WriteInt(v) + }) +} + +// INCRBYFLOAT +func (m *Miniredis) cmdIncrbyfloat(c *server.Peer, cmd string, args []string) { + if len(args) != 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + delta, err := strconv.ParseFloat(args[1], 64) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidFloat) + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if t, ok := db.keys[key]; ok && t != "string" { + c.WriteError(msgWrongType) + return + } + + v, err := db.stringIncrfloat(key, delta) + if err != nil { + c.WriteError(err.Error()) + return + } + // Don't touch TTL + c.WriteBulk(formatFloat(v)) + }) +} + +// DECR +func (m *Miniredis) cmdDecr(c *server.Peer, cmd string, args []string) { + if len(args) != 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + key := args[0] + if t, ok := db.keys[key]; ok && t != "string" { + c.WriteError(msgWrongType) + return + } + v, err := db.stringIncr(key, -1) + if err != nil { + c.WriteError(err.Error()) + return + } + // Don't touch TTL + c.WriteInt(v) + }) +} + +// DECRBY +func (m *Miniredis) cmdDecrby(c *server.Peer, cmd string, args []string) { + if len(args) != 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + delta, err := strconv.Atoi(args[1]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if t, ok := db.keys[key]; ok && t != "string" { + c.WriteError(msgWrongType) + return + } + + v, err := db.stringIncr(key, -delta) + if err != nil { + c.WriteError(err.Error()) + return + } + // Don't touch TTL + c.WriteInt(v) + }) +} + +// STRLEN +func (m *Miniredis) cmdStrlen(c *server.Peer, cmd string, args []string) { + if len(args) != 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if t, ok := db.keys[key]; ok && t != "string" { + c.WriteError(msgWrongType) + return + } + + c.WriteInt(len(db.stringKeys[key])) + }) +} + +// APPEND +func (m *Miniredis) cmdAppend(c *server.Peer, cmd string, args []string) { + if len(args) != 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key, value := args[0], args[1] + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if t, ok := db.keys[key]; ok && t != "string" { + c.WriteError(msgWrongType) + return + } + + newValue := db.stringKeys[key] + value + db.stringSet(key, newValue) + + c.WriteInt(len(newValue)) + }) +} + +// GETRANGE +func (m *Miniredis) cmdGetrange(c *server.Peer, cmd string, args []string) { + if len(args) != 3 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + start, err := strconv.Atoi(args[1]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + end, err := strconv.Atoi(args[2]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if t, ok := db.keys[key]; ok && t != "string" { + c.WriteError(msgWrongType) + return + } + + v := db.stringKeys[key] + c.WriteBulk(withRange(v, start, end)) + }) +} + +// SETRANGE +func (m *Miniredis) cmdSetrange(c *server.Peer, cmd string, args []string) { + if len(args) != 3 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + pos, err := strconv.Atoi(args[1]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + if pos < 0 { + setDirty(c) + c.WriteError("ERR offset is out of range") + return + } + 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" { + c.WriteError(msgWrongType) + return + } + + v := []byte(db.stringKeys[key]) + if len(v) < pos+len(subst) { + newV := make([]byte, pos+len(subst)) + copy(newV, v) + v = newV + } + copy(v[pos:pos+len(subst)], subst) + db.stringSet(key, string(v)) + c.WriteInt(len(v)) + }) +} + +// BITCOUNT +func (m *Miniredis) cmdBitcount(c *server.Peer, cmd string, args []string) { + if len(args) < 1 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + var ( + useRange = false + start, end = 0, 0 + key = args[0] + ) + args = args[1:] + if len(args) >= 2 { + useRange = true + var err error + start, err = strconv.Atoi(args[0]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + end, err = strconv.Atoi(args[1]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + args = args[2:] + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if !db.exists(key) { + c.WriteInt(0) + return + } + if db.t(key) != "string" { + c.WriteError(msgWrongType) + return + } + + // Real redis only checks after it knows the key is there and a string. + if len(args) != 0 { + c.WriteError(msgSyntaxError) + return + } + + v := db.stringKeys[key] + if useRange { + v = withRange(v, start, end) + } + + c.WriteInt(countBits([]byte(v))) + }) +} + +// BITOP +func (m *Miniredis) cmdBitop(c *server.Peer, cmd string, args []string) { + if len(args) < 3 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + var ( + op = strings.ToUpper(args[0]) + target = args[1] + 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 { + case "AND", "OR", "XOR": + first := input[0] + if t, ok := db.keys[first]; ok && t != "string" { + c.WriteError(msgWrongType) + return + } + res := []byte(db.stringKeys[first]) + for _, vk := range input[1:] { + if t, ok := db.keys[vk]; ok && t != "string" { + c.WriteError(msgWrongType) + return + } + v := db.stringKeys[vk] + cb := map[string]func(byte, byte) byte{ + "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] + res = sliceBinOp(cb, res, []byte(v)) + } + db.del(target, false) // Keep TTL + if len(res) == 0 { + db.del(target, true) + } else { + db.stringSet(target, string(res)) + } + c.WriteInt(len(res)) + case "NOT": + // NOT only takes a single argument. + if len(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" { + c.WriteError(msgWrongType) + return + } + value := []byte(db.stringKeys[key]) + for i := range value { + value[i] = ^value[i] + } + db.del(target, false) // Keep TTL + if len(value) == 0 { + db.del(target, true) + } else { + db.stringSet(target, string(value)) + } + c.WriteInt(len(value)) + default: + c.WriteError(msgSyntaxError) + } + }) +} + +// BITPOS +func (m *Miniredis) cmdBitpos(c *server.Peer, cmd string, args []string) { + if len(args) < 2 || len(args) > 4 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + bit, err := strconv.Atoi(args[1]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + var start, end int + withEnd := false + if len(args) > 2 { + start, err = strconv.Atoi(args[2]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + } + if len(args) > 3 { + end, err = strconv.Atoi(args[3]) + if err != nil { + setDirty(c) + c.WriteError(msgInvalidInt) + return + } + withEnd = true + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if t, ok := db.keys[key]; ok && t != "string" { + c.WriteError(msgWrongType) + return + } + value := db.stringKeys[key] + if start != 0 { + if start > len(value) { + start = len(value) + } + } + if withEnd { + end++ // redis end semantics. + if end < 0 { + end = len(value) + end + } + if end > len(value) { + end = len(value) + } + } else { + end = len(value) + } + if start != 0 || withEnd { + if end < start { + value = "" + } else { + value = value[start:end] + } + } + pos := bitPos([]byte(value), bit == 1) + if pos >= 0 { + pos += start * 8 + } + // Special case when looking for 0, but not when start and end are + // given. + if bit == 0 && pos == -1 && !withEnd { + pos = start*8 + len(value)*8 + } + c.WriteInt(pos) + }) +} + +// GETBIT +func (m *Miniredis) cmdGetbit(c *server.Peer, cmd string, args []string) { + if len(args) != 2 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + bit, err := strconv.Atoi(args[1]) + if err != nil { + setDirty(c) + c.WriteError("ERR bit offset is not an integer or out of range") + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if t, ok := db.keys[key]; ok && t != "string" { + c.WriteError(msgWrongType) + return + } + value := db.stringKeys[key] + + ourByteNr := bit / 8 + var ourByte byte + if ourByteNr > len(value)-1 { + ourByte = '\x00' + } else { + ourByte = value[ourByteNr] + } + res := 0 + if toBits(ourByte)[bit%8] { + res = 1 + } + c.WriteInt(res) + }) +} + +// SETBIT +func (m *Miniredis) cmdSetbit(c *server.Peer, cmd string, args []string) { + if len(args) != 3 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + key := args[0] + bit, err := strconv.Atoi(args[1]) + if err != nil || 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) { + setDirty(c) + c.WriteError("ERR bit is not an integer or out of range") + return + } + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + db := m.db(ctx.selectedDB) + + if t, ok := db.keys[key]; ok && t != "string" { + c.WriteError(msgWrongType) + return + } + value := []byte(db.stringKeys[key]) + + ourByteNr := bit / 8 + ourBitNr := bit % 8 + if ourByteNr > len(value)-1 { + // Too short. Expand. + newValue := make([]byte, ourByteNr+1) + copy(newValue, value) + value = newValue + } + old := 0 + if toBits(value[ourByteNr])[ourBitNr] { + old = 1 + } + if newBit == 0 { + value[ourByteNr] &^= 1 << uint8(7-ourBitNr) + } else { + value[ourByteNr] |= 1 << uint8(7-ourBitNr) + } + db.stringSet(key, string(value)) + + c.WriteInt(old) + }) +} + +// Redis range. both start and end can be negative. +func withRange(v string, start, end int) string { + s, e := redisRange(len(v), start, end, true /* string getrange symantics */) + return v[s:e] +} + +func countBits(v []byte) int { + count := 0 + for _, b := range []byte(v) { + for b > 0 { + count += int((b % uint8(2))) + b = b >> 1 + } + } + return count +} + +// sliceBinOp applies an operator to all slice elements, with Redis string +// padding logic. +func sliceBinOp(f func(a, b byte) byte, a, b []byte) []byte { + maxl := len(a) + if len(b) > maxl { + maxl = len(b) + } + lA := make([]byte, maxl) + copy(lA, a) + lB := make([]byte, maxl) + copy(lB, b) + res := make([]byte, maxl) + for i := range res { + res[i] = f(lA[i], lB[i]) + } + return res +} + +// Return the number of the first bit set/unset. +func bitPos(s []byte, bit bool) int { + for i, b := range s { + for j, set := range toBits(b) { + if set == bit { + return i*8 + j + } + } + } + return -1 +} + +// toBits changes a byte in 8 bools. +func toBits(s byte) [8]bool { + r := [8]bool{} + for i := range r { + if s&(uint8(1)< version { + // Abort! Abort! + stopTx(ctx) + c.WriteLen(0) + return + } + } + + c.WriteLen(len(ctx.transaction)) + for _, cb := range ctx.transaction { + cb(c, ctx) + } + // wake up anyone who waits on anything. + m.signal.Broadcast() + + stopTx(ctx) +} + +// DISCARD +func (m *Miniredis) cmdDiscard(c *server.Peer, cmd string, args []string) { + if len(args) != 0 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + ctx := getCtx(c) + if !inTx(ctx) { + c.WriteError("ERR DISCARD without MULTI") + return + } + + stopTx(ctx) + c.WriteOK() +} + +// WATCH +func (m *Miniredis) cmdWatch(c *server.Peer, cmd string, args []string) { + if len(args) == 0 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + ctx := getCtx(c) + if inTx(ctx) { + c.WriteError("ERR WATCH in MULTI") + return + } + + m.Lock() + defer m.Unlock() + db := m.db(ctx.selectedDB) + + for _, key := range args { + watch(db, ctx, key) + } + c.WriteOK() +} + +// UNWATCH +func (m *Miniredis) cmdUnwatch(c *server.Peer, cmd string, args []string) { + if len(args) != 0 { + setDirty(c) + c.WriteError(errWrongNumber(cmd)) + return + } + if !m.handleAuth(c) { + return + } + + // Doesn't matter if UNWATCH is in a TX or not. Looks like a Redis bug to me. + unwatch(getCtx(c)) + + withTx(m, c, func(c *server.Peer, ctx *connCtx) { + // Do nothing if it's called in a transaction. + c.WriteOK() + }) +} diff --git a/vendor/github.com/alicebob/miniredis/db.go b/vendor/github.com/alicebob/miniredis/db.go new file mode 100644 index 00000000000..5600afe854c --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/db.go @@ -0,0 +1,551 @@ +package miniredis + +import ( + "sort" + "strconv" + "time" +) + +func (db *RedisDB) exists(k string) bool { + _, ok := db.keys[k] + return ok +} + +// t gives the type of a key, or "" +func (db *RedisDB) t(k string) string { + return db.keys[k] +} + +// allKeys returns all keys. Sorted. +func (db *RedisDB) allKeys() []string { + res := make([]string, 0, len(db.keys)) + for k := range db.keys { + res = append(res, k) + } + sort.Strings(res) // To make things deterministic. + return res +} + +// flush removes all keys and values. +func (db *RedisDB) flush() { + db.keys = map[string]string{} + db.stringKeys = map[string]string{} + db.hashKeys = map[string]hashKey{} + db.listKeys = map[string]listKey{} + db.setKeys = map[string]setKey{} + db.sortedsetKeys = map[string]sortedSet{} + db.ttl = map[string]time.Duration{} +} + +// move something to another db. Will return ok. Or not. +func (db *RedisDB) move(key string, to *RedisDB) bool { + if _, ok := to.keys[key]; ok { + return false + } + + t, ok := db.keys[key] + if !ok { + return false + } + to.keys[key] = db.keys[key] + switch t { + case "string": + to.stringKeys[key] = db.stringKeys[key] + case "hash": + to.hashKeys[key] = db.hashKeys[key] + case "list": + to.listKeys[key] = db.listKeys[key] + case "set": + to.setKeys[key] = db.setKeys[key] + case "zset": + to.sortedsetKeys[key] = db.sortedsetKeys[key] + default: + panic("unhandled key type") + } + to.keyVersion[key]++ + if v, ok := db.ttl[key]; ok { + to.ttl[key] = v + } + db.del(key, true) + return true +} + +func (db *RedisDB) rename(from, to string) { + db.del(to, true) + switch db.t(from) { + case "string": + db.stringKeys[to] = db.stringKeys[from] + case "hash": + db.hashKeys[to] = db.hashKeys[from] + case "list": + db.listKeys[to] = db.listKeys[from] + case "set": + db.setKeys[to] = db.setKeys[from] + case "zset": + db.sortedsetKeys[to] = db.sortedsetKeys[from] + default: + panic("missing case") + } + db.keys[to] = db.keys[from] + db.keyVersion[to]++ + db.ttl[to] = db.ttl[from] + + db.del(from, true) +} + +func (db *RedisDB) del(k string, delTTL bool) { + if !db.exists(k) { + return + } + t := db.t(k) + delete(db.keys, k) + db.keyVersion[k]++ + if delTTL { + delete(db.ttl, k) + } + switch t { + case "string": + delete(db.stringKeys, k) + case "hash": + delete(db.hashKeys, k) + case "list": + delete(db.listKeys, k) + case "set": + delete(db.setKeys, k) + case "zset": + delete(db.sortedsetKeys, k) + default: + panic("Unknown key type: " + t) + } +} + +// 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" { + return "" + } + return db.stringKeys[k] +} + +// 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.stringKeys[k] = v + db.keyVersion[k]++ +} + +// change int key value +func (db *RedisDB) stringIncr(k string, delta int) (int, error) { + v := 0 + if sv, ok := db.stringKeys[k]; ok { + var err error + v, err = strconv.Atoi(sv) + if err != nil { + return 0, ErrIntValueError + } + } + v += delta + db.stringSet(k, strconv.Itoa(v)) + return v, nil +} + +// change float key value +func (db *RedisDB) stringIncrfloat(k string, delta float64) (float64, error) { + v := 0.0 + if sv, ok := db.stringKeys[k]; ok { + var err error + v, err = strconv.ParseFloat(sv, 64) + if err != nil { + return 0, ErrFloatValueError + } + } + v += delta + db.stringSet(k, formatFloat(v)) + return v, nil +} + +// listLpush is 'left push', aka unshift. Returns the new length. +func (db *RedisDB) listLpush(k, v string) int { + l, ok := db.listKeys[k] + if !ok { + db.keys[k] = "list" + } + l = append([]string{v}, l...) + db.listKeys[k] = l + db.keyVersion[k]++ + return len(l) +} + +// 'left pop', aka shift. +func (db *RedisDB) listLpop(k string) string { + l := db.listKeys[k] + el := l[0] + l = l[1:] + if len(l) == 0 { + db.del(k, true) + } else { + db.listKeys[k] = l + } + db.keyVersion[k]++ + return el +} + +func (db *RedisDB) listPush(k string, v ...string) int { + l, ok := db.listKeys[k] + if !ok { + db.keys[k] = "list" + } + l = append(l, v...) + db.listKeys[k] = l + db.keyVersion[k]++ + return len(l) +} + +func (db *RedisDB) listPop(k string) string { + l := db.listKeys[k] + el := l[len(l)-1] + l = l[:len(l)-1] + if len(l) == 0 { + db.del(k, true) + } else { + db.listKeys[k] = l + db.keyVersion[k]++ + } + return el +} + +// setset replaces a whole set. +func (db *RedisDB) setSet(k string, set setKey) { + db.keys[k] = "set" + db.setKeys[k] = set + db.keyVersion[k]++ +} + +// setadd adds members to a set. Returns nr of new keys. +func (db *RedisDB) setAdd(k string, elems ...string) int { + s, ok := db.setKeys[k] + if !ok { + s = setKey{} + db.keys[k] = "set" + } + added := 0 + for _, e := range elems { + if _, ok := s[e]; !ok { + added++ + } + s[e] = struct{}{} + } + db.setKeys[k] = s + db.keyVersion[k]++ + return added +} + +// setrem removes members from a set. Returns nr of deleted keys. +func (db *RedisDB) setRem(k string, fields ...string) int { + s, ok := db.setKeys[k] + if !ok { + return 0 + } + removed := 0 + for _, f := range fields { + if _, ok := s[f]; ok { + removed++ + delete(s, f) + } + } + if len(s) == 0 { + db.del(k, true) + } else { + db.setKeys[k] = s + } + db.keyVersion[k]++ + return removed +} + +// All members of a set. +func (db *RedisDB) setMembers(k string) []string { + set := db.setKeys[k] + members := make([]string, 0, len(set)) + for k := range set { + members = append(members, k) + } + sort.Strings(members) + return members +} + +// Is a SET value present? +func (db *RedisDB) setIsMember(k, v string) bool { + set, ok := db.setKeys[k] + if !ok { + return false + } + _, ok = set[v] + return ok +} + +// hashFields returns all (sorted) keys ('fields') for a hash key. +func (db *RedisDB) hashFields(k string) []string { + v := db.hashKeys[k] + r := make([]string, 0, len(v)) + for k := range v { + r = append(r, k) + } + sort.Strings(r) + return r +} + +// hashGet a value +func (db *RedisDB) hashGet(key, field string) string { + return db.hashKeys[key][field] +} + +// hashSet returns whether the key already existed +func (db *RedisDB) hashSet(k, f, v string) bool { + if t, ok := db.keys[k]; ok && t != "hash" { + db.del(k, true) + } + db.keys[k] = "hash" + if _, ok := db.hashKeys[k]; !ok { + db.hashKeys[k] = map[string]string{} + } + _, ok := db.hashKeys[k][f] + db.hashKeys[k][f] = v + db.keyVersion[k]++ + return ok +} + +// hashIncr changes int key value +func (db *RedisDB) hashIncr(key, field string, delta int) (int, error) { + v := 0 + if h, ok := db.hashKeys[key]; ok { + if f, ok := h[field]; ok { + var err error + v, err = strconv.Atoi(f) + if err != nil { + return 0, ErrIntValueError + } + } + } + v += delta + db.hashSet(key, field, strconv.Itoa(v)) + return v, nil +} + +// hashIncrfloat changes float key value +func (db *RedisDB) hashIncrfloat(key, field string, delta float64) (float64, error) { + v := 0.0 + if h, ok := db.hashKeys[key]; ok { + if f, ok := h[field]; ok { + var err error + v, err = strconv.ParseFloat(f, 64) + if err != nil { + return 0, ErrFloatValueError + } + } + } + v += delta + db.hashSet(key, field, formatFloat(v)) + return v, nil +} + +// sortedSet set returns a sortedSet as map +func (db *RedisDB) sortedSet(key string) map[string]float64 { + ss := db.sortedsetKeys[key] + return map[string]float64(ss) +} + +// ssetSet sets a complete sorted set. +func (db *RedisDB) ssetSet(key string, sset sortedSet) { + db.keys[key] = "zset" + db.keyVersion[key]++ + db.sortedsetKeys[key] = sset +} + +// ssetAdd adds member to a sorted set. Returns whether this was a new member. +func (db *RedisDB) ssetAdd(key string, score float64, member string) bool { + ss, ok := db.sortedsetKeys[key] + if !ok { + ss = newSortedSet() + db.keys[key] = "zset" + } + _, ok = ss[member] + ss[member] = score + db.sortedsetKeys[key] = ss + db.keyVersion[key]++ + return !ok +} + +// All members from a sorted set, ordered by score. +func (db *RedisDB) ssetMembers(key string) []string { + ss, ok := db.sortedsetKeys[key] + if !ok { + return nil + } + elems := ss.byScore(asc) + members := make([]string, 0, len(elems)) + for _, e := range elems { + members = append(members, e.member) + } + return members +} + +// All members+scores from a sorted set, ordered by score. +func (db *RedisDB) ssetElements(key string) ssElems { + ss, ok := db.sortedsetKeys[key] + if !ok { + return nil + } + return ss.byScore(asc) +} + +// ssetCard is the sorted set cardinality. +func (db *RedisDB) ssetCard(key string) int { + ss := db.sortedsetKeys[key] + return ss.card() +} + +// ssetRank is the sorted set rank. +func (db *RedisDB) ssetRank(key, member string, d direction) (int, bool) { + ss := db.sortedsetKeys[key] + return ss.rankByScore(member, d) +} + +// ssetScore is sorted set score. +func (db *RedisDB) ssetScore(key, member string) float64 { + ss := db.sortedsetKeys[key] + return ss[member] +} + +// ssetRem is sorted set key delete. +func (db *RedisDB) ssetRem(key, member string) bool { + ss := db.sortedsetKeys[key] + _, ok := ss[member] + delete(ss, member) + if len(ss) == 0 { + // Delete key on removal of last member + db.del(key, true) + } + return ok +} + +// ssetExists tells if a member exists in a sorted set. +func (db *RedisDB) ssetExists(key, member string) bool { + ss := db.sortedsetKeys[key] + _, ok := ss[member] + return ok +} + +// ssetIncrby changes float sorted set score. +func (db *RedisDB) ssetIncrby(k, m string, delta float64) float64 { + ss, ok := db.sortedsetKeys[k] + if !ok { + ss = newSortedSet() + db.keys[k] = "zset" + db.sortedsetKeys[k] = ss + } + + v, _ := ss.get(m) + v += delta + ss.set(v, m) + db.keyVersion[k]++ + return v +} + +// setDiff implements the logic behind SDIFF* +func (db *RedisDB) setDiff(keys []string) (setKey, error) { + key := keys[0] + keys = keys[1:] + if db.exists(key) && db.t(key) != "set" { + return nil, ErrWrongType + } + s := setKey{} + for k := range db.setKeys[key] { + s[k] = struct{}{} + } + for _, sk := range keys { + if !db.exists(sk) { + continue + } + if db.t(sk) != "set" { + return nil, ErrWrongType + } + for e := range db.setKeys[sk] { + delete(s, e) + } + } + return s, nil +} + +// setInter implements the logic behind SINTER* +func (db *RedisDB) setInter(keys []string) (setKey, error) { + key := keys[0] + keys = keys[1:] + if !db.exists(key) { + return setKey{}, nil + } + if db.t(key) != "set" { + return nil, ErrWrongType + } + s := setKey{} + for k := range db.setKeys[key] { + s[k] = struct{}{} + } + for _, sk := range keys { + if !db.exists(sk) { + return setKey{}, nil + } + if db.t(sk) != "set" { + return nil, ErrWrongType + } + other := db.setKeys[sk] + for e := range s { + if _, ok := other[e]; ok { + continue + } + delete(s, e) + } + } + return s, nil +} + +// setUnion implements the logic behind SUNION* +func (db *RedisDB) setUnion(keys []string) (setKey, error) { + key := keys[0] + keys = keys[1:] + if db.exists(key) && db.t(key) != "set" { + return nil, ErrWrongType + } + s := setKey{} + for k := range db.setKeys[key] { + s[k] = struct{}{} + } + for _, sk := range keys { + if !db.exists(sk) { + continue + } + if db.t(sk) != "set" { + return nil, ErrWrongType + } + for e := range db.setKeys[sk] { + s[e] = struct{}{} + } + } + return s, nil +} + +// fastForward proceeds the current timestamp with duration, works as a time machine +func (db *RedisDB) fastForward(duration time.Duration) { + for _, key := range db.allKeys() { + if value, ok := db.ttl[key]; ok { + db.ttl[key] = value - duration + db.checkTTL(key) + } + } +} + +func (db *RedisDB) checkTTL(key string) { + if v, ok := db.ttl[key]; ok && v <= 0 { + db.del(key, true) + } +} diff --git a/vendor/github.com/alicebob/miniredis/direct.go b/vendor/github.com/alicebob/miniredis/direct.go new file mode 100644 index 00000000000..ca41449fed7 --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/direct.go @@ -0,0 +1,549 @@ +package miniredis + +// Commands to modify and query our databases directly. + +import ( + "errors" + "time" +) + +var ( + // ErrKeyNotFound is returned when a key doesn't exist. + ErrKeyNotFound = errors.New(msgKeyNotFound) + // ErrWrongType when a key is not the right type. + ErrWrongType = errors.New(msgWrongType) + // ErrIntValueError can returned by INCRBY + ErrIntValueError = errors.New(msgInvalidInt) + // ErrFloatValueError can returned by INCRBYFLOAT + ErrFloatValueError = errors.New(msgInvalidFloat) +) + +// Select sets the DB id for all direct commands. +func (m *Miniredis) Select(i int) { + m.Lock() + defer m.Unlock() + m.selectedDB = i +} + +// Keys returns all keys from the selected database, sorted. +func (m *Miniredis) Keys() []string { + return m.DB(m.selectedDB).Keys() +} + +// Keys returns all keys, sorted. +func (db *RedisDB) Keys() []string { + db.master.Lock() + defer db.master.Unlock() + return db.allKeys() +} + +// FlushAll removes all keys from all databases. +func (m *Miniredis) FlushAll() { + m.Lock() + defer m.Unlock() + m.flushAll() +} + +func (m *Miniredis) flushAll() { + for _, db := range m.dbs { + db.flush() + } +} + +// FlushDB removes all keys from the selected database. +func (m *Miniredis) FlushDB() { + m.DB(m.selectedDB).FlushDB() +} + +// FlushDB removes all keys. +func (db *RedisDB) FlushDB() { + db.master.Lock() + defer db.master.Unlock() + db.flush() +} + +// Get returns string keys added with SET. +func (m *Miniredis) Get(k string) (string, error) { + return m.DB(m.selectedDB).Get(k) +} + +// Get returns a string key. +func (db *RedisDB) Get(k string) (string, error) { + db.master.Lock() + defer db.master.Unlock() + if !db.exists(k) { + return "", ErrKeyNotFound + } + if db.t(k) != "string" { + return "", ErrWrongType + } + return db.stringGet(k), nil +} + +// Set sets a string key. Removes expire. +func (m *Miniredis) Set(k, v string) error { + return m.DB(m.selectedDB).Set(k, v) +} + +// Set sets a string key. Removes expire. +// Unlike redis the key can't be an existing non-string key. +func (db *RedisDB) Set(k, v string) error { + db.master.Lock() + defer db.master.Unlock() + + if db.exists(k) && db.t(k) != "string" { + return ErrWrongType + } + db.del(k, true) // Remove expire + db.stringSet(k, v) + return nil +} + +// Incr changes a int string value by delta. +func (m *Miniredis) Incr(k string, delta int) (int, error) { + return m.DB(m.selectedDB).Incr(k, delta) +} + +// Incr changes a int string value by delta. +func (db *RedisDB) Incr(k string, delta int) (int, error) { + db.master.Lock() + defer db.master.Unlock() + + if db.exists(k) && db.t(k) != "string" { + return 0, ErrWrongType + } + + return db.stringIncr(k, delta) +} + +// Incrfloat changes a float string value by delta. +func (m *Miniredis) Incrfloat(k string, delta float64) (float64, error) { + return m.DB(m.selectedDB).Incrfloat(k, delta) +} + +// Incrfloat changes a float string value by delta. +func (db *RedisDB) Incrfloat(k string, delta float64) (float64, error) { + db.master.Lock() + defer db.master.Unlock() + + if db.exists(k) && db.t(k) != "string" { + return 0, ErrWrongType + } + + return db.stringIncrfloat(k, delta) +} + +// List returns the list k, or an error if it's not there or something else. +// This is the same as the Redis command `LRANGE 0 -1`, but you can do your own +// range-ing. +func (m *Miniredis) List(k string) ([]string, error) { + return m.DB(m.selectedDB).List(k) +} + +// List returns the list k, or an error if it's not there or something else. +// This is the same as the Redis command `LRANGE 0 -1`, but you can do your own +// range-ing. +func (db *RedisDB) List(k string) ([]string, error) { + db.master.Lock() + defer db.master.Unlock() + + if !db.exists(k) { + return nil, ErrKeyNotFound + } + if db.t(k) != "list" { + return nil, ErrWrongType + } + return db.listKeys[k], nil +} + +// Lpush is an unshift. Returns the new length. +func (m *Miniredis) Lpush(k, v string) (int, error) { + return m.DB(m.selectedDB).Lpush(k, v) +} + +// Lpush is an unshift. Returns the new length. +func (db *RedisDB) Lpush(k, v string) (int, error) { + db.master.Lock() + defer db.master.Unlock() + + if db.exists(k) && db.t(k) != "list" { + return 0, ErrWrongType + } + return db.listLpush(k, v), nil +} + +// Lpop is a shift. Returns the popped element. +func (m *Miniredis) Lpop(k string) (string, error) { + return m.DB(m.selectedDB).Lpop(k) +} + +// Lpop is a shift. Returns the popped element. +func (db *RedisDB) Lpop(k string) (string, error) { + db.master.Lock() + defer db.master.Unlock() + + if !db.exists(k) { + return "", ErrKeyNotFound + } + if db.t(k) != "list" { + return "", ErrWrongType + } + return db.listLpop(k), nil +} + +// Push add element at the end. Is called RPUSH in redis. Returns the new length. +func (m *Miniredis) Push(k string, v ...string) (int, error) { + return m.DB(m.selectedDB).Push(k, v...) +} + +// Push add element at the end. Is called RPUSH in redis. Returns the new length. +func (db *RedisDB) Push(k string, v ...string) (int, error) { + db.master.Lock() + defer db.master.Unlock() + + if db.exists(k) && db.t(k) != "list" { + return 0, ErrWrongType + } + return db.listPush(k, v...), nil +} + +// Pop removes and returns the last element. Is called RPOP in Redis. +func (m *Miniredis) Pop(k string) (string, error) { + return m.DB(m.selectedDB).Pop(k) +} + +// Pop removes and returns the last element. Is called RPOP in Redis. +func (db *RedisDB) Pop(k string) (string, error) { + db.master.Lock() + defer db.master.Unlock() + + if !db.exists(k) { + return "", ErrKeyNotFound + } + if db.t(k) != "list" { + return "", ErrWrongType + } + + return db.listPop(k), nil +} + +// SetAdd adds keys to a set. Returns the number of new keys. +func (m *Miniredis) SetAdd(k string, elems ...string) (int, error) { + return m.DB(m.selectedDB).SetAdd(k, elems...) +} + +// SetAdd adds keys to a set. Returns the number of new keys. +func (db *RedisDB) SetAdd(k string, elems ...string) (int, error) { + db.master.Lock() + defer db.master.Unlock() + if db.exists(k) && db.t(k) != "set" { + return 0, ErrWrongType + } + return db.setAdd(k, elems...), nil +} + +// Members gives all set keys. Sorted. +func (m *Miniredis) Members(k string) ([]string, error) { + return m.DB(m.selectedDB).Members(k) +} + +// Members gives all set keys. Sorted. +func (db *RedisDB) Members(k string) ([]string, error) { + db.master.Lock() + defer db.master.Unlock() + if !db.exists(k) { + return nil, ErrKeyNotFound + } + if db.t(k) != "set" { + return nil, ErrWrongType + } + return db.setMembers(k), nil +} + +// IsMember tells if value is in the set. +func (m *Miniredis) IsMember(k, v string) (bool, error) { + return m.DB(m.selectedDB).IsMember(k, v) +} + +// IsMember tells if value is in the set. +func (db *RedisDB) IsMember(k, v string) (bool, error) { + db.master.Lock() + defer db.master.Unlock() + if !db.exists(k) { + return false, ErrKeyNotFound + } + if db.t(k) != "set" { + return false, ErrWrongType + } + return db.setIsMember(k, v), nil +} + +// HKeys returns all (sorted) keys ('fields') for a hash key. +func (m *Miniredis) HKeys(k string) ([]string, error) { + return m.DB(m.selectedDB).HKeys(k) +} + +// HKeys returns all (sorted) keys ('fields') for a hash key. +func (db *RedisDB) HKeys(key string) ([]string, error) { + db.master.Lock() + defer db.master.Unlock() + if !db.exists(key) { + return nil, ErrKeyNotFound + } + if db.t(key) != "hash" { + return nil, ErrWrongType + } + return db.hashFields(key), nil +} + +// Del deletes a key and any expiration value. Returns whether there was a key. +func (m *Miniredis) Del(k string) bool { + return m.DB(m.selectedDB).Del(k) +} + +// Del deletes a key and any expiration value. Returns whether there was a key. +func (db *RedisDB) Del(k string) bool { + db.master.Lock() + defer db.master.Unlock() + if !db.exists(k) { + return false + } + db.del(k, true) + return true +} + +// TTL is the left over time to live. As set via EXPIRE, PEXPIRE, EXPIREAT, +// PEXPIREAT. +// 0 if not set. +func (m *Miniredis) TTL(k string) time.Duration { + return m.DB(m.selectedDB).TTL(k) +} + +// TTL is the left over time to live. As set via EXPIRE, PEXPIRE, EXPIREAT, +// PEXPIREAT. +// 0 if not set. +func (db *RedisDB) TTL(k string) time.Duration { + db.master.Lock() + defer db.master.Unlock() + return db.ttl[k] +} + +// SetTTL sets the TTL of a key. +func (m *Miniredis) SetTTL(k string, ttl time.Duration) { + m.DB(m.selectedDB).SetTTL(k, ttl) +} + +// SetTTL sets the time to live of a key. +func (db *RedisDB) SetTTL(k string, ttl time.Duration) { + db.master.Lock() + defer db.master.Unlock() + db.ttl[k] = ttl + db.keyVersion[k]++ +} + +// Type gives the type of a key, or "" +func (m *Miniredis) Type(k string) string { + return m.DB(m.selectedDB).Type(k) +} + +// Type gives the type of a key, or "" +func (db *RedisDB) Type(k string) string { + db.master.Lock() + defer db.master.Unlock() + return db.t(k) +} + +// Exists tells whether a key exists. +func (m *Miniredis) Exists(k string) bool { + return m.DB(m.selectedDB).Exists(k) +} + +// Exists tells whether a key exists. +func (db *RedisDB) Exists(k string) bool { + db.master.Lock() + defer db.master.Unlock() + return db.exists(k) +} + +// HGet returns hash keys added with HSET. +// This will return an empty string if the key is not set. Redis would return +// a nil. +// Returns empty string when the key is of a different type. +func (m *Miniredis) HGet(k, f string) string { + return m.DB(m.selectedDB).HGet(k, f) +} + +// HGet returns hash keys added with HSET. +// Returns empty string when the key is of a different type. +func (db *RedisDB) HGet(k, f string) string { + db.master.Lock() + defer db.master.Unlock() + h, ok := db.hashKeys[k] + if !ok { + return "" + } + return h[f] +} + +// HSet sets a hash key. +// If there is another key by the same name it will be gone. +func (m *Miniredis) HSet(k, f, v string) { + m.DB(m.selectedDB).HSet(k, f, v) +} + +// HSet sets a hash key. +// If there is another key by the same name it will be gone. +func (db *RedisDB) HSet(k, f, v string) { + db.master.Lock() + defer db.master.Unlock() + db.hashSet(k, f, v) +} + +// HDel deletes a hash key. +func (m *Miniredis) HDel(k, f string) { + m.DB(m.selectedDB).HDel(k, f) +} + +// HDel deletes a hash key. +func (db *RedisDB) HDel(k, f string) { + db.master.Lock() + defer db.master.Unlock() + db.hdel(k, f) +} + +func (db *RedisDB) hdel(k, f string) { + if _, ok := db.hashKeys[k]; !ok { + return + } + delete(db.hashKeys[k], f) + db.keyVersion[k]++ +} + +// HIncr increases a key/field by delta (int). +func (m *Miniredis) HIncr(k, f string, delta int) (int, error) { + return m.DB(m.selectedDB).HIncr(k, f, delta) +} + +// HIncr increases a key/field by delta (int). +func (db *RedisDB) HIncr(k, f string, delta int) (int, error) { + db.master.Lock() + defer db.master.Unlock() + return db.hashIncr(k, f, delta) +} + +// HIncrfloat increases a key/field by delta (float). +func (m *Miniredis) HIncrfloat(k, f string, delta float64) (float64, error) { + return m.DB(m.selectedDB).HIncrfloat(k, f, delta) +} + +// HIncrfloat increases a key/field by delta (float). +func (db *RedisDB) HIncrfloat(k, f string, delta float64) (float64, error) { + db.master.Lock() + defer db.master.Unlock() + return db.hashIncrfloat(k, f, delta) +} + +// SRem removes fields from a set. Returns number of deleted fields. +func (m *Miniredis) SRem(k string, fields ...string) (int, error) { + return m.DB(m.selectedDB).SRem(k, fields...) +} + +// SRem removes fields from a set. Returns number of deleted fields. +func (db *RedisDB) SRem(k string, fields ...string) (int, error) { + db.master.Lock() + defer db.master.Unlock() + if !db.exists(k) { + return 0, ErrKeyNotFound + } + if db.t(k) != "set" { + return 0, ErrWrongType + } + return db.setRem(k, fields...), nil +} + +// ZAdd adds a score,member to a sorted set. +func (m *Miniredis) ZAdd(k string, score float64, member string) (bool, error) { + return m.DB(m.selectedDB).ZAdd(k, score, member) +} + +// ZAdd adds a score,member to a sorted set. +func (db *RedisDB) ZAdd(k string, score float64, member string) (bool, error) { + db.master.Lock() + defer db.master.Unlock() + if db.exists(k) && db.t(k) != "zset" { + return false, ErrWrongType + } + return db.ssetAdd(k, score, member), nil +} + +// ZMembers returns all members by score +func (m *Miniredis) ZMembers(k string) ([]string, error) { + return m.DB(m.selectedDB).ZMembers(k) +} + +// ZMembers returns all members by score +func (db *RedisDB) ZMembers(k string) ([]string, error) { + db.master.Lock() + defer db.master.Unlock() + if !db.exists(k) { + return nil, ErrKeyNotFound + } + if db.t(k) != "zset" { + return nil, ErrWrongType + } + return db.ssetMembers(k), nil +} + +// SortedSet returns a raw string->float64 map. +func (m *Miniredis) SortedSet(k string) (map[string]float64, error) { + return m.DB(m.selectedDB).SortedSet(k) +} + +// SortedSet returns a raw string->float64 map. +func (db *RedisDB) SortedSet(k string) (map[string]float64, error) { + db.master.Lock() + defer db.master.Unlock() + if !db.exists(k) { + return nil, ErrKeyNotFound + } + if db.t(k) != "zset" { + return nil, ErrWrongType + } + return db.sortedSet(k), nil +} + +// ZRem deletes a member. Returns whether the was a key. +func (m *Miniredis) ZRem(k, member string) (bool, error) { + return m.DB(m.selectedDB).ZRem(k, member) +} + +// ZRem deletes a member. Returns whether the was a key. +func (db *RedisDB) ZRem(k, member string) (bool, error) { + db.master.Lock() + defer db.master.Unlock() + if !db.exists(k) { + return false, ErrKeyNotFound + } + if db.t(k) != "zset" { + return false, ErrWrongType + } + return db.ssetRem(k, member), nil +} + +// ZScore gives the score of a sorted set member. +func (m *Miniredis) ZScore(k, member string) (float64, error) { + return m.DB(m.selectedDB).ZScore(k, member) +} + +// ZScore gives the score of a sorted set member. +func (db *RedisDB) ZScore(k, member string) (float64, error) { + db.master.Lock() + defer db.master.Unlock() + if !db.exists(k) { + return 0, ErrKeyNotFound + } + if db.t(k) != "zset" { + return 0, ErrWrongType + } + return db.ssetScore(k, member), nil +} diff --git a/vendor/github.com/alicebob/miniredis/keys.go b/vendor/github.com/alicebob/miniredis/keys.go new file mode 100644 index 00000000000..b7cd98fba0d --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/keys.go @@ -0,0 +1,65 @@ +package miniredis + +// Translate the 'KEYS' argument ('foo*', 'f??', &c.) into a regexp. + +import ( + "bytes" + "regexp" +) + +// patternRE compiles a KEYS argument to a regexp. Returns nil if the given +// pattern will never match anything. +// The general strategy is to sandwich all non-meta characters between \Q...\E. +func patternRE(k string) *regexp.Regexp { + re := bytes.Buffer{} + re.WriteString(`^\Q`) + for i := 0; i < len(k); i++ { + p := k[i] + switch p { + case '*': + re.WriteString(`\E.*\Q`) + case '?': + re.WriteString(`\E.\Q`) + case '[': + charClass := bytes.Buffer{} + i++ + for ; i < len(k); i++ { + if k[i] == ']' { + break + } + if k[i] == '\\' { + if i == len(k)-1 { + // Ends with a '\'. U-huh. + return nil + } + charClass.WriteByte(k[i]) + i++ + charClass.WriteByte(k[i]) + continue + } + charClass.WriteByte(k[i]) + } + if charClass.Len() == 0 { + // '[]' is valid in Redis, but matches nothing. + return nil + } + re.WriteString(`\E[`) + re.Write(charClass.Bytes()) + re.WriteString(`]\Q`) + + case '\\': + if i == len(k)-1 { + // Ends with a '\'. U-huh. + return nil + } + // Forget the \, keep the next char. + i++ + re.WriteByte(k[i]) + continue + default: + re.WriteByte(p) + } + } + re.WriteString(`\E$`) + return regexp.MustCompile(re.String()) +} diff --git a/vendor/github.com/alicebob/miniredis/lua.go b/vendor/github.com/alicebob/miniredis/lua.go new file mode 100644 index 00000000000..a338425bfaf --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/lua.go @@ -0,0 +1,189 @@ +package miniredis + +import ( + redigo "github.com/gomodule/redigo/redis" + "github.com/yuin/gopher-lua" + + "github.com/alicebob/miniredis/server" +) + +func mkLuaFuncs(conn redigo.Conn) map[string]lua.LGFunction { + mkCall := func(failFast bool) func(l *lua.LState) int { + 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) + return 0 + } + var args []interface{} + for i := 1; i <= top; i++ { + switch a := l.Get(i).(type) { + // case lua.LBool: + // args[i-2] = a + case lua.LNumber: + // value, _ := strconv.ParseFloat(lua.LVAsString(arg), 64) + args = append(args, float64(a)) + case lua.LString: + args = append(args, string(a)) + default: + l.Error(lua.LString("Lua redis() command arguments must be strings or integers"), 1) + return 0 + } + } + cmd, ok := args[0].(string) + if !ok { + l.Error(lua.LString("Unknown Redis command called from Lua script"), 1) + return 0 + } + res, err := conn.Do(cmd, args[1:]...) + if err != nil { + if failFast { + // call() mode + l.Error(lua.LString(err.Error()), 1) + return 0 + } + // pcall() mode + l.Push(lua.LNil) + return 1 + } + + if res == nil { + l.Push(lua.LFalse) + } else { + switch r := res.(type) { + case int64: + l.Push(lua.LNumber(r)) + case []uint8: + l.Push(lua.LString(string(r))) + case []interface{}: + l.Push(redisToLua(l, r)) + case string: + l.Push(lua.LString(r)) + default: + panic("type not handled") + } + } + return 1 + } + } + + return map[string]lua.LGFunction{ + "call": mkCall(true), + "pcall": mkCall(false), + "error_reply": func(l *lua.LState) int { + msg := l.CheckString(1) + res := &lua.LTable{} + res.RawSetString("err", lua.LString(msg)) + l.Push(res) + return 1 + }, + "status_reply": func(l *lua.LState) int { + msg := l.CheckString(1) + res := &lua.LTable{} + res.RawSetString("ok", lua.LString(msg)) + l.Push(res) + return 1 + }, + "sha1hex": func(l *lua.LState) int { + top := l.GetTop() + if top != 1 { + l.Error(lua.LString("wrong number of arguments"), 1) + return 0 + } + msg := lua.LVAsString(l.Get(1)) + l.Push(lua.LString(sha1Hex(msg))) + return 1 + }, + "replicate_commands": func(l *lua.LState) int { + // ignored + return 1 + }, + } +} + +func luaToRedis(l *lua.LState, c *server.Peer, value lua.LValue) { + if value == nil { + c.WriteNull() + return + } + + switch t := value.(type) { + case *lua.LNilType: + c.WriteNull() + case lua.LBool: + if lua.LVAsBool(value) { + c.WriteInt(1) + } else { + c.WriteNull() + } + case lua.LNumber: + c.WriteInt(int(lua.LVAsNumber(value))) + case lua.LString: + s := lua.LVAsString(value) + if s == "OK" { + c.WriteInline(s) + } else { + c.WriteBulk(s) + } + case *lua.LTable: + // special case for tables with an 'err' or 'ok' field + // note: according to the docs this only counts when 'err' or 'ok' is + // the only field. + if s := t.RawGetString("err"); s.Type() != lua.LTNil { + c.WriteError(s.String()) + return + } + if s := t.RawGetString("ok"); s.Type() != lua.LTNil { + c.WriteInline(s.String()) + return + } + + result := []lua.LValue{} + for j := 1; true; j++ { + val := l.GetTable(value, lua.LNumber(j)) + if val == nil { + result = append(result, val) + continue + } + + if val.Type() == lua.LTNil { + break + } + + result = append(result, val) + } + + c.WriteLen(len(result)) + for _, r := range result { + luaToRedis(l, c, r) + } + default: + panic("....") + } +} + +func redisToLua(l *lua.LState, res []interface{}) *lua.LTable { + rettb := l.NewTable() + for _, e := range res { + var v lua.LValue + if e == nil { + v = lua.LFalse + } else { + switch et := e.(type) { + case int64: + v = lua.LNumber(et) + case []uint8: + v = lua.LString(string(et)) + case []interface{}: + v = redisToLua(l, et) + case string: + v = lua.LString(et) + default: + // TODO: oops? + v = lua.LString(e.(string)) + } + } + l.RawSet(rettb, lua.LNumber(rettb.Len()+1), v) + } + return rettb +} diff --git a/vendor/github.com/alicebob/miniredis/miniredis.go b/vendor/github.com/alicebob/miniredis/miniredis.go new file mode 100644 index 00000000000..0688bdfef0e --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/miniredis.go @@ -0,0 +1,373 @@ +// Package miniredis is a pure Go Redis test server, for use in Go unittests. +// There are no dependencies on system binaries, and every server you start +// will be empty. +// +// Start a server with `s, err := miniredis.Run()`. +// Stop it with `defer s.Close()`. +// +// Point your Redis client to `s.Addr()` or `s.Host(), s.Port()`. +// +// Set keys directly via s.Set(...) and similar commands, or use a Redis client. +// +// 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 ( + "fmt" + "net" + "strconv" + "sync" + "time" + + redigo "github.com/gomodule/redigo/redis" + + "github.com/alicebob/miniredis/server" +) + +type hashKey map[string]string +type listKey []string +type setKey map[string]struct{} + +// RedisDB holds a single (numbered) Redis database. +type RedisDB struct { + master *sync.Mutex // pointer to the lock in Miniredis + id int // db id + keys map[string]string // Master map of keys with their type + stringKeys map[string]string // GET/SET &c. keys + hashKeys map[string]hashKey // MGET/MSET &c. keys + listKeys map[string]listKey // LPUSH &c. keys + setKeys map[string]setKey // SADD &c. keys + sortedsetKeys map[string]sortedSet // ZADD &c. keys + ttl map[string]time.Duration // effective TTL values + keyVersion map[string]uint // used to watch values +} + +// Miniredis is a Redis server implementation. +type Miniredis struct { + sync.Mutex + srv *server.Server + port int + password string + dbs map[int]*RedisDB + selectedDB int // DB id used in the direct Get(), Set() &c. + scripts map[string]string // sha1 -> lua src + signal *sync.Cond + now time.Time // used to make a duration from EXPIREAT. time.Now() if not set. +} + +type txCmd func(*server.Peer, *connCtx) + +// database id + key combo +type dbKey struct { + db int + key string +} + +// connCtx has all state for a single connection. +type connCtx struct { + selectedDB int // selected DB + authenticated bool // auth enabled and a valid AUTH seen + transaction []txCmd // transaction callbacks. Or nil. + dirtyTransaction bool // any error during QUEUEing. + watch map[dbKey]uint // WATCHed keys. +} + +// NewMiniRedis makes a new, non-started, Miniredis object. +func NewMiniRedis() *Miniredis { + m := Miniredis{ + dbs: map[int]*RedisDB{}, + scripts: map[string]string{}, + } + m.signal = sync.NewCond(&m) + return &m +} + +func newRedisDB(id int, l *sync.Mutex) RedisDB { + return RedisDB{ + id: id, + master: l, + keys: map[string]string{}, + stringKeys: map[string]string{}, + hashKeys: map[string]hashKey{}, + listKeys: map[string]listKey{}, + setKeys: map[string]setKey{}, + sortedsetKeys: map[string]sortedSet{}, + ttl: map[string]time.Duration{}, + keyVersion: map[string]uint{}, + } +} + +// Run creates and Start()s a Miniredis. +func Run() (*Miniredis, error) { + m := NewMiniRedis() + return m, m.Start() +} + +// Start starts a server. It listens on a random port on localhost. See also +// Addr(). +func (m *Miniredis) Start() error { + s, err := server.NewServer(fmt.Sprintf("127.0.0.1:%d", m.port)) + if err != nil { + return err + } + return m.start(s) +} + +// StartAddr runs miniredis with a given addr. Examples: "127.0.0.1:6379", +// ":6379", or "127.0.0.1:0" +func (m *Miniredis) StartAddr(addr string) error { + s, err := server.NewServer(addr) + if err != nil { + return err + } + return m.start(s) +} + +func (m *Miniredis) start(s *server.Server) error { + m.Lock() + defer m.Unlock() + m.srv = s + m.port = s.Addr().Port + + commandsConnection(m) + commandsGeneric(m) + commandsServer(m) + commandsString(m) + commandsHash(m) + commandsList(m) + commandsSet(m) + commandsSortedSet(m) + commandsTransaction(m) + commandsScripting(m) + + return nil +} + +// Restart restarts a Close()d server on the same port. Values will be +// preserved. +func (m *Miniredis) Restart() error { + return m.Start() +} + +// Close shuts down a Miniredis. +func (m *Miniredis) Close() { + m.Lock() + defer m.Unlock() + if m.srv == nil { + return + } + m.srv.Close() + m.srv = nil +} + +// RequireAuth makes every connection need to AUTH first. Disable again by +// setting an empty string. +func (m *Miniredis) RequireAuth(pw string) { + m.Lock() + defer m.Unlock() + m.password = pw +} + +// DB returns a DB by ID. +func (m *Miniredis) DB(i int) *RedisDB { + m.Lock() + defer m.Unlock() + return m.db(i) +} + +// get DB. No locks! +func (m *Miniredis) db(i int) *RedisDB { + if db, ok := m.dbs[i]; ok { + return db + } + db := newRedisDB(i, &m.Mutex) // the DB has our lock. + m.dbs[i] = &db + return &db +} + +// Addr returns '127.0.0.1:12345'. Can be given to a Dial(). See also Host() +// and Port(), which return the same things. +func (m *Miniredis) Addr() string { + m.Lock() + defer m.Unlock() + return m.srv.Addr().String() +} + +// Host returns the host part of Addr(). +func (m *Miniredis) Host() string { + m.Lock() + defer m.Unlock() + return m.srv.Addr().IP.String() +} + +// Port returns the (random) port part of Addr(). +func (m *Miniredis) Port() string { + m.Lock() + defer m.Unlock() + return strconv.Itoa(m.srv.Addr().Port) +} + +// CommandCount returns the number of processed commands. +func (m *Miniredis) CommandCount() int { + m.Lock() + defer m.Unlock() + return int(m.srv.TotalCommands()) +} + +// CurrentConnectionCount returns the number of currently connected clients. +func (m *Miniredis) CurrentConnectionCount() int { + m.Lock() + defer m.Unlock() + return m.srv.ClientsLen() +} + +// TotalConnectionCount returns the number of client connections since server start. +func (m *Miniredis) TotalConnectionCount() int { + m.Lock() + defer m.Unlock() + return int(m.srv.TotalConnections()) +} + +// FastForward decreases all TTLs by the given duration. All TTLs <= 0 will be +// expired. +func (m *Miniredis) FastForward(duration time.Duration) { + m.Lock() + defer m.Unlock() + for _, db := range m.dbs { + db.fastForward(duration) + } +} + +// redigo returns a redigo.Conn, connected using net.Pipe +func (m *Miniredis) redigo() redigo.Conn { + c1, c2 := net.Pipe() + m.srv.ServeConn(c1) + c := redigo.NewConn(c2, 0, 0) + if m.password != "" { + if _, err := c.Do("AUTH", m.password); err != nil { + // ? + } + } + return c +} + +// Dump returns a text version of the selected DB, usable for debugging. +func (m *Miniredis) Dump() string { + m.Lock() + defer m.Unlock() + + var ( + maxLen = 60 + indent = " " + db = m.db(m.selectedDB) + r = "" + v = func(s string) string { + suffix := "" + if len(s) > maxLen { + suffix = fmt.Sprintf("...(%d)", len(s)) + s = s[:maxLen-len(suffix)] + } + return fmt.Sprintf("%q%s", s, suffix) + } + ) + for _, k := range db.allKeys() { + r += fmt.Sprintf("- %s\n", k) + t := db.t(k) + switch t { + case "string": + r += fmt.Sprintf("%s%s\n", indent, v(db.stringKeys[k])) + case "hash": + for _, hk := range db.hashFields(k) { + r += fmt.Sprintf("%s%s: %s\n", indent, hk, v(db.hashGet(k, hk))) + } + case "list": + for _, lk := range db.listKeys[k] { + r += fmt.Sprintf("%s%s\n", indent, v(lk)) + } + case "set": + for _, mk := range db.setMembers(k) { + r += fmt.Sprintf("%s%s\n", indent, v(mk)) + } + case "zset": + for _, el := range db.ssetElements(k) { + r += fmt.Sprintf("%s%f: %s\n", indent, el.score, v(el.member)) + } + default: + r += fmt.Sprintf("%s(a %s, fixme!)\n", indent, t) + } + } + return r +} + +// SetTime sets the time against which EXPIREAT values are compared. EXPIREAT +// will use time.Now() if this is not set. +func (m *Miniredis) SetTime(t time.Time) { + m.Lock() + defer m.Unlock() + m.now = t +} + +// handleAuth returns false if connection has no access. It sends the reply. +func (m *Miniredis) handleAuth(c *server.Peer) bool { + m.Lock() + defer m.Unlock() + if m.password == "" { + return true + } + if !getCtx(c).authenticated { + c.WriteError("NOAUTH Authentication required.") + return false + } + return true +} + +func getCtx(c *server.Peer) *connCtx { + if c.Ctx == nil { + c.Ctx = &connCtx{} + } + return c.Ctx.(*connCtx) +} + +func startTx(ctx *connCtx) { + ctx.transaction = []txCmd{} + ctx.dirtyTransaction = false +} + +func stopTx(ctx *connCtx) { + ctx.transaction = nil + unwatch(ctx) +} + +func inTx(ctx *connCtx) bool { + return ctx.transaction != nil +} + +func addTxCmd(ctx *connCtx, cb txCmd) { + ctx.transaction = append(ctx.transaction, cb) +} + +func watch(db *RedisDB, ctx *connCtx, key string) { + if ctx.watch == nil { + ctx.watch = map[dbKey]uint{} + } + ctx.watch[dbKey{db: db.id, key: key}] = db.keyVersion[key] // Can be 0. +} + +func unwatch(ctx *connCtx) { + ctx.watch = nil +} + +// setDirty can be called even when not in an tx. Is an no-op then. +func setDirty(c *server.Peer) { + if c.Ctx == nil { + // No transaction. Not relevant. + return + } + getCtx(c).dirtyTransaction = true +} + +func setAuthenticated(c *server.Peer) { + getCtx(c).authenticated = true +} diff --git a/vendor/github.com/alicebob/miniredis/redis.go b/vendor/github.com/alicebob/miniredis/redis.go new file mode 100644 index 00000000000..49ff7bc3c47 --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/redis.go @@ -0,0 +1,208 @@ +package miniredis + +import ( + "fmt" + "math" + "strings" + "sync" + "time" + + "github.com/alicebob/miniredis/server" +) + +const ( + msgWrongType = "WRONGTYPE Operation against a key holding the wrong kind of value" + msgInvalidInt = "ERR value is not an integer or out of range" + 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 an integer or out of range" + 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" + 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." + msgSingleElementPair = "ERR INCR option supports a single increment-element pair" + msgNoScriptFound = "NOSCRIPT No matching script. Please use EVAL." +) + +func errWrongNumber(cmd string) string { + return fmt.Sprintf("ERR wrong number of arguments for '%s' command", strings.ToLower(cmd)) +} + +func errLuaParseError(err error) string { + return fmt.Sprintf("ERR Error compiling script (new function): %s", err.Error()) +} + +// withTx wraps the non-argument-checking part of command handling code in +// transaction logic. +func withTx( + m *Miniredis, + c *server.Peer, + cb txCmd, +) { + ctx := getCtx(c) + if inTx(ctx) { + addTxCmd(ctx, cb) + c.WriteInline("QUEUED") + return + } + m.Lock() + cb(c, ctx) + // done, wake up anyone who waits on anything. + m.signal.Broadcast() + m.Unlock() +} + +// blockCmd is executed returns whether it is done +type blockCmd func(*server.Peer, *connCtx) bool + +// blocking keeps trying a command until the callback returns true. Calls +// onTimeout after the timeout (or when we call this in a transaction). +func blocking( + m *Miniredis, + c *server.Peer, + timeout time.Duration, + cb blockCmd, + onTimeout func(*server.Peer), +) { + var ( + ctx = getCtx(c) + dl *time.Timer + dlc <-chan time.Time + ) + if inTx(ctx) { + addTxCmd(ctx, func(c *server.Peer, ctx *connCtx) { + if !cb(c, ctx) { + onTimeout(c) + } + }) + c.WriteInline("QUEUED") + return + } + if timeout != 0 { + dl = time.NewTimer(timeout) + defer dl.Stop() + dlc = dl.C + } + + m.Lock() + defer m.Unlock() + for { + done := cb(c, ctx) + if done { + return + } + // there is no cond.WaitTimeout(), so hence the the goroutine to wait + // for a timeout + var ( + wg sync.WaitGroup + wakeup = make(chan struct{}, 1) + ) + wg.Add(1) + go func() { + m.signal.Wait() + wakeup <- struct{}{} + wg.Done() + }() + select { + case <-wakeup: + case <-dlc: + onTimeout(c) + m.signal.Broadcast() // to kill the wakeup go routine + wg.Wait() + return + } + wg.Wait() + } +} + +// formatFloat formats a float the way redis does (sort-of) +func formatFloat(v float64) string { + // Format with %f and strip trailing 0s. This is the most like Redis does + // it :( + // .12 is the magic number where most output is the same as Redis. + if math.IsInf(v, +1) { + return "inf" + } + if math.IsInf(v, -1) { + return "-inf" + } + sv := fmt.Sprintf("%.12f", v) + 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 +} + +// redisRange gives Go offsets for something l long with start/end in +// Redis semantics. Both start and end can be negative. +// Used for string range and list range things. +// The results can be used as: v[start:end] +// Note that GETRANGE (on a string key) never returns an empty string when end +// is a large negative number. +func redisRange(l, start, end int, stringSymantics bool) (int, int) { + if start < 0 { + start = l + start + if start < 0 { + start = 0 + } + } + if start > l { + start = l + } + + if end < 0 { + end = l + end + if end < 0 { + end = -1 + if stringSymantics { + end = 0 + } + } + } + end++ // end argument is inclusive in Redis. + if end > l { + end = l + } + + if end < start { + return 0, 0 + } + return start, end +} + +// matchKeys filters only matching keys. +// Will return an empty list on invalid match expression. +func matchKeys(keys []string, match string) []string { + re := patternRE(match) + if re == nil { + // Special case, the given pattern won't match anything / is + // invalid. + return nil + } + res := []string{} + for _, k := range keys { + if !re.MatchString(k) { + continue + } + res = append(res, k) + } + return res +} diff --git a/vendor/github.com/alicebob/miniredis/server/Makefile b/vendor/github.com/alicebob/miniredis/server/Makefile new file mode 100644 index 00000000000..c82e336f9d4 --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/server/Makefile @@ -0,0 +1,9 @@ +.PHONY: all build test + +all: build test + +build: + go build + +test: + go test diff --git a/vendor/github.com/alicebob/miniredis/server/proto.go b/vendor/github.com/alicebob/miniredis/server/proto.go new file mode 100644 index 00000000000..27e62d4f00b --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/server/proto.go @@ -0,0 +1,84 @@ +package server + +import ( + "bufio" + "errors" + "strconv" +) + +// ErrProtocol is the general error for unexpected input +var ErrProtocol = errors.New("invalid request") + +// client always sends arrays with bulk strings +func readArray(rd *bufio.Reader) ([]string, error) { + line, err := rd.ReadString('\n') + if err != nil { + return nil, err + } + if len(line) < 3 { + return nil, ErrProtocol + } + + switch line[0] { + default: + return nil, ErrProtocol + case '*': + l, err := strconv.Atoi(line[1 : len(line)-2]) + if err != nil { + return nil, err + } + // l can be -1 + var fields []string + for ; l > 0; l-- { + s, err := readString(rd) + if err != nil { + return nil, err + } + fields = append(fields, s) + } + return fields, nil + } +} + +func readString(rd *bufio.Reader) (string, error) { + line, err := rd.ReadString('\n') + if err != nil { + return "", err + } + if len(line) < 3 { + return "", ErrProtocol + } + + switch line[0] { + default: + return "", ErrProtocol + case '+', '-', ':': + // +: simple string + // -: errors + // :: integer + // Simple line based replies. + return string(line[1 : len(line)-2]), 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 "", nil + } + var ( + buf = make([]byte, length+2) + pos = 0 + ) + for pos < length+2 { + n, err := rd.Read(buf[pos:]) + if err != nil { + return "", err + } + pos += n + } + return string(buf[:length]), nil + } +} diff --git a/vendor/github.com/alicebob/miniredis/server/server.go b/vendor/github.com/alicebob/miniredis/server/server.go new file mode 100644 index 00000000000..1796453dd5a --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/server/server.go @@ -0,0 +1,242 @@ +package server + +import ( + "bufio" + "fmt" + "net" + "strings" + "sync" + "unicode" +) + +func errUnknownCommand(cmd string, args []string) string { + s := fmt.Sprintf("ERR unknown command `%s`, with args beginning with: ", cmd) + if len(args) > 20 { + args = args[:20] + } + for _, a := range args { + s += fmt.Sprintf("`%s`, ", a) + } + return s +} + +// Cmd is what Register expects +type Cmd func(c *Peer, cmd string, args []string) + +// Server is a simple redis server +type Server struct { + l net.Listener + cmds map[string]Cmd + peers map[net.Conn]struct{} + mu sync.Mutex + wg sync.WaitGroup + infoConns int + infoCmds int +} + +// NewServer makes a server listening on addr. Close with .Close(). +func NewServer(addr string) (*Server, error) { + s := Server{ + cmds: map[string]Cmd{}, + peers: map[net.Conn]struct{}{}, + } + + l, err := net.Listen("tcp", addr) + if err != nil { + return nil, err + } + s.l = l + + s.wg.Add(1) + go func() { + defer s.wg.Done() + s.serve(l) + }() + return &s, nil +} + +func (s *Server) serve(l net.Listener) { + for { + conn, err := l.Accept() + if err != nil { + return + } + s.ServeConn(conn) + } +} + +// ServeConn handles a net.Conn. Nice with net.Pipe() +func (s *Server) ServeConn(conn net.Conn) { + s.wg.Add(1) + go func() { + defer s.wg.Done() + defer conn.Close() + s.mu.Lock() + s.peers[conn] = struct{}{} + s.infoConns++ + s.mu.Unlock() + + s.servePeer(conn) + + s.mu.Lock() + delete(s.peers, conn) + s.mu.Unlock() + }() +} + +// Addr has the net.Addr struct +func (s *Server) Addr() *net.TCPAddr { + s.mu.Lock() + defer s.mu.Unlock() + if s.l == nil { + return nil + } + return s.l.Addr().(*net.TCPAddr) +} + +// Close a server started with NewServer. It will wait until all clients are +// closed. +func (s *Server) Close() { + s.mu.Lock() + if s.l != nil { + s.l.Close() + } + s.l = nil + for c := range s.peers { + c.Close() + } + s.mu.Unlock() + s.wg.Wait() +} + +// Register a command. It can't have been registered before. Safe to call on a +// running server. +func (s *Server) Register(cmd string, f Cmd) error { + s.mu.Lock() + defer s.mu.Unlock() + cmd = strings.ToUpper(cmd) + if _, ok := s.cmds[cmd]; ok { + return fmt.Errorf("command already registered: %s", cmd) + } + s.cmds[cmd] = f + return nil +} + +func (s *Server) servePeer(c net.Conn) { + r := bufio.NewReader(c) + cl := &Peer{ + w: bufio.NewWriter(c), + } + for { + args, err := readArray(r) + if err != nil { + return + } + s.dispatch(cl, args) + cl.w.Flush() + if cl.closed { + c.Close() + return + } + } +} + +func (s *Server) dispatch(c *Peer, args []string) { + cmd, args := args[0], args[1:] + cmdUp := strings.ToUpper(cmd) + s.mu.Lock() + cb, ok := s.cmds[cmdUp] + s.mu.Unlock() + if !ok { + c.WriteError(errUnknownCommand(cmd, args)) + return + } + + s.mu.Lock() + s.infoCmds++ + s.mu.Unlock() + cb(c, cmdUp, args) +} + +// TotalCommands is total (known) commands since this the server started +func (s *Server) TotalCommands() int { + s.mu.Lock() + defer s.mu.Unlock() + return s.infoCmds +} + +// ClientsLen gives the number of connected clients right now +func (s *Server) ClientsLen() int { + s.mu.Lock() + defer s.mu.Unlock() + return len(s.peers) +} + +// TotalConnections give the number of clients connected since the server +// started, including the currently connected ones +func (s *Server) TotalConnections() int { + s.mu.Lock() + defer s.mu.Unlock() + return s.infoConns +} + +// Peer is a client connected to the server +type Peer struct { + w *bufio.Writer + closed bool + Ctx interface{} // anything goes, server won't touch this +} + +// Flush the write buffer. Called automatically after every redis command +func (c *Peer) Flush() { + c.w.Flush() +} + +// Close the client connection after the current command is done. +func (c *Peer) Close() { + c.closed = true +} + +// WriteError writes a redis 'Error' +func (c *Peer) WriteError(e string) { + fmt.Fprintf(c.w, "-%s\r\n", toInline(e)) +} + +// WriteInline writes a redis inline string +func (c *Peer) WriteInline(s string) { + fmt.Fprintf(c.w, "+%s\r\n", toInline(s)) +} + +// WriteOK write the inline string `OK` +func (c *Peer) WriteOK() { + c.WriteInline("OK") +} + +// WriteBulk writes a bulk string +func (c *Peer) WriteBulk(s string) { + fmt.Fprintf(c.w, "$%d\r\n%s\r\n", len(s), s) +} + +// WriteNull writes a redis Null element +func (c *Peer) WriteNull() { + fmt.Fprintf(c.w, "$-1\r\n") +} + +// WriteLen starts an array with the given length +func (c *Peer) WriteLen(n int) { + fmt.Fprintf(c.w, "*%d\r\n", n) +} + +// WriteInt writes an integer +func (c *Peer) WriteInt(i int) { + fmt.Fprintf(c.w, ":%d\r\n", i) +} + +func toInline(s string) string { + return strings.Map(func(r rune) rune { + if unicode.IsSpace(r) { + return ' ' + } + return r + }, s) +} diff --git a/vendor/github.com/alicebob/miniredis/sorted_set.go b/vendor/github.com/alicebob/miniredis/sorted_set.go new file mode 100644 index 00000000000..9b1894d8adf --- /dev/null +++ b/vendor/github.com/alicebob/miniredis/sorted_set.go @@ -0,0 +1,97 @@ +package miniredis + +// The most KISS way to implement a sorted set. Luckily we don't care about +// performance that much. + +import ( + "sort" +) + +type direction int + +const ( + asc direction = iota + desc +) + +type sortedSet map[string]float64 + +type ssElem struct { + score float64 + member string +} +type ssElems []ssElem + +type byScore ssElems + +func (sse byScore) Len() int { return len(sse) } +func (sse byScore) Swap(i, j int) { sse[i], sse[j] = sse[j], sse[i] } +func (sse byScore) Less(i, j int) bool { + if sse[i].score != sse[j].score { + return sse[i].score < sse[j].score + } + return sse[i].member < sse[j].member +} + +func newSortedSet() sortedSet { + return sortedSet{} +} + +func (ss *sortedSet) card() int { + return len(*ss) +} + +func (ss *sortedSet) set(score float64, member string) { + (*ss)[member] = score +} + +func (ss *sortedSet) get(member string) (float64, bool) { + v, ok := (*ss)[member] + return v, ok +} + +// elems gives the list of ssElem, ready to sort. +func (ss *sortedSet) elems() ssElems { + elems := make(ssElems, 0, len(*ss)) + for e, s := range *ss { + elems = append(elems, ssElem{s, e}) + } + return elems +} + +func (ss *sortedSet) byScore(d direction) ssElems { + elems := ss.elems() + sort.Sort(byScore(elems)) + if d == desc { + reverseElems(elems) + } + return ssElems(elems) +} + +// rankByScore gives the (0-based) index of member, or returns false. +func (ss *sortedSet) rankByScore(member string, d direction) (int, bool) { + if _, ok := (*ss)[member]; !ok { + return 0, false + } + for i, e := range ss.byScore(d) { + if e.member == member { + return i, true + } + } + // Can't happen + return 0, false +} + +func reverseSlice(o []string) { + for i := range make([]struct{}, len(o)/2) { + other := len(o) - 1 - i + o[i], o[other] = o[other], o[i] + } +} + +func reverseElems(o ssElems) { + for i := range make([]struct{}, len(o)/2) { + other := len(o) - 1 - i + o[i], o[other] = o[other], o[i] + } +} diff --git a/vendor/github.com/gomodule/redigo/LICENSE b/vendor/github.com/gomodule/redigo/LICENSE new file mode 100644 index 00000000000..67db8588217 --- /dev/null +++ b/vendor/github.com/gomodule/redigo/LICENSE @@ -0,0 +1,175 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. diff --git a/vendor/github.com/gomodule/redigo/internal/commandinfo.go b/vendor/github.com/gomodule/redigo/internal/commandinfo.go new file mode 100644 index 00000000000..b763efbdd8d --- /dev/null +++ b/vendor/github.com/gomodule/redigo/internal/commandinfo.go @@ -0,0 +1,54 @@ +// Copyright 2014 Gary Burd +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package internal // import "github.com/gomodule/redigo/internal" + +import ( + "strings" +) + +const ( + WatchState = 1 << iota + MultiState + SubscribeState + MonitorState +) + +type CommandInfo struct { + Set, Clear int +} + +var commandInfos = map[string]CommandInfo{ + "WATCH": {Set: WatchState}, + "UNWATCH": {Clear: WatchState}, + "MULTI": {Set: MultiState}, + "EXEC": {Clear: WatchState | MultiState}, + "DISCARD": {Clear: WatchState | MultiState}, + "PSUBSCRIBE": {Set: SubscribeState}, + "SUBSCRIBE": {Set: SubscribeState}, + "MONITOR": {Set: MonitorState}, +} + +func init() { + for n, ci := range commandInfos { + commandInfos[strings.ToLower(n)] = ci + } +} + +func LookupCommandInfo(commandName string) CommandInfo { + if ci, ok := commandInfos[commandName]; ok { + return ci + } + return commandInfos[strings.ToUpper(commandName)] +} diff --git a/vendor/github.com/gomodule/redigo/redis/conn.go b/vendor/github.com/gomodule/redigo/redis/conn.go new file mode 100644 index 00000000000..5aa0f32f2e2 --- /dev/null +++ b/vendor/github.com/gomodule/redigo/redis/conn.go @@ -0,0 +1,673 @@ +// Copyright 2012 Gary Burd +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package redis + +import ( + "bufio" + "bytes" + "crypto/tls" + "errors" + "fmt" + "io" + "net" + "net/url" + "regexp" + "strconv" + "sync" + "time" +) + +var ( + _ ConnWithTimeout = (*conn)(nil) +) + +// conn is the low-level implementation of Conn +type conn struct { + // Shared + mu sync.Mutex + pending int + err error + conn net.Conn + + // Read + readTimeout time.Duration + br *bufio.Reader + + // Write + writeTimeout time.Duration + bw *bufio.Writer + + // Scratch space for formatting argument length. + // '*' or '$', length, "\r\n" + lenScratch [32]byte + + // Scratch space for formatting integers and floats. + numScratch [40]byte +} + +// DialTimeout acts like Dial but takes timeouts for establishing the +// connection to the server, writing a command and reading a reply. +// +// Deprecated: Use Dial with options instead. +func DialTimeout(network, address string, connectTimeout, readTimeout, writeTimeout time.Duration) (Conn, error) { + return Dial(network, address, + DialConnectTimeout(connectTimeout), + DialReadTimeout(readTimeout), + DialWriteTimeout(writeTimeout)) +} + +// DialOption specifies an option for dialing a Redis server. +type DialOption struct { + f func(*dialOptions) +} + +type dialOptions struct { + readTimeout time.Duration + writeTimeout time.Duration + dialer *net.Dialer + dial func(network, addr string) (net.Conn, error) + db int + password string + useTLS bool + skipVerify bool + tlsConfig *tls.Config +} + +// DialReadTimeout specifies the timeout for reading a single command reply. +func DialReadTimeout(d time.Duration) DialOption { + return DialOption{func(do *dialOptions) { + do.readTimeout = d + }} +} + +// DialWriteTimeout specifies the timeout for writing a single command. +func DialWriteTimeout(d time.Duration) DialOption { + return DialOption{func(do *dialOptions) { + do.writeTimeout = d + }} +} + +// DialConnectTimeout specifies the timeout for connecting to the Redis server when +// no DialNetDial option is specified. +func DialConnectTimeout(d time.Duration) DialOption { + return DialOption{func(do *dialOptions) { + do.dialer.Timeout = d + }} +} + +// DialKeepAlive specifies the keep-alive period for TCP connections to the Redis server +// when no DialNetDial option is specified. +// If zero, keep-alives are not enabled. If no DialKeepAlive option is specified then +// the default of 5 minutes is used to ensure that half-closed TCP sessions are detected. +func DialKeepAlive(d time.Duration) DialOption { + return DialOption{func(do *dialOptions) { + do.dialer.KeepAlive = d + }} +} + +// DialNetDial specifies a custom dial function for creating TCP +// connections, otherwise a net.Dialer customized via the other options is used. +// DialNetDial overrides DialConnectTimeout and DialKeepAlive. +func DialNetDial(dial func(network, addr string) (net.Conn, error)) DialOption { + return DialOption{func(do *dialOptions) { + do.dial = dial + }} +} + +// DialDatabase specifies the database to select when dialing a connection. +func DialDatabase(db int) DialOption { + return DialOption{func(do *dialOptions) { + do.db = db + }} +} + +// DialPassword specifies the password to use when connecting to +// the Redis server. +func DialPassword(password string) DialOption { + return DialOption{func(do *dialOptions) { + do.password = password + }} +} + +// DialTLSConfig specifies the config to use when a TLS connection is dialed. +// Has no effect when not dialing a TLS connection. +func DialTLSConfig(c *tls.Config) DialOption { + return DialOption{func(do *dialOptions) { + do.tlsConfig = c + }} +} + +// DialTLSSkipVerify disables server name verification when connecting over +// TLS. Has no effect when not dialing a TLS connection. +func DialTLSSkipVerify(skip bool) DialOption { + return DialOption{func(do *dialOptions) { + do.skipVerify = skip + }} +} + +// DialUseTLS specifies whether TLS should be used when connecting to the +// server. This option is ignore by DialURL. +func DialUseTLS(useTLS bool) DialOption { + return DialOption{func(do *dialOptions) { + do.useTLS = useTLS + }} +} + +// Dial connects to the Redis server at the given network and +// address using the specified options. +func Dial(network, address string, options ...DialOption) (Conn, error) { + do := dialOptions{ + dialer: &net.Dialer{ + KeepAlive: time.Minute * 5, + }, + } + for _, option := range options { + option.f(&do) + } + if do.dial == nil { + do.dial = do.dialer.Dial + } + + netConn, err := do.dial(network, address) + if err != nil { + return nil, err + } + + if do.useTLS { + var tlsConfig *tls.Config + if do.tlsConfig == nil { + tlsConfig = &tls.Config{InsecureSkipVerify: do.skipVerify} + } else { + tlsConfig = cloneTLSConfig(do.tlsConfig) + } + if tlsConfig.ServerName == "" { + host, _, err := net.SplitHostPort(address) + if err != nil { + netConn.Close() + return nil, err + } + tlsConfig.ServerName = host + } + + tlsConn := tls.Client(netConn, tlsConfig) + if err := tlsConn.Handshake(); err != nil { + netConn.Close() + return nil, err + } + netConn = tlsConn + } + + c := &conn{ + conn: netConn, + bw: bufio.NewWriter(netConn), + br: bufio.NewReader(netConn), + readTimeout: do.readTimeout, + writeTimeout: do.writeTimeout, + } + + if do.password != "" { + if _, err := c.Do("AUTH", do.password); err != nil { + netConn.Close() + return nil, err + } + } + + if do.db != 0 { + if _, err := c.Do("SELECT", do.db); err != nil { + netConn.Close() + return nil, err + } + } + + return c, nil +} + +var pathDBRegexp = regexp.MustCompile(`/(\d*)\z`) + +// DialURL connects to a Redis server at the given URL using the Redis +// URI scheme. URLs should follow the draft IANA specification for the +// scheme (https://www.iana.org/assignments/uri-schemes/prov/redis). +func DialURL(rawurl string, options ...DialOption) (Conn, error) { + u, err := url.Parse(rawurl) + if err != nil { + return nil, err + } + + if u.Scheme != "redis" && u.Scheme != "rediss" { + return nil, fmt.Errorf("invalid redis URL scheme: %s", u.Scheme) + } + + // As per the IANA draft spec, the host defaults to localhost and + // the port defaults to 6379. + host, port, err := net.SplitHostPort(u.Host) + if err != nil { + // assume port is missing + host = u.Host + port = "6379" + } + if host == "" { + host = "localhost" + } + address := net.JoinHostPort(host, port) + + if u.User != nil { + password, isSet := u.User.Password() + if isSet { + options = append(options, DialPassword(password)) + } + } + + match := pathDBRegexp.FindStringSubmatch(u.Path) + if len(match) == 2 { + db := 0 + if len(match[1]) > 0 { + db, err = strconv.Atoi(match[1]) + if err != nil { + return nil, fmt.Errorf("invalid database: %s", u.Path[1:]) + } + } + if db != 0 { + options = append(options, DialDatabase(db)) + } + } else if u.Path != "" { + return nil, fmt.Errorf("invalid database: %s", u.Path[1:]) + } + + options = append(options, DialUseTLS(u.Scheme == "rediss")) + + return Dial("tcp", address, options...) +} + +// NewConn returns a new Redigo connection for the given net connection. +func NewConn(netConn net.Conn, readTimeout, writeTimeout time.Duration) Conn { + return &conn{ + conn: netConn, + bw: bufio.NewWriter(netConn), + br: bufio.NewReader(netConn), + readTimeout: readTimeout, + writeTimeout: writeTimeout, + } +} + +func (c *conn) Close() error { + c.mu.Lock() + err := c.err + if c.err == nil { + c.err = errors.New("redigo: closed") + err = c.conn.Close() + } + c.mu.Unlock() + return err +} + +func (c *conn) fatal(err error) error { + c.mu.Lock() + if c.err == nil { + c.err = err + // Close connection to force errors on subsequent calls and to unblock + // other reader or writer. + c.conn.Close() + } + c.mu.Unlock() + return err +} + +func (c *conn) Err() error { + c.mu.Lock() + err := c.err + c.mu.Unlock() + return err +} + +func (c *conn) writeLen(prefix byte, n int) error { + c.lenScratch[len(c.lenScratch)-1] = '\n' + c.lenScratch[len(c.lenScratch)-2] = '\r' + i := len(c.lenScratch) - 3 + for { + c.lenScratch[i] = byte('0' + n%10) + i -= 1 + n = n / 10 + if n == 0 { + break + } + } + c.lenScratch[i] = prefix + _, err := c.bw.Write(c.lenScratch[i:]) + return err +} + +func (c *conn) writeString(s string) error { + c.writeLen('$', len(s)) + c.bw.WriteString(s) + _, err := c.bw.WriteString("\r\n") + return err +} + +func (c *conn) writeBytes(p []byte) error { + c.writeLen('$', len(p)) + c.bw.Write(p) + _, err := c.bw.WriteString("\r\n") + return err +} + +func (c *conn) writeInt64(n int64) error { + return c.writeBytes(strconv.AppendInt(c.numScratch[:0], n, 10)) +} + +func (c *conn) writeFloat64(n float64) error { + return c.writeBytes(strconv.AppendFloat(c.numScratch[:0], n, 'g', -1, 64)) +} + +func (c *conn) writeCommand(cmd string, args []interface{}) error { + c.writeLen('*', 1+len(args)) + if err := c.writeString(cmd); err != nil { + return err + } + for _, arg := range args { + if err := c.writeArg(arg, true); err != nil { + return err + } + } + return nil +} + +func (c *conn) writeArg(arg interface{}, argumentTypeOK bool) (err error) { + switch arg := arg.(type) { + case string: + return c.writeString(arg) + case []byte: + return c.writeBytes(arg) + case int: + return c.writeInt64(int64(arg)) + case int64: + return c.writeInt64(arg) + case float64: + return c.writeFloat64(arg) + case bool: + if arg { + return c.writeString("1") + } else { + return c.writeString("0") + } + case nil: + return c.writeString("") + case Argument: + if argumentTypeOK { + return c.writeArg(arg.RedisArg(), false) + } + // See comment in default clause below. + var buf bytes.Buffer + fmt.Fprint(&buf, arg) + return c.writeBytes(buf.Bytes()) + default: + // This default clause is intended to handle builtin numeric types. + // The function should return an error for other types, but this is not + // done for compatibility with previous versions of the package. + var buf bytes.Buffer + fmt.Fprint(&buf, arg) + return c.writeBytes(buf.Bytes()) + } +} + +type protocolError string + +func (pe protocolError) Error() string { + return fmt.Sprintf("redigo: %s (possible server error or unsupported concurrent read by application)", string(pe)) +} + +func (c *conn) readLine() ([]byte, error) { + p, err := c.br.ReadSlice('\n') + if err == bufio.ErrBufferFull { + return nil, protocolError("long response line") + } + if err != nil { + return nil, err + } + i := len(p) - 2 + if i < 0 || p[i] != '\r' { + return nil, protocolError("bad response line terminator") + } + return p[:i], nil +} + +// parseLen parses bulk string and array lengths. +func parseLen(p []byte) (int, error) { + if len(p) == 0 { + return -1, protocolError("malformed length") + } + + if p[0] == '-' && len(p) == 2 && p[1] == '1' { + // handle $-1 and $-1 null replies. + return -1, nil + } + + var n int + for _, b := range p { + n *= 10 + if b < '0' || b > '9' { + return -1, protocolError("illegal bytes in length") + } + n += int(b - '0') + } + + return n, nil +} + +// parseInt parses an integer reply. +func parseInt(p []byte) (interface{}, error) { + if len(p) == 0 { + return 0, protocolError("malformed integer") + } + + var negate bool + if p[0] == '-' { + negate = true + p = p[1:] + if len(p) == 0 { + return 0, protocolError("malformed integer") + } + } + + var n int64 + for _, b := range p { + n *= 10 + if b < '0' || b > '9' { + return 0, protocolError("illegal bytes in length") + } + n += int64(b - '0') + } + + if negate { + n = -n + } + return n, nil +} + +var ( + okReply interface{} = "OK" + pongReply interface{} = "PONG" +) + +func (c *conn) readReply() (interface{}, error) { + line, err := c.readLine() + if err != nil { + return nil, err + } + if len(line) == 0 { + return nil, protocolError("short response line") + } + switch line[0] { + case '+': + switch { + case len(line) == 3 && line[1] == 'O' && line[2] == 'K': + // Avoid allocation for frequent "+OK" response. + return okReply, nil + case len(line) == 5 && line[1] == 'P' && line[2] == 'O' && line[3] == 'N' && line[4] == 'G': + // Avoid allocation in PING command benchmarks :) + return pongReply, nil + default: + return string(line[1:]), nil + } + case '-': + return Error(string(line[1:])), nil + case ':': + return parseInt(line[1:]) + case '$': + n, err := parseLen(line[1:]) + if n < 0 || err != nil { + return nil, err + } + p := make([]byte, n) + _, err = io.ReadFull(c.br, p) + if err != nil { + return nil, err + } + if line, err := c.readLine(); err != nil { + return nil, err + } else if len(line) != 0 { + return nil, protocolError("bad bulk string format") + } + return p, nil + case '*': + n, err := parseLen(line[1:]) + if n < 0 || err != nil { + return nil, err + } + r := make([]interface{}, n) + for i := range r { + r[i], err = c.readReply() + if err != nil { + return nil, err + } + } + return r, nil + } + return nil, protocolError("unexpected response line") +} + +func (c *conn) Send(cmd string, args ...interface{}) error { + c.mu.Lock() + c.pending += 1 + c.mu.Unlock() + if c.writeTimeout != 0 { + c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout)) + } + if err := c.writeCommand(cmd, args); err != nil { + return c.fatal(err) + } + return nil +} + +func (c *conn) Flush() error { + if c.writeTimeout != 0 { + c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout)) + } + if err := c.bw.Flush(); err != nil { + return c.fatal(err) + } + return nil +} + +func (c *conn) Receive() (interface{}, error) { + return c.ReceiveWithTimeout(c.readTimeout) +} + +func (c *conn) ReceiveWithTimeout(timeout time.Duration) (reply interface{}, err error) { + var deadline time.Time + if timeout != 0 { + deadline = time.Now().Add(timeout) + } + c.conn.SetReadDeadline(deadline) + + if reply, err = c.readReply(); err != nil { + return nil, c.fatal(err) + } + // When using pub/sub, the number of receives can be greater than the + // number of sends. To enable normal use of the connection after + // unsubscribing from all channels, we do not decrement pending to a + // negative value. + // + // The pending field is decremented after the reply is read to handle the + // case where Receive is called before Send. + c.mu.Lock() + if c.pending > 0 { + c.pending -= 1 + } + c.mu.Unlock() + if err, ok := reply.(Error); ok { + return nil, err + } + return +} + +func (c *conn) Do(cmd string, args ...interface{}) (interface{}, error) { + return c.DoWithTimeout(c.readTimeout, cmd, args...) +} + +func (c *conn) DoWithTimeout(readTimeout time.Duration, cmd string, args ...interface{}) (interface{}, error) { + c.mu.Lock() + pending := c.pending + c.pending = 0 + c.mu.Unlock() + + if cmd == "" && pending == 0 { + return nil, nil + } + + if c.writeTimeout != 0 { + c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout)) + } + + if cmd != "" { + if err := c.writeCommand(cmd, args); err != nil { + return nil, c.fatal(err) + } + } + + if err := c.bw.Flush(); err != nil { + return nil, c.fatal(err) + } + + var deadline time.Time + if readTimeout != 0 { + deadline = time.Now().Add(readTimeout) + } + c.conn.SetReadDeadline(deadline) + + if cmd == "" { + reply := make([]interface{}, pending) + for i := range reply { + r, e := c.readReply() + if e != nil { + return nil, c.fatal(e) + } + reply[i] = r + } + return reply, nil + } + + var err error + var reply interface{} + for i := 0; i <= pending; i++ { + var e error + if reply, e = c.readReply(); e != nil { + return nil, c.fatal(e) + } + if e, ok := reply.(Error); ok && err == nil { + err = e + } + } + return reply, err +} diff --git a/vendor/github.com/gomodule/redigo/redis/doc.go b/vendor/github.com/gomodule/redigo/redis/doc.go new file mode 100644 index 00000000000..70ec1ea6919 --- /dev/null +++ b/vendor/github.com/gomodule/redigo/redis/doc.go @@ -0,0 +1,177 @@ +// Copyright 2012 Gary Burd +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +// Package redis is a client for the Redis database. +// +// The Redigo FAQ (https://github.com/gomodule/redigo/wiki/FAQ) contains more +// documentation about this package. +// +// Connections +// +// The Conn interface is the primary interface for working with Redis. +// Applications create connections by calling the Dial, DialWithTimeout or +// NewConn functions. In the future, functions will be added for creating +// sharded and other types of connections. +// +// The application must call the connection Close method when the application +// is done with the connection. +// +// Executing Commands +// +// The Conn interface has a generic method for executing Redis commands: +// +// Do(commandName string, args ...interface{}) (reply interface{}, err error) +// +// The Redis command reference (http://redis.io/commands) lists the available +// commands. An example of using the Redis APPEND command is: +// +// n, err := conn.Do("APPEND", "key", "value") +// +// The Do method converts command arguments to bulk strings for transmission +// to the server as follows: +// +// Go Type Conversion +// []byte Sent as is +// string Sent as is +// int, int64 strconv.FormatInt(v) +// float64 strconv.FormatFloat(v, 'g', -1, 64) +// bool true -> "1", false -> "0" +// nil "" +// all other types fmt.Fprint(w, v) +// +// Redis command reply types are represented using the following Go types: +// +// Redis type Go type +// error redis.Error +// integer int64 +// simple string string +// bulk string []byte or nil if value not present. +// array []interface{} or nil if value not present. +// +// Use type assertions or the reply helper functions to convert from +// interface{} to the specific Go type for the command result. +// +// Pipelining +// +// Connections support pipelining using the Send, Flush and Receive methods. +// +// Send(commandName string, args ...interface{}) error +// Flush() error +// Receive() (reply interface{}, err error) +// +// Send writes the command to the connection's output buffer. Flush flushes the +// connection's output buffer to the server. Receive reads a single reply from +// the server. The following example shows a simple pipeline. +// +// c.Send("SET", "foo", "bar") +// c.Send("GET", "foo") +// c.Flush() +// c.Receive() // reply from SET +// v, err = c.Receive() // reply from GET +// +// The Do method combines the functionality of the Send, Flush and Receive +// methods. The Do method starts by writing the command and flushing the output +// buffer. Next, the Do method receives all pending replies including the reply +// for the command just sent by Do. If any of the received replies is an error, +// then Do returns the error. If there are no errors, then Do returns the last +// reply. If the command argument to the Do method is "", then the Do method +// will flush the output buffer and receive pending replies without sending a +// command. +// +// Use the Send and Do methods to implement pipelined transactions. +// +// c.Send("MULTI") +// c.Send("INCR", "foo") +// c.Send("INCR", "bar") +// r, err := c.Do("EXEC") +// fmt.Println(r) // prints [1, 1] +// +// Concurrency +// +// Connections support one concurrent caller to the Receive method and one +// concurrent caller to the Send and Flush methods. No other concurrency is +// supported including concurrent calls to the Do method. +// +// For full concurrent access to Redis, use the thread-safe Pool to get, use +// and release a connection from within a goroutine. Connections returned from +// a Pool have the concurrency restrictions described in the previous +// paragraph. +// +// Publish and Subscribe +// +// Use the Send, Flush and Receive methods to implement Pub/Sub subscribers. +// +// c.Send("SUBSCRIBE", "example") +// c.Flush() +// for { +// reply, err := c.Receive() +// if err != nil { +// return err +// } +// // process pushed message +// } +// +// The PubSubConn type wraps a Conn with convenience methods for implementing +// subscribers. The Subscribe, PSubscribe, Unsubscribe and PUnsubscribe methods +// send and flush a subscription management command. The receive method +// converts a pushed message to convenient types for use in a type switch. +// +// psc := redis.PubSubConn{Conn: c} +// psc.Subscribe("example") +// for { +// switch v := psc.Receive().(type) { +// case redis.Message: +// fmt.Printf("%s: message: %s\n", v.Channel, v.Data) +// case redis.Subscription: +// fmt.Printf("%s: %s %d\n", v.Channel, v.Kind, v.Count) +// case error: +// return v +// } +// } +// +// Reply Helpers +// +// The Bool, Int, Bytes, String, Strings and Values functions convert a reply +// to a value of a specific type. To allow convenient wrapping of calls to the +// connection Do and Receive methods, the functions take a second argument of +// type error. If the error is non-nil, then the helper function returns the +// error. If the error is nil, the function converts the reply to the specified +// type: +// +// exists, err := redis.Bool(c.Do("EXISTS", "foo")) +// if err != nil { +// // handle error return from c.Do or type conversion error. +// } +// +// The Scan function converts elements of a array reply to Go types: +// +// var value1 int +// var value2 string +// reply, err := redis.Values(c.Do("MGET", "key1", "key2")) +// if err != nil { +// // handle error +// } +// if _, err := redis.Scan(reply, &value1, &value2); err != nil { +// // handle error +// } +// +// Errors +// +// Connection methods return error replies from the server as type redis.Error. +// +// Call the connection Err() method to determine if the connection encountered +// non-recoverable error such as a network error or protocol parsing error. If +// Err() returns a non-nil value, then the connection is not usable and should +// be closed. +package redis // import "github.com/gomodule/redigo/redis" diff --git a/vendor/github.com/gomodule/redigo/redis/go16.go b/vendor/github.com/gomodule/redigo/redis/go16.go new file mode 100644 index 00000000000..f6b1a7ccdce --- /dev/null +++ b/vendor/github.com/gomodule/redigo/redis/go16.go @@ -0,0 +1,27 @@ +// +build !go1.7 + +package redis + +import "crypto/tls" + +func cloneTLSConfig(cfg *tls.Config) *tls.Config { + return &tls.Config{ + Rand: cfg.Rand, + Time: cfg.Time, + Certificates: cfg.Certificates, + NameToCertificate: cfg.NameToCertificate, + GetCertificate: cfg.GetCertificate, + RootCAs: cfg.RootCAs, + NextProtos: cfg.NextProtos, + ServerName: cfg.ServerName, + ClientAuth: cfg.ClientAuth, + ClientCAs: cfg.ClientCAs, + InsecureSkipVerify: cfg.InsecureSkipVerify, + CipherSuites: cfg.CipherSuites, + PreferServerCipherSuites: cfg.PreferServerCipherSuites, + ClientSessionCache: cfg.ClientSessionCache, + MinVersion: cfg.MinVersion, + MaxVersion: cfg.MaxVersion, + CurvePreferences: cfg.CurvePreferences, + } +} diff --git a/vendor/github.com/gomodule/redigo/redis/go17.go b/vendor/github.com/gomodule/redigo/redis/go17.go new file mode 100644 index 00000000000..5f36379113c --- /dev/null +++ b/vendor/github.com/gomodule/redigo/redis/go17.go @@ -0,0 +1,29 @@ +// +build go1.7,!go1.8 + +package redis + +import "crypto/tls" + +func cloneTLSConfig(cfg *tls.Config) *tls.Config { + return &tls.Config{ + Rand: cfg.Rand, + Time: cfg.Time, + Certificates: cfg.Certificates, + NameToCertificate: cfg.NameToCertificate, + GetCertificate: cfg.GetCertificate, + RootCAs: cfg.RootCAs, + NextProtos: cfg.NextProtos, + ServerName: cfg.ServerName, + ClientAuth: cfg.ClientAuth, + ClientCAs: cfg.ClientCAs, + InsecureSkipVerify: cfg.InsecureSkipVerify, + CipherSuites: cfg.CipherSuites, + PreferServerCipherSuites: cfg.PreferServerCipherSuites, + ClientSessionCache: cfg.ClientSessionCache, + MinVersion: cfg.MinVersion, + MaxVersion: cfg.MaxVersion, + CurvePreferences: cfg.CurvePreferences, + DynamicRecordSizingDisabled: cfg.DynamicRecordSizingDisabled, + Renegotiation: cfg.Renegotiation, + } +} diff --git a/vendor/github.com/gomodule/redigo/redis/go18.go b/vendor/github.com/gomodule/redigo/redis/go18.go new file mode 100644 index 00000000000..558363be39a --- /dev/null +++ b/vendor/github.com/gomodule/redigo/redis/go18.go @@ -0,0 +1,9 @@ +// +build go1.8 + +package redis + +import "crypto/tls" + +func cloneTLSConfig(cfg *tls.Config) *tls.Config { + return cfg.Clone() +} diff --git a/vendor/github.com/gomodule/redigo/redis/log.go b/vendor/github.com/gomodule/redigo/redis/log.go new file mode 100644 index 00000000000..b2996611c6d --- /dev/null +++ b/vendor/github.com/gomodule/redigo/redis/log.go @@ -0,0 +1,134 @@ +// Copyright 2012 Gary Burd +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package redis + +import ( + "bytes" + "fmt" + "log" + "time" +) + +var ( + _ ConnWithTimeout = (*loggingConn)(nil) +) + +// NewLoggingConn returns a logging wrapper around a connection. +func NewLoggingConn(conn Conn, logger *log.Logger, prefix string) Conn { + if prefix != "" { + prefix = prefix + "." + } + return &loggingConn{conn, logger, prefix} +} + +type loggingConn struct { + Conn + logger *log.Logger + prefix string +} + +func (c *loggingConn) Close() error { + err := c.Conn.Close() + var buf bytes.Buffer + fmt.Fprintf(&buf, "%sClose() -> (%v)", c.prefix, err) + c.logger.Output(2, buf.String()) + return err +} + +func (c *loggingConn) printValue(buf *bytes.Buffer, v interface{}) { + const chop = 32 + switch v := v.(type) { + case []byte: + if len(v) > chop { + fmt.Fprintf(buf, "%q...", v[:chop]) + } else { + fmt.Fprintf(buf, "%q", v) + } + case string: + if len(v) > chop { + fmt.Fprintf(buf, "%q...", v[:chop]) + } else { + fmt.Fprintf(buf, "%q", v) + } + case []interface{}: + if len(v) == 0 { + buf.WriteString("[]") + } else { + sep := "[" + fin := "]" + if len(v) > chop { + v = v[:chop] + fin = "...]" + } + for _, vv := range v { + buf.WriteString(sep) + c.printValue(buf, vv) + sep = ", " + } + buf.WriteString(fin) + } + default: + fmt.Fprint(buf, v) + } +} + +func (c *loggingConn) print(method, commandName string, args []interface{}, reply interface{}, err error) { + var buf bytes.Buffer + fmt.Fprintf(&buf, "%s%s(", c.prefix, method) + if method != "Receive" { + buf.WriteString(commandName) + for _, arg := range args { + buf.WriteString(", ") + c.printValue(&buf, arg) + } + } + buf.WriteString(") -> (") + if method != "Send" { + c.printValue(&buf, reply) + buf.WriteString(", ") + } + fmt.Fprintf(&buf, "%v)", err) + c.logger.Output(3, buf.String()) +} + +func (c *loggingConn) Do(commandName string, args ...interface{}) (interface{}, error) { + reply, err := c.Conn.Do(commandName, args...) + c.print("Do", commandName, args, reply, err) + return reply, err +} + +func (c *loggingConn) DoWithTimeout(timeout time.Duration, commandName string, args ...interface{}) (interface{}, error) { + reply, err := DoWithTimeout(c.Conn, timeout, commandName, args...) + c.print("DoWithTimeout", commandName, args, reply, err) + return reply, err +} + +func (c *loggingConn) Send(commandName string, args ...interface{}) error { + err := c.Conn.Send(commandName, args...) + c.print("Send", commandName, args, nil, err) + return err +} + +func (c *loggingConn) Receive() (interface{}, error) { + reply, err := c.Conn.Receive() + c.print("Receive", "", nil, reply, err) + return reply, err +} + +func (c *loggingConn) ReceiveWithTimeout(timeout time.Duration) (interface{}, error) { + reply, err := ReceiveWithTimeout(c.Conn, timeout) + c.print("ReceiveWithTimeout", "", nil, reply, err) + return reply, err +} diff --git a/vendor/github.com/gomodule/redigo/redis/pool.go b/vendor/github.com/gomodule/redigo/redis/pool.go new file mode 100644 index 00000000000..d77da3254a3 --- /dev/null +++ b/vendor/github.com/gomodule/redigo/redis/pool.go @@ -0,0 +1,562 @@ +// Copyright 2012 Gary Burd +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package redis + +import ( + "bytes" + "crypto/rand" + "crypto/sha1" + "errors" + "io" + "strconv" + "sync" + "sync/atomic" + "time" + + "github.com/gomodule/redigo/internal" +) + +var ( + _ ConnWithTimeout = (*activeConn)(nil) + _ ConnWithTimeout = (*errorConn)(nil) +) + +var nowFunc = time.Now // for testing + +// ErrPoolExhausted is returned from a pool connection method (Do, Send, +// Receive, Flush, Err) when the maximum number of database connections in the +// pool has been reached. +var ErrPoolExhausted = errors.New("redigo: connection pool exhausted") + +var ( + errPoolClosed = errors.New("redigo: connection pool closed") + errConnClosed = errors.New("redigo: connection closed") +) + +// Pool maintains a pool of connections. The application calls the Get method +// to get a connection from the pool and the connection's Close method to +// return the connection's resources to the pool. +// +// The following example shows how to use a pool in a web application. The +// application creates a pool at application startup and makes it available to +// request handlers using a package level variable. The pool configuration used +// here is an example, not a recommendation. +// +// func newPool(addr string) *redis.Pool { +// return &redis.Pool{ +// MaxIdle: 3, +// IdleTimeout: 240 * time.Second, +// Dial: func () (redis.Conn, error) { return redis.Dial("tcp", addr) }, +// } +// } +// +// var ( +// pool *redis.Pool +// redisServer = flag.String("redisServer", ":6379", "") +// ) +// +// func main() { +// flag.Parse() +// pool = newPool(*redisServer) +// ... +// } +// +// A request handler gets a connection from the pool and closes the connection +// when the handler is done: +// +// func serveHome(w http.ResponseWriter, r *http.Request) { +// conn := pool.Get() +// defer conn.Close() +// ... +// } +// +// Use the Dial function to authenticate connections with the AUTH command or +// select a database with the SELECT command: +// +// pool := &redis.Pool{ +// // Other pool configuration not shown in this example. +// Dial: func () (redis.Conn, error) { +// c, err := redis.Dial("tcp", server) +// if err != nil { +// return nil, err +// } +// if _, err := c.Do("AUTH", password); err != nil { +// c.Close() +// return nil, err +// } +// if _, err := c.Do("SELECT", db); err != nil { +// c.Close() +// return nil, err +// } +// return c, nil +// }, +// } +// +// Use the TestOnBorrow function to check the health of an idle connection +// before the connection is returned to the application. This example PINGs +// connections that have been idle more than a minute: +// +// pool := &redis.Pool{ +// // Other pool configuration not shown in this example. +// TestOnBorrow: func(c redis.Conn, t time.Time) error { +// if time.Since(t) < time.Minute { +// return nil +// } +// _, err := c.Do("PING") +// return err +// }, +// } +// +type Pool struct { + // Dial is an application supplied function for creating and configuring a + // connection. + // + // The connection returned from Dial must not be in a special state + // (subscribed to pubsub channel, transaction started, ...). + Dial func() (Conn, error) + + // TestOnBorrow is an optional application supplied function for checking + // the health of an idle connection before the connection is used again by + // the application. Argument t is the time that the connection was returned + // to the pool. If the function returns an error, then the connection is + // closed. + TestOnBorrow func(c Conn, t time.Time) error + + // Maximum number of idle connections in the pool. + MaxIdle int + + // Maximum number of connections allocated by the pool at a given time. + // When zero, there is no limit on the number of connections in the pool. + MaxActive int + + // Close connections after remaining idle for this duration. If the value + // is zero, then idle connections are not closed. Applications should set + // the timeout to a value less than the server's timeout. + IdleTimeout time.Duration + + // If Wait is true and the pool is at the MaxActive limit, then Get() waits + // for a connection to be returned to the pool before returning. + Wait bool + + // Close connections older than this duration. If the value is zero, then + // the pool does not close connections based on age. + MaxConnLifetime time.Duration + + chInitialized uint32 // set to 1 when field ch is initialized + + mu sync.Mutex // mu protects the following fields + closed bool // set to true when the pool is closed. + active int // the number of open connections in the pool + ch chan struct{} // limits open connections when p.Wait is true + idle idleList // idle connections +} + +// NewPool creates a new pool. +// +// Deprecated: Initialize the Pool directory as shown in the example. +func NewPool(newFn func() (Conn, error), maxIdle int) *Pool { + return &Pool{Dial: newFn, MaxIdle: maxIdle} +} + +// Get gets a connection. The application must close the returned connection. +// This method always returns a valid connection so that applications can defer +// error handling to the first use of the connection. If there is an error +// getting an underlying connection, then the connection Err, Do, Send, Flush +// and Receive methods return that error. +func (p *Pool) Get() Conn { + pc, err := p.get(nil) + if err != nil { + return errorConn{err} + } + return &activeConn{p: p, pc: pc} +} + +// PoolStats contains pool statistics. +type PoolStats struct { + // ActiveCount is the number of connections in the pool. The count includes + // idle connections and connections in use. + ActiveCount int + // IdleCount is the number of idle connections in the pool. + IdleCount int +} + +// Stats returns pool's statistics. +func (p *Pool) Stats() PoolStats { + p.mu.Lock() + stats := PoolStats{ + ActiveCount: p.active, + IdleCount: p.idle.count, + } + p.mu.Unlock() + + return stats +} + +// ActiveCount returns the number of connections in the pool. The count +// includes idle connections and connections in use. +func (p *Pool) ActiveCount() int { + p.mu.Lock() + active := p.active + p.mu.Unlock() + return active +} + +// IdleCount returns the number of idle connections in the pool. +func (p *Pool) IdleCount() int { + p.mu.Lock() + idle := p.idle.count + p.mu.Unlock() + return idle +} + +// Close releases the resources used by the pool. +func (p *Pool) Close() error { + p.mu.Lock() + if p.closed { + p.mu.Unlock() + return nil + } + p.closed = true + p.active -= p.idle.count + pc := p.idle.front + p.idle.count = 0 + p.idle.front, p.idle.back = nil, nil + if p.ch != nil { + close(p.ch) + } + p.mu.Unlock() + for ; pc != nil; pc = pc.next { + pc.c.Close() + } + return nil +} + +func (p *Pool) lazyInit() { + // Fast path. + if atomic.LoadUint32(&p.chInitialized) == 1 { + return + } + // Slow path. + p.mu.Lock() + if p.chInitialized == 0 { + p.ch = make(chan struct{}, p.MaxActive) + if p.closed { + close(p.ch) + } else { + for i := 0; i < p.MaxActive; i++ { + p.ch <- struct{}{} + } + } + atomic.StoreUint32(&p.chInitialized, 1) + } + p.mu.Unlock() +} + +// get prunes stale connections and returns a connection from the idle list or +// creates a new connection. +func (p *Pool) get(ctx interface { + Done() <-chan struct{} + Err() error +}) (*poolConn, error) { + + // Handle limit for p.Wait == true. + if p.Wait && p.MaxActive > 0 { + p.lazyInit() + if ctx == nil { + <-p.ch + } else { + select { + case <-p.ch: + case <-ctx.Done(): + return nil, ctx.Err() + } + } + } + + p.mu.Lock() + + // Prune stale connections at the back of the idle list. + if p.IdleTimeout > 0 { + n := p.idle.count + for i := 0; i < n && p.idle.back != nil && p.idle.back.t.Add(p.IdleTimeout).Before(nowFunc()); i++ { + pc := p.idle.back + p.idle.popBack() + p.mu.Unlock() + pc.c.Close() + p.mu.Lock() + p.active-- + } + } + + // Get idle connection from the front of idle list. + for p.idle.front != nil { + pc := p.idle.front + p.idle.popFront() + p.mu.Unlock() + if (p.TestOnBorrow == nil || p.TestOnBorrow(pc.c, pc.t) == nil) && + (p.MaxConnLifetime == 0 || nowFunc().Sub(pc.created) < p.MaxConnLifetime) { + return pc, nil + } + pc.c.Close() + p.mu.Lock() + p.active-- + } + + // Check for pool closed before dialing a new connection. + if p.closed { + p.mu.Unlock() + return nil, errors.New("redigo: get on closed pool") + } + + // Handle limit for p.Wait == false. + if !p.Wait && p.MaxActive > 0 && p.active >= p.MaxActive { + p.mu.Unlock() + return nil, ErrPoolExhausted + } + + p.active++ + p.mu.Unlock() + c, err := p.Dial() + if err != nil { + c = nil + p.mu.Lock() + p.active-- + if p.ch != nil && !p.closed { + p.ch <- struct{}{} + } + p.mu.Unlock() + } + return &poolConn{c: c, created: nowFunc()}, err +} + +func (p *Pool) put(pc *poolConn, forceClose bool) error { + p.mu.Lock() + if !p.closed && !forceClose { + pc.t = nowFunc() + p.idle.pushFront(pc) + if p.idle.count > p.MaxIdle { + pc = p.idle.back + p.idle.popBack() + } else { + pc = nil + } + } + + if pc != nil { + p.mu.Unlock() + pc.c.Close() + p.mu.Lock() + p.active-- + } + + if p.ch != nil && !p.closed { + p.ch <- struct{}{} + } + p.mu.Unlock() + return nil +} + +type activeConn struct { + p *Pool + pc *poolConn + state int +} + +var ( + sentinel []byte + sentinelOnce sync.Once +) + +func initSentinel() { + p := make([]byte, 64) + if _, err := rand.Read(p); err == nil { + sentinel = p + } else { + h := sha1.New() + io.WriteString(h, "Oops, rand failed. Use time instead.") + io.WriteString(h, strconv.FormatInt(time.Now().UnixNano(), 10)) + sentinel = h.Sum(nil) + } +} + +func (ac *activeConn) Close() error { + pc := ac.pc + if pc == nil { + return nil + } + ac.pc = nil + + if ac.state&internal.MultiState != 0 { + pc.c.Send("DISCARD") + ac.state &^= (internal.MultiState | internal.WatchState) + } else if ac.state&internal.WatchState != 0 { + pc.c.Send("UNWATCH") + ac.state &^= internal.WatchState + } + if ac.state&internal.SubscribeState != 0 { + pc.c.Send("UNSUBSCRIBE") + pc.c.Send("PUNSUBSCRIBE") + // To detect the end of the message stream, ask the server to echo + // a sentinel value and read until we see that value. + sentinelOnce.Do(initSentinel) + pc.c.Send("ECHO", sentinel) + pc.c.Flush() + for { + p, err := pc.c.Receive() + if err != nil { + break + } + if p, ok := p.([]byte); ok && bytes.Equal(p, sentinel) { + ac.state &^= internal.SubscribeState + break + } + } + } + pc.c.Do("") + ac.p.put(pc, ac.state != 0 || pc.c.Err() != nil) + return nil +} + +func (ac *activeConn) Err() error { + pc := ac.pc + if pc == nil { + return errConnClosed + } + return pc.c.Err() +} + +func (ac *activeConn) Do(commandName string, args ...interface{}) (reply interface{}, err error) { + pc := ac.pc + if pc == nil { + return nil, errConnClosed + } + ci := internal.LookupCommandInfo(commandName) + ac.state = (ac.state | ci.Set) &^ ci.Clear + return pc.c.Do(commandName, args...) +} + +func (ac *activeConn) DoWithTimeout(timeout time.Duration, commandName string, args ...interface{}) (reply interface{}, err error) { + pc := ac.pc + if pc == nil { + return nil, errConnClosed + } + cwt, ok := pc.c.(ConnWithTimeout) + if !ok { + return nil, errTimeoutNotSupported + } + ci := internal.LookupCommandInfo(commandName) + ac.state = (ac.state | ci.Set) &^ ci.Clear + return cwt.DoWithTimeout(timeout, commandName, args...) +} + +func (ac *activeConn) Send(commandName string, args ...interface{}) error { + pc := ac.pc + if pc == nil { + return errConnClosed + } + ci := internal.LookupCommandInfo(commandName) + ac.state = (ac.state | ci.Set) &^ ci.Clear + return pc.c.Send(commandName, args...) +} + +func (ac *activeConn) Flush() error { + pc := ac.pc + if pc == nil { + return errConnClosed + } + return pc.c.Flush() +} + +func (ac *activeConn) Receive() (reply interface{}, err error) { + pc := ac.pc + if pc == nil { + return nil, errConnClosed + } + return pc.c.Receive() +} + +func (ac *activeConn) ReceiveWithTimeout(timeout time.Duration) (reply interface{}, err error) { + pc := ac.pc + if pc == nil { + return nil, errConnClosed + } + cwt, ok := pc.c.(ConnWithTimeout) + if !ok { + return nil, errTimeoutNotSupported + } + return cwt.ReceiveWithTimeout(timeout) +} + +type errorConn struct{ err error } + +func (ec errorConn) Do(string, ...interface{}) (interface{}, error) { return nil, ec.err } +func (ec errorConn) DoWithTimeout(time.Duration, string, ...interface{}) (interface{}, error) { + return nil, ec.err +} +func (ec errorConn) Send(string, ...interface{}) error { return ec.err } +func (ec errorConn) Err() error { return ec.err } +func (ec errorConn) Close() error { return nil } +func (ec errorConn) Flush() error { return ec.err } +func (ec errorConn) Receive() (interface{}, error) { return nil, ec.err } +func (ec errorConn) ReceiveWithTimeout(time.Duration) (interface{}, error) { return nil, ec.err } + +type idleList struct { + count int + front, back *poolConn +} + +type poolConn struct { + c Conn + t time.Time + created time.Time + next, prev *poolConn +} + +func (l *idleList) pushFront(pc *poolConn) { + pc.next = l.front + pc.prev = nil + if l.count == 0 { + l.back = pc + } else { + l.front.prev = pc + } + l.front = pc + l.count++ + return +} + +func (l *idleList) popFront() { + pc := l.front + l.count-- + if l.count == 0 { + l.front, l.back = nil, nil + } else { + pc.next.prev = nil + l.front = pc.next + } + pc.next, pc.prev = nil, nil +} + +func (l *idleList) popBack() { + pc := l.back + l.count-- + if l.count == 0 { + l.front, l.back = nil, nil + } else { + pc.prev.next = nil + l.back = pc.prev + } + pc.next, pc.prev = nil, nil +} diff --git a/vendor/github.com/gomodule/redigo/redis/pool17.go b/vendor/github.com/gomodule/redigo/redis/pool17.go new file mode 100644 index 00000000000..c1ea18ee37d --- /dev/null +++ b/vendor/github.com/gomodule/redigo/redis/pool17.go @@ -0,0 +1,35 @@ +// Copyright 2018 Gary Burd +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +// +build go1.7 + +package redis + +import "context" + +// GetContext gets a connection using the provided context. +// +// The provided Context must be non-nil. If the context expires before the +// connection is complete, an error is returned. Any expiration on the context +// will not affect the returned connection. +// +// If the function completes without error, then the application must close the +// returned connection. +func (p *Pool) GetContext(ctx context.Context) (Conn, error) { + pc, err := p.get(ctx) + if err != nil { + return errorConn{err}, err + } + return &activeConn{p: p, pc: pc}, nil +} diff --git a/vendor/github.com/gomodule/redigo/redis/pubsub.go b/vendor/github.com/gomodule/redigo/redis/pubsub.go new file mode 100644 index 00000000000..2da60211d9f --- /dev/null +++ b/vendor/github.com/gomodule/redigo/redis/pubsub.go @@ -0,0 +1,148 @@ +// Copyright 2012 Gary Burd +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package redis + +import ( + "errors" + "time" +) + +// Subscription represents a subscribe or unsubscribe notification. +type Subscription struct { + // Kind is "subscribe", "unsubscribe", "psubscribe" or "punsubscribe" + Kind string + + // The channel that was changed. + Channel string + + // The current number of subscriptions for connection. + Count int +} + +// Message represents a message notification. +type Message struct { + // The originating channel. + Channel string + + // The matched pattern, if any + Pattern string + + // The message data. + Data []byte +} + +// Pong represents a pubsub pong notification. +type Pong struct { + Data string +} + +// PubSubConn wraps a Conn with convenience methods for subscribers. +type PubSubConn struct { + Conn Conn +} + +// Close closes the connection. +func (c PubSubConn) Close() error { + return c.Conn.Close() +} + +// Subscribe subscribes the connection to the specified channels. +func (c PubSubConn) Subscribe(channel ...interface{}) error { + c.Conn.Send("SUBSCRIBE", channel...) + return c.Conn.Flush() +} + +// PSubscribe subscribes the connection to the given patterns. +func (c PubSubConn) PSubscribe(channel ...interface{}) error { + c.Conn.Send("PSUBSCRIBE", channel...) + return c.Conn.Flush() +} + +// Unsubscribe unsubscribes the connection from the given channels, or from all +// of them if none is given. +func (c PubSubConn) Unsubscribe(channel ...interface{}) error { + c.Conn.Send("UNSUBSCRIBE", channel...) + return c.Conn.Flush() +} + +// PUnsubscribe unsubscribes the connection from the given patterns, or from all +// of them if none is given. +func (c PubSubConn) PUnsubscribe(channel ...interface{}) error { + c.Conn.Send("PUNSUBSCRIBE", channel...) + return c.Conn.Flush() +} + +// Ping sends a PING to the server with the specified data. +// +// The connection must be subscribed to at least one channel or pattern when +// calling this method. +func (c PubSubConn) Ping(data string) error { + c.Conn.Send("PING", data) + return c.Conn.Flush() +} + +// Receive returns a pushed message as a Subscription, Message, Pong or error. +// The return value is intended to be used directly in a type switch as +// illustrated in the PubSubConn example. +func (c PubSubConn) Receive() interface{} { + return c.receiveInternal(c.Conn.Receive()) +} + +// ReceiveWithTimeout is like Receive, but it allows the application to +// override the connection's default timeout. +func (c PubSubConn) ReceiveWithTimeout(timeout time.Duration) interface{} { + return c.receiveInternal(ReceiveWithTimeout(c.Conn, timeout)) +} + +func (c PubSubConn) receiveInternal(replyArg interface{}, errArg error) interface{} { + reply, err := Values(replyArg, errArg) + if err != nil { + return err + } + + var kind string + reply, err = Scan(reply, &kind) + if err != nil { + return err + } + + switch kind { + case "message": + var m Message + if _, err := Scan(reply, &m.Channel, &m.Data); err != nil { + return err + } + return m + case "pmessage": + var m Message + if _, err := Scan(reply, &m.Pattern, &m.Channel, &m.Data); err != nil { + return err + } + return m + case "subscribe", "psubscribe", "unsubscribe", "punsubscribe": + s := Subscription{Kind: kind} + if _, err := Scan(reply, &s.Channel, &s.Count); err != nil { + return err + } + return s + case "pong": + var p Pong + if _, err := Scan(reply, &p.Data); err != nil { + return err + } + return p + } + return errors.New("redigo: unknown pubsub notification") +} diff --git a/vendor/github.com/gomodule/redigo/redis/redis.go b/vendor/github.com/gomodule/redigo/redis/redis.go new file mode 100644 index 00000000000..141fa4a9121 --- /dev/null +++ b/vendor/github.com/gomodule/redigo/redis/redis.go @@ -0,0 +1,117 @@ +// Copyright 2012 Gary Burd +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package redis + +import ( + "errors" + "time" +) + +// Error represents an error returned in a command reply. +type Error string + +func (err Error) Error() string { return string(err) } + +// Conn represents a connection to a Redis server. +type Conn interface { + // Close closes the connection. + Close() error + + // Err returns a non-nil value when the connection is not usable. + Err() error + + // Do sends a command to the server and returns the received reply. + Do(commandName string, args ...interface{}) (reply interface{}, err error) + + // Send writes the command to the client's output buffer. + Send(commandName string, args ...interface{}) error + + // Flush flushes the output buffer to the Redis server. + Flush() error + + // Receive receives a single reply from the Redis server + Receive() (reply interface{}, err error) +} + +// Argument is the interface implemented by an object which wants to control how +// the object is converted to Redis bulk strings. +type Argument interface { + // RedisArg returns a value to be encoded as a bulk string per the + // conversions listed in the section 'Executing Commands'. + // Implementations should typically return a []byte or string. + RedisArg() interface{} +} + +// Scanner is implemented by an object which wants to control its value is +// interpreted when read from Redis. +type Scanner interface { + // RedisScan assigns a value from a Redis value. The argument src is one of + // the reply types listed in the section `Executing Commands`. + // + // An error should be returned if the value cannot be stored without + // loss of information. + RedisScan(src interface{}) error +} + +// ConnWithTimeout is an optional interface that allows the caller to override +// a connection's default read timeout. This interface is useful for executing +// the BLPOP, BRPOP, BRPOPLPUSH, XREAD and other commands that block at the +// server. +// +// A connection's default read timeout is set with the DialReadTimeout dial +// option. Applications should rely on the default timeout for commands that do +// not block at the server. +// +// All of the Conn implementations in this package satisfy the ConnWithTimeout +// interface. +// +// Use the DoWithTimeout and ReceiveWithTimeout helper functions to simplify +// use of this interface. +type ConnWithTimeout interface { + Conn + + // Do sends a command to the server and returns the received reply. + // The timeout overrides the read timeout set when dialing the + // connection. + DoWithTimeout(timeout time.Duration, commandName string, args ...interface{}) (reply interface{}, err error) + + // Receive receives a single reply from the Redis server. The timeout + // overrides the read timeout set when dialing the connection. + ReceiveWithTimeout(timeout time.Duration) (reply interface{}, err error) +} + +var errTimeoutNotSupported = errors.New("redis: connection does not support ConnWithTimeout") + +// DoWithTimeout executes a Redis command with the specified read timeout. If +// the connection does not satisfy the ConnWithTimeout interface, then an error +// is returned. +func DoWithTimeout(c Conn, timeout time.Duration, cmd string, args ...interface{}) (interface{}, error) { + cwt, ok := c.(ConnWithTimeout) + if !ok { + return nil, errTimeoutNotSupported + } + return cwt.DoWithTimeout(timeout, cmd, args...) +} + +// ReceiveWithTimeout receives a reply with the specified read timeout. If the +// connection does not satisfy the ConnWithTimeout interface, then an error is +// returned. +func ReceiveWithTimeout(c Conn, timeout time.Duration) (interface{}, error) { + cwt, ok := c.(ConnWithTimeout) + if !ok { + return nil, errTimeoutNotSupported + } + return cwt.ReceiveWithTimeout(timeout) +} diff --git a/vendor/github.com/gomodule/redigo/redis/reply.go b/vendor/github.com/gomodule/redigo/redis/reply.go new file mode 100644 index 00000000000..c2b3b2b6e74 --- /dev/null +++ b/vendor/github.com/gomodule/redigo/redis/reply.go @@ -0,0 +1,479 @@ +// Copyright 2012 Gary Burd +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package redis + +import ( + "errors" + "fmt" + "strconv" +) + +// ErrNil indicates that a reply value is nil. +var ErrNil = errors.New("redigo: nil returned") + +// Int is a helper that converts a command reply to an integer. If err is not +// equal to nil, then Int returns 0, err. Otherwise, Int converts the +// reply to an int as follows: +// +// Reply type Result +// integer int(reply), nil +// bulk string parsed reply, nil +// nil 0, ErrNil +// other 0, error +func Int(reply interface{}, err error) (int, error) { + if err != nil { + return 0, err + } + switch reply := reply.(type) { + case int64: + x := int(reply) + if int64(x) != reply { + return 0, strconv.ErrRange + } + return x, nil + case []byte: + n, err := strconv.ParseInt(string(reply), 10, 0) + return int(n), err + case nil: + return 0, ErrNil + case Error: + return 0, reply + } + return 0, fmt.Errorf("redigo: unexpected type for Int, got type %T", reply) +} + +// Int64 is a helper that converts a command reply to 64 bit integer. If err is +// not equal to nil, then Int returns 0, err. Otherwise, Int64 converts the +// reply to an int64 as follows: +// +// Reply type Result +// integer reply, nil +// bulk string parsed reply, nil +// nil 0, ErrNil +// other 0, error +func Int64(reply interface{}, err error) (int64, error) { + if err != nil { + return 0, err + } + switch reply := reply.(type) { + case int64: + return reply, nil + case []byte: + n, err := strconv.ParseInt(string(reply), 10, 64) + return n, err + case nil: + return 0, ErrNil + case Error: + return 0, reply + } + return 0, fmt.Errorf("redigo: unexpected type for Int64, got type %T", reply) +} + +var errNegativeInt = errors.New("redigo: unexpected value for Uint64") + +// Uint64 is a helper that converts a command reply to 64 bit integer. If err is +// not equal to nil, then Int returns 0, err. Otherwise, Int64 converts the +// reply to an int64 as follows: +// +// Reply type Result +// integer reply, nil +// bulk string parsed reply, nil +// nil 0, ErrNil +// other 0, error +func Uint64(reply interface{}, err error) (uint64, error) { + if err != nil { + return 0, err + } + switch reply := reply.(type) { + case int64: + if reply < 0 { + return 0, errNegativeInt + } + return uint64(reply), nil + case []byte: + n, err := strconv.ParseUint(string(reply), 10, 64) + return n, err + case nil: + return 0, ErrNil + case Error: + return 0, reply + } + return 0, fmt.Errorf("redigo: unexpected type for Uint64, got type %T", reply) +} + +// Float64 is a helper that converts a command reply to 64 bit float. If err is +// not equal to nil, then Float64 returns 0, err. Otherwise, Float64 converts +// the reply to an int as follows: +// +// Reply type Result +// bulk string parsed reply, nil +// nil 0, ErrNil +// other 0, error +func Float64(reply interface{}, err error) (float64, error) { + if err != nil { + return 0, err + } + switch reply := reply.(type) { + case []byte: + n, err := strconv.ParseFloat(string(reply), 64) + return n, err + case nil: + return 0, ErrNil + case Error: + return 0, reply + } + return 0, fmt.Errorf("redigo: unexpected type for Float64, got type %T", reply) +} + +// String is a helper that converts a command reply to a string. If err is not +// equal to nil, then String returns "", err. Otherwise String converts the +// reply to a string as follows: +// +// Reply type Result +// bulk string string(reply), nil +// simple string reply, nil +// nil "", ErrNil +// other "", error +func String(reply interface{}, err error) (string, error) { + if err != nil { + return "", err + } + switch reply := reply.(type) { + case []byte: + return string(reply), nil + case string: + return reply, nil + case nil: + return "", ErrNil + case Error: + return "", reply + } + return "", fmt.Errorf("redigo: unexpected type for String, got type %T", reply) +} + +// Bytes is a helper that converts a command reply to a slice of bytes. If err +// is not equal to nil, then Bytes returns nil, err. Otherwise Bytes converts +// the reply to a slice of bytes as follows: +// +// Reply type Result +// bulk string reply, nil +// simple string []byte(reply), nil +// nil nil, ErrNil +// other nil, error +func Bytes(reply interface{}, err error) ([]byte, error) { + if err != nil { + return nil, err + } + switch reply := reply.(type) { + case []byte: + return reply, nil + case string: + return []byte(reply), nil + case nil: + return nil, ErrNil + case Error: + return nil, reply + } + return nil, fmt.Errorf("redigo: unexpected type for Bytes, got type %T", reply) +} + +// Bool is a helper that converts a command reply to a boolean. If err is not +// equal to nil, then Bool returns false, err. Otherwise Bool converts the +// reply to boolean as follows: +// +// Reply type Result +// integer value != 0, nil +// bulk string strconv.ParseBool(reply) +// nil false, ErrNil +// other false, error +func Bool(reply interface{}, err error) (bool, error) { + if err != nil { + return false, err + } + switch reply := reply.(type) { + case int64: + return reply != 0, nil + case []byte: + return strconv.ParseBool(string(reply)) + case nil: + return false, ErrNil + case Error: + return false, reply + } + return false, fmt.Errorf("redigo: unexpected type for Bool, got type %T", reply) +} + +// MultiBulk is a helper that converts an array command reply to a []interface{}. +// +// Deprecated: Use Values instead. +func MultiBulk(reply interface{}, err error) ([]interface{}, error) { return Values(reply, err) } + +// Values is a helper that converts an array command reply to a []interface{}. +// If err is not equal to nil, then Values returns nil, err. Otherwise, Values +// converts the reply as follows: +// +// Reply type Result +// array reply, nil +// nil nil, ErrNil +// other nil, error +func Values(reply interface{}, err error) ([]interface{}, error) { + if err != nil { + return nil, err + } + switch reply := reply.(type) { + case []interface{}: + return reply, nil + case nil: + return nil, ErrNil + case Error: + return nil, reply + } + return nil, fmt.Errorf("redigo: unexpected type for Values, got type %T", reply) +} + +func sliceHelper(reply interface{}, err error, name string, makeSlice func(int), assign func(int, interface{}) error) error { + if err != nil { + return err + } + switch reply := reply.(type) { + case []interface{}: + makeSlice(len(reply)) + for i := range reply { + if reply[i] == nil { + continue + } + if err := assign(i, reply[i]); err != nil { + return err + } + } + return nil + case nil: + return ErrNil + case Error: + return reply + } + return fmt.Errorf("redigo: unexpected type for %s, got type %T", name, reply) +} + +// Float64s is a helper that converts an array command reply to a []float64. If +// err is not equal to nil, then Float64s returns nil, err. Nil array items are +// converted to 0 in the output slice. Floats64 returns an error if an array +// item is not a bulk string or nil. +func Float64s(reply interface{}, err error) ([]float64, error) { + var result []float64 + err = sliceHelper(reply, err, "Float64s", func(n int) { result = make([]float64, n) }, func(i int, v interface{}) error { + p, ok := v.([]byte) + if !ok { + return fmt.Errorf("redigo: unexpected element type for Floats64, got type %T", v) + } + f, err := strconv.ParseFloat(string(p), 64) + result[i] = f + return err + }) + return result, err +} + +// Strings is a helper that converts an array command reply to a []string. If +// err is not equal to nil, then Strings returns nil, err. Nil array items are +// converted to "" in the output slice. Strings returns an error if an array +// item is not a bulk string or nil. +func Strings(reply interface{}, err error) ([]string, error) { + var result []string + err = sliceHelper(reply, err, "Strings", func(n int) { result = make([]string, n) }, func(i int, v interface{}) error { + switch v := v.(type) { + case string: + result[i] = v + return nil + case []byte: + result[i] = string(v) + return nil + default: + return fmt.Errorf("redigo: unexpected element type for Strings, got type %T", v) + } + }) + return result, err +} + +// ByteSlices is a helper that converts an array command reply to a [][]byte. +// If err is not equal to nil, then ByteSlices returns nil, err. Nil array +// items are stay nil. ByteSlices returns an error if an array item is not a +// bulk string or nil. +func ByteSlices(reply interface{}, err error) ([][]byte, error) { + var result [][]byte + err = sliceHelper(reply, err, "ByteSlices", func(n int) { result = make([][]byte, n) }, func(i int, v interface{}) error { + p, ok := v.([]byte) + if !ok { + return fmt.Errorf("redigo: unexpected element type for ByteSlices, got type %T", v) + } + result[i] = p + return nil + }) + return result, err +} + +// Int64s is a helper that converts an array command reply to a []int64. +// If err is not equal to nil, then Int64s returns nil, err. Nil array +// items are stay nil. Int64s returns an error if an array item is not a +// bulk string or nil. +func Int64s(reply interface{}, err error) ([]int64, error) { + var result []int64 + err = sliceHelper(reply, err, "Int64s", func(n int) { result = make([]int64, n) }, func(i int, v interface{}) error { + switch v := v.(type) { + case int64: + result[i] = v + return nil + case []byte: + n, err := strconv.ParseInt(string(v), 10, 64) + result[i] = n + return err + default: + return fmt.Errorf("redigo: unexpected element type for Int64s, got type %T", v) + } + }) + return result, err +} + +// Ints is a helper that converts an array command reply to a []in. +// If err is not equal to nil, then Ints returns nil, err. Nil array +// items are stay nil. Ints returns an error if an array item is not a +// bulk string or nil. +func Ints(reply interface{}, err error) ([]int, error) { + var result []int + err = sliceHelper(reply, err, "Ints", func(n int) { result = make([]int, n) }, func(i int, v interface{}) error { + switch v := v.(type) { + case int64: + n := int(v) + if int64(n) != v { + return strconv.ErrRange + } + result[i] = n + return nil + case []byte: + n, err := strconv.Atoi(string(v)) + result[i] = n + return err + default: + return fmt.Errorf("redigo: unexpected element type for Ints, got type %T", v) + } + }) + return result, err +} + +// StringMap is a helper that converts an array of strings (alternating key, value) +// into a map[string]string. The HGETALL and CONFIG GET commands return replies in this format. +// Requires an even number of values in result. +func StringMap(result interface{}, err error) (map[string]string, error) { + values, err := Values(result, err) + if err != nil { + return nil, err + } + if len(values)%2 != 0 { + return nil, errors.New("redigo: StringMap expects even number of values result") + } + m := make(map[string]string, len(values)/2) + for i := 0; i < len(values); i += 2 { + key, okKey := values[i].([]byte) + value, okValue := values[i+1].([]byte) + if !okKey || !okValue { + return nil, errors.New("redigo: StringMap key not a bulk string value") + } + m[string(key)] = string(value) + } + return m, nil +} + +// IntMap is a helper that converts an array of strings (alternating key, value) +// into a map[string]int. The HGETALL commands return replies in this format. +// Requires an even number of values in result. +func IntMap(result interface{}, err error) (map[string]int, error) { + values, err := Values(result, err) + if err != nil { + return nil, err + } + if len(values)%2 != 0 { + return nil, errors.New("redigo: IntMap expects even number of values result") + } + m := make(map[string]int, len(values)/2) + for i := 0; i < len(values); i += 2 { + key, ok := values[i].([]byte) + if !ok { + return nil, errors.New("redigo: IntMap key not a bulk string value") + } + value, err := Int(values[i+1], nil) + if err != nil { + return nil, err + } + m[string(key)] = value + } + return m, nil +} + +// Int64Map is a helper that converts an array of strings (alternating key, value) +// into a map[string]int64. The HGETALL commands return replies in this format. +// Requires an even number of values in result. +func Int64Map(result interface{}, err error) (map[string]int64, error) { + values, err := Values(result, err) + if err != nil { + return nil, err + } + if len(values)%2 != 0 { + return nil, errors.New("redigo: Int64Map expects even number of values result") + } + m := make(map[string]int64, len(values)/2) + for i := 0; i < len(values); i += 2 { + key, ok := values[i].([]byte) + if !ok { + return nil, errors.New("redigo: Int64Map key not a bulk string value") + } + value, err := Int64(values[i+1], nil) + if err != nil { + return nil, err + } + m[string(key)] = value + } + return m, nil +} + +// Positions is a helper that converts an array of positions (lat, long) +// into a [][2]float64. The GEOPOS command returns replies in this format. +func Positions(result interface{}, err error) ([]*[2]float64, error) { + values, err := Values(result, err) + if err != nil { + return nil, err + } + positions := make([]*[2]float64, len(values)) + for i := range values { + if values[i] == nil { + continue + } + p, ok := values[i].([]interface{}) + if !ok { + return nil, fmt.Errorf("redigo: unexpected element type for interface slice, got type %T", values[i]) + } + if len(p) != 2 { + return nil, fmt.Errorf("redigo: unexpected number of values for a member position, got %d", len(p)) + } + lat, err := Float64(p[0], nil) + if err != nil { + return nil, err + } + long, err := Float64(p[1], nil) + if err != nil { + return nil, err + } + positions[i] = &[2]float64{lat, long} + } + return positions, nil +} diff --git a/vendor/github.com/gomodule/redigo/redis/scan.go b/vendor/github.com/gomodule/redigo/redis/scan.go new file mode 100644 index 00000000000..ef9551bd476 --- /dev/null +++ b/vendor/github.com/gomodule/redigo/redis/scan.go @@ -0,0 +1,585 @@ +// Copyright 2012 Gary Burd +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package redis + +import ( + "errors" + "fmt" + "reflect" + "strconv" + "strings" + "sync" +) + +func ensureLen(d reflect.Value, n int) { + if n > d.Cap() { + d.Set(reflect.MakeSlice(d.Type(), n, n)) + } else { + d.SetLen(n) + } +} + +func cannotConvert(d reflect.Value, s interface{}) error { + var sname string + switch s.(type) { + case string: + sname = "Redis simple string" + case Error: + sname = "Redis error" + case int64: + sname = "Redis integer" + case []byte: + sname = "Redis bulk string" + case []interface{}: + sname = "Redis array" + default: + sname = reflect.TypeOf(s).String() + } + return fmt.Errorf("cannot convert from %s to %s", sname, d.Type()) +} + +func convertAssignBulkString(d reflect.Value, s []byte) (err error) { + switch d.Type().Kind() { + case reflect.Float32, reflect.Float64: + var x float64 + x, err = strconv.ParseFloat(string(s), d.Type().Bits()) + d.SetFloat(x) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + var x int64 + x, err = strconv.ParseInt(string(s), 10, d.Type().Bits()) + d.SetInt(x) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + var x uint64 + x, err = strconv.ParseUint(string(s), 10, d.Type().Bits()) + d.SetUint(x) + case reflect.Bool: + var x bool + x, err = strconv.ParseBool(string(s)) + d.SetBool(x) + case reflect.String: + d.SetString(string(s)) + case reflect.Slice: + if d.Type().Elem().Kind() != reflect.Uint8 { + err = cannotConvert(d, s) + } else { + d.SetBytes(s) + } + default: + err = cannotConvert(d, s) + } + return +} + +func convertAssignInt(d reflect.Value, s int64) (err error) { + switch d.Type().Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + d.SetInt(s) + if d.Int() != s { + err = strconv.ErrRange + d.SetInt(0) + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + if s < 0 { + err = strconv.ErrRange + } else { + x := uint64(s) + d.SetUint(x) + if d.Uint() != x { + err = strconv.ErrRange + d.SetUint(0) + } + } + case reflect.Bool: + d.SetBool(s != 0) + default: + err = cannotConvert(d, s) + } + return +} + +func convertAssignValue(d reflect.Value, s interface{}) (err error) { + if d.Kind() != reflect.Ptr { + if d.CanAddr() { + d2 := d.Addr() + if d2.CanInterface() { + if scanner, ok := d2.Interface().(Scanner); ok { + return scanner.RedisScan(s) + } + } + } + } else if d.CanInterface() { + // Already a reflect.Ptr + if d.IsNil() { + d.Set(reflect.New(d.Type().Elem())) + } + if scanner, ok := d.Interface().(Scanner); ok { + return scanner.RedisScan(s) + } + } + + switch s := s.(type) { + case []byte: + err = convertAssignBulkString(d, s) + case int64: + err = convertAssignInt(d, s) + default: + err = cannotConvert(d, s) + } + return err +} + +func convertAssignArray(d reflect.Value, s []interface{}) error { + if d.Type().Kind() != reflect.Slice { + return cannotConvert(d, s) + } + ensureLen(d, len(s)) + for i := 0; i < len(s); i++ { + if err := convertAssignValue(d.Index(i), s[i]); err != nil { + return err + } + } + return nil +} + +func convertAssign(d interface{}, s interface{}) (err error) { + if scanner, ok := d.(Scanner); ok { + return scanner.RedisScan(s) + } + + // Handle the most common destination types using type switches and + // fall back to reflection for all other types. + switch s := s.(type) { + case nil: + // ignore + case []byte: + switch d := d.(type) { + case *string: + *d = string(s) + case *int: + *d, err = strconv.Atoi(string(s)) + case *bool: + *d, err = strconv.ParseBool(string(s)) + case *[]byte: + *d = s + case *interface{}: + *d = s + case nil: + // skip value + default: + if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr { + err = cannotConvert(d, s) + } else { + err = convertAssignBulkString(d.Elem(), s) + } + } + case int64: + switch d := d.(type) { + case *int: + x := int(s) + if int64(x) != s { + err = strconv.ErrRange + x = 0 + } + *d = x + case *bool: + *d = s != 0 + case *interface{}: + *d = s + case nil: + // skip value + default: + if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr { + err = cannotConvert(d, s) + } else { + err = convertAssignInt(d.Elem(), s) + } + } + case string: + switch d := d.(type) { + case *string: + *d = s + case *interface{}: + *d = s + case nil: + // skip value + default: + err = cannotConvert(reflect.ValueOf(d), s) + } + case []interface{}: + switch d := d.(type) { + case *[]interface{}: + *d = s + case *interface{}: + *d = s + case nil: + // skip value + default: + if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr { + err = cannotConvert(d, s) + } else { + err = convertAssignArray(d.Elem(), s) + } + } + case Error: + err = s + default: + err = cannotConvert(reflect.ValueOf(d), s) + } + return +} + +// Scan copies from src to the values pointed at by dest. +// +// Scan uses RedisScan if available otherwise: +// +// The values pointed at by dest must be an integer, float, boolean, string, +// []byte, interface{} or slices of these types. Scan uses the standard strconv +// package to convert bulk strings to numeric and boolean types. +// +// If a dest value is nil, then the corresponding src value is skipped. +// +// If a src element is nil, then the corresponding dest value is not modified. +// +// To enable easy use of Scan in a loop, Scan returns the slice of src +// following the copied values. +func Scan(src []interface{}, dest ...interface{}) ([]interface{}, error) { + if len(src) < len(dest) { + return nil, errors.New("redigo.Scan: array short") + } + var err error + for i, d := range dest { + err = convertAssign(d, src[i]) + if err != nil { + err = fmt.Errorf("redigo.Scan: cannot assign to dest %d: %v", i, err) + break + } + } + return src[len(dest):], err +} + +type fieldSpec struct { + name string + index []int + omitEmpty bool +} + +type structSpec struct { + m map[string]*fieldSpec + l []*fieldSpec +} + +func (ss *structSpec) fieldSpec(name []byte) *fieldSpec { + return ss.m[string(name)] +} + +func compileStructSpec(t reflect.Type, depth map[string]int, index []int, ss *structSpec) { + for i := 0; i < t.NumField(); i++ { + f := t.Field(i) + switch { + case f.PkgPath != "" && !f.Anonymous: + // Ignore unexported fields. + case f.Anonymous: + // TODO: Handle pointers. Requires change to decoder and + // protection against infinite recursion. + if f.Type.Kind() == reflect.Struct { + compileStructSpec(f.Type, depth, append(index, i), ss) + } + default: + fs := &fieldSpec{name: f.Name} + tag := f.Tag.Get("redis") + p := strings.Split(tag, ",") + if len(p) > 0 { + if p[0] == "-" { + continue + } + if len(p[0]) > 0 { + fs.name = p[0] + } + for _, s := range p[1:] { + switch s { + case "omitempty": + fs.omitEmpty = true + default: + panic(fmt.Errorf("redigo: unknown field tag %s for type %s", s, t.Name())) + } + } + } + d, found := depth[fs.name] + if !found { + d = 1 << 30 + } + switch { + case len(index) == d: + // At same depth, remove from result. + delete(ss.m, fs.name) + j := 0 + for i := 0; i < len(ss.l); i++ { + if fs.name != ss.l[i].name { + ss.l[j] = ss.l[i] + j += 1 + } + } + ss.l = ss.l[:j] + case len(index) < d: + fs.index = make([]int, len(index)+1) + copy(fs.index, index) + fs.index[len(index)] = i + depth[fs.name] = len(index) + ss.m[fs.name] = fs + ss.l = append(ss.l, fs) + } + } + } +} + +var ( + structSpecMutex sync.RWMutex + structSpecCache = make(map[reflect.Type]*structSpec) + defaultFieldSpec = &fieldSpec{} +) + +func structSpecForType(t reflect.Type) *structSpec { + + structSpecMutex.RLock() + ss, found := structSpecCache[t] + structSpecMutex.RUnlock() + if found { + return ss + } + + structSpecMutex.Lock() + defer structSpecMutex.Unlock() + ss, found = structSpecCache[t] + if found { + return ss + } + + ss = &structSpec{m: make(map[string]*fieldSpec)} + compileStructSpec(t, make(map[string]int), nil, ss) + structSpecCache[t] = ss + return ss +} + +var errScanStructValue = errors.New("redigo.ScanStruct: value must be non-nil pointer to a struct") + +// ScanStruct scans alternating names and values from src to a struct. The +// HGETALL and CONFIG GET commands return replies in this format. +// +// ScanStruct uses exported field names to match values in the response. Use +// 'redis' field tag to override the name: +// +// Field int `redis:"myName"` +// +// Fields with the tag redis:"-" are ignored. +// +// Each field uses RedisScan if available otherwise: +// Integer, float, boolean, string and []byte fields are supported. Scan uses the +// standard strconv package to convert bulk string values to numeric and +// boolean types. +// +// If a src element is nil, then the corresponding field is not modified. +func ScanStruct(src []interface{}, dest interface{}) error { + d := reflect.ValueOf(dest) + if d.Kind() != reflect.Ptr || d.IsNil() { + return errScanStructValue + } + d = d.Elem() + if d.Kind() != reflect.Struct { + return errScanStructValue + } + ss := structSpecForType(d.Type()) + + if len(src)%2 != 0 { + return errors.New("redigo.ScanStruct: number of values not a multiple of 2") + } + + for i := 0; i < len(src); i += 2 { + s := src[i+1] + if s == nil { + continue + } + name, ok := src[i].([]byte) + if !ok { + return fmt.Errorf("redigo.ScanStruct: key %d not a bulk string value", i) + } + fs := ss.fieldSpec(name) + if fs == nil { + continue + } + if err := convertAssignValue(d.FieldByIndex(fs.index), s); err != nil { + return fmt.Errorf("redigo.ScanStruct: cannot assign field %s: %v", fs.name, err) + } + } + return nil +} + +var ( + errScanSliceValue = errors.New("redigo.ScanSlice: dest must be non-nil pointer to a struct") +) + +// ScanSlice scans src to the slice pointed to by dest. The elements the dest +// slice must be integer, float, boolean, string, struct or pointer to struct +// values. +// +// Struct fields must be integer, float, boolean or string values. All struct +// fields are used unless a subset is specified using fieldNames. +func ScanSlice(src []interface{}, dest interface{}, fieldNames ...string) error { + d := reflect.ValueOf(dest) + if d.Kind() != reflect.Ptr || d.IsNil() { + return errScanSliceValue + } + d = d.Elem() + if d.Kind() != reflect.Slice { + return errScanSliceValue + } + + isPtr := false + t := d.Type().Elem() + if t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct { + isPtr = true + t = t.Elem() + } + + if t.Kind() != reflect.Struct { + ensureLen(d, len(src)) + for i, s := range src { + if s == nil { + continue + } + if err := convertAssignValue(d.Index(i), s); err != nil { + return fmt.Errorf("redigo.ScanSlice: cannot assign element %d: %v", i, err) + } + } + return nil + } + + ss := structSpecForType(t) + fss := ss.l + if len(fieldNames) > 0 { + fss = make([]*fieldSpec, len(fieldNames)) + for i, name := range fieldNames { + fss[i] = ss.m[name] + if fss[i] == nil { + return fmt.Errorf("redigo.ScanSlice: ScanSlice bad field name %s", name) + } + } + } + + if len(fss) == 0 { + return errors.New("redigo.ScanSlice: no struct fields") + } + + n := len(src) / len(fss) + if n*len(fss) != len(src) { + return errors.New("redigo.ScanSlice: length not a multiple of struct field count") + } + + ensureLen(d, n) + for i := 0; i < n; i++ { + d := d.Index(i) + if isPtr { + if d.IsNil() { + d.Set(reflect.New(t)) + } + d = d.Elem() + } + for j, fs := range fss { + s := src[i*len(fss)+j] + if s == nil { + continue + } + if err := convertAssignValue(d.FieldByIndex(fs.index), s); err != nil { + return fmt.Errorf("redigo.ScanSlice: cannot assign element %d to field %s: %v", i*len(fss)+j, fs.name, err) + } + } + } + return nil +} + +// Args is a helper for constructing command arguments from structured values. +type Args []interface{} + +// Add returns the result of appending value to args. +func (args Args) Add(value ...interface{}) Args { + return append(args, value...) +} + +// AddFlat returns the result of appending the flattened value of v to args. +// +// Maps are flattened by appending the alternating keys and map values to args. +// +// Slices are flattened by appending the slice elements to args. +// +// Structs are flattened by appending the alternating names and values of +// exported fields to args. If v is a nil struct pointer, then nothing is +// appended. The 'redis' field tag overrides struct field names. See ScanStruct +// for more information on the use of the 'redis' field tag. +// +// Other types are appended to args as is. +func (args Args) AddFlat(v interface{}) Args { + rv := reflect.ValueOf(v) + switch rv.Kind() { + case reflect.Struct: + args = flattenStruct(args, rv) + case reflect.Slice: + for i := 0; i < rv.Len(); i++ { + args = append(args, rv.Index(i).Interface()) + } + case reflect.Map: + for _, k := range rv.MapKeys() { + args = append(args, k.Interface(), rv.MapIndex(k).Interface()) + } + case reflect.Ptr: + if rv.Type().Elem().Kind() == reflect.Struct { + if !rv.IsNil() { + args = flattenStruct(args, rv.Elem()) + } + } else { + args = append(args, v) + } + default: + args = append(args, v) + } + return args +} + +func flattenStruct(args Args, v reflect.Value) Args { + ss := structSpecForType(v.Type()) + for _, fs := range ss.l { + fv := v.FieldByIndex(fs.index) + if fs.omitEmpty { + var empty = false + switch fv.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + empty = fv.Len() == 0 + case reflect.Bool: + empty = !fv.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + empty = fv.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + empty = fv.Uint() == 0 + case reflect.Float32, reflect.Float64: + empty = fv.Float() == 0 + case reflect.Interface, reflect.Ptr: + empty = fv.IsNil() + } + if empty { + continue + } + } + args = append(args, fs.name, fv.Interface()) + } + return args +} diff --git a/vendor/github.com/gomodule/redigo/redis/script.go b/vendor/github.com/gomodule/redigo/redis/script.go new file mode 100644 index 00000000000..0ef1c821f71 --- /dev/null +++ b/vendor/github.com/gomodule/redigo/redis/script.go @@ -0,0 +1,91 @@ +// Copyright 2012 Gary Burd +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package redis + +import ( + "crypto/sha1" + "encoding/hex" + "io" + "strings" +) + +// Script encapsulates the source, hash and key count for a Lua script. See +// http://redis.io/commands/eval for information on scripts in Redis. +type Script struct { + keyCount int + src string + hash string +} + +// NewScript returns a new script object. If keyCount is greater than or equal +// to zero, then the count is automatically inserted in the EVAL command +// argument list. If keyCount is less than zero, then the application supplies +// the count as the first value in the keysAndArgs argument to the Do, Send and +// SendHash methods. +func NewScript(keyCount int, src string) *Script { + h := sha1.New() + io.WriteString(h, src) + return &Script{keyCount, src, hex.EncodeToString(h.Sum(nil))} +} + +func (s *Script) args(spec string, keysAndArgs []interface{}) []interface{} { + var args []interface{} + if s.keyCount < 0 { + args = make([]interface{}, 1+len(keysAndArgs)) + args[0] = spec + copy(args[1:], keysAndArgs) + } else { + args = make([]interface{}, 2+len(keysAndArgs)) + args[0] = spec + args[1] = s.keyCount + copy(args[2:], keysAndArgs) + } + return args +} + +// Hash returns the script hash. +func (s *Script) Hash() string { + return s.hash +} + +// Do evaluates the script. Under the covers, Do optimistically evaluates the +// script using the EVALSHA command. If the command fails because the script is +// not loaded, then Do evaluates the script using the EVAL command (thus +// causing the script to load). +func (s *Script) Do(c Conn, keysAndArgs ...interface{}) (interface{}, error) { + v, err := c.Do("EVALSHA", s.args(s.hash, keysAndArgs)...) + if e, ok := err.(Error); ok && strings.HasPrefix(string(e), "NOSCRIPT ") { + v, err = c.Do("EVAL", s.args(s.src, keysAndArgs)...) + } + return v, err +} + +// SendHash evaluates the script without waiting for the reply. The script is +// evaluated with the EVALSHA command. The application must ensure that the +// script is loaded by a previous call to Send, Do or Load methods. +func (s *Script) SendHash(c Conn, keysAndArgs ...interface{}) error { + return c.Send("EVALSHA", s.args(s.hash, keysAndArgs)...) +} + +// Send evaluates the script without waiting for the reply. +func (s *Script) Send(c Conn, keysAndArgs ...interface{}) error { + return c.Send("EVAL", s.args(s.src, keysAndArgs)...) +} + +// Load loads the script without evaluating it. +func (s *Script) Load(c Conn) error { + _, err := c.Do("SCRIPT", "LOAD", s.src) + return err +} diff --git a/vendor/github.com/yuin/gopher-lua/.travis.yml b/vendor/github.com/yuin/gopher-lua/.travis.yml new file mode 100644 index 00000000000..68df5e7b1a1 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/.travis.yml @@ -0,0 +1,18 @@ +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/LICENSE b/vendor/github.com/yuin/gopher-lua/LICENSE new file mode 100644 index 00000000000..4daf480a2fd --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Yusuke Inuzuka + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/yuin/gopher-lua/Makefile b/vendor/github.com/yuin/gopher-lua/Makefile new file mode 100644 index 00000000000..6d9e55c35f7 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/Makefile @@ -0,0 +1,10 @@ +.PHONY: build test glua + +build: + ./_tools/go-inline *.go && go fmt . && go build + +glua: *.go pm/*.go cmd/glua/glua.go + ./_tools/go-inline *.go && go fmt . && go build cmd/glua/glua.go + +test: + ./_tools/go-inline *.go && go fmt . && go test diff --git a/vendor/github.com/yuin/gopher-lua/README.rst b/vendor/github.com/yuin/gopher-lua/README.rst new file mode 100644 index 00000000000..b479e46357e --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/README.rst @@ -0,0 +1,887 @@ + +=============================================================================== +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://travis-ci.org/yuin/gopher-lua.svg + :target: https://travis-ci.org/yuin/gopher-lua + +.. image:: https://coveralls.io/repos/yuin/gopher-lua/badge.svg + :target: https://coveralls.io/r/yuin/gopher-lua + +.. image:: https://badges.gitter.im/Join%20Chat.svg + :alt: Join the chat at https://gitter.im/yuin/gopher-lua + :target: https://gitter.im/yuin/gopher-lua?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge + +| + + +GopherLua is a Lua5.1 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. + +.. contents:: + :depth: 1 + +---------------------------------------------------------------- +Design principle +---------------------------------------------------------------- + +- Be a scripting language with extensible semantics. +- User-friendly Go API + - The stack based API like the one used in the original Lua + implementation will cause a performance improvements in GopherLua + (It will reduce memory allocations and concrete type <-> interface conversions). + GopherLua API is **not** the stack based API. + GopherLua give preference to the user-friendliness over the performance. + +---------------------------------------------------------------- +How about performance? +---------------------------------------------------------------- +GopherLua is not fast but not too slow, I think. + +GopherLua has almost equivalent ( or little bit better ) performance as Python3 on micro benchmarks. + +There are some benchmarks on the `wiki page `_ . + +---------------------------------------------------------------- +Installation +---------------------------------------------------------------- + +.. code-block:: bash + + go get github.com/yuin/gopher-lua + +GopherLua supports >= Go1.9. + +---------------------------------------------------------------- +Usage +---------------------------------------------------------------- +GopherLua APIs perform in much the same way as Lua, **but the stack is used only +for passing arguments and receiving returned values.** + +GopherLua supports channel operations. See **"Goroutines"** section. + +Import a package. + +.. code-block:: go + + import ( + "github.com/yuin/gopher-lua" + ) + +Run scripts in the VM. + +.. code-block:: go + + L := lua.NewState() + defer L.Close() + if err := L.DoString(`print("hello")`); err != nil { + panic(err) + } + +.. code-block:: go + + L := lua.NewState() + defer L.Close() + if err := L.DoFile("hello.lua"); err != nil { + panic(err) + } + +Refer to `Lua Reference Manual `_ and `Go doc `_ for further information. + +Note that elements that are not commented in `Go doc `_ equivalent to `Lua Reference Manual `_ , except GopherLua uses objects instead of Lua stack indices. + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Data model +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +All data in a GopherLua program is an ``LValue`` . ``LValue`` is an interface +type that has following methods. + +- ``String() string`` +- ``Type() LValueType`` + + +Objects implement an LValue interface are + +================ ========================= ================== ======================= + Type name Go type Type() value Constants +================ ========================= ================== ======================= + ``LNilType`` (constants) ``LTNil`` ``LNil`` + ``LBool`` (constants) ``LTBool`` ``LTrue``, ``LFalse`` + ``LNumber`` float64 ``LTNumber`` ``-`` + ``LString`` string ``LTString`` ``-`` + ``LFunction`` struct pointer ``LTFunction`` ``-`` + ``LUserData`` struct pointer ``LTUserData`` ``-`` + ``LState`` struct pointer ``LTThread`` ``-`` + ``LTable`` struct pointer ``LTTable`` ``-`` + ``LChannel`` chan LValue ``LTChannel`` ``-`` +================ ========================= ================== ======================= + +You can test an object type in Go way(type assertion) or using a ``Type()`` value. + +.. code-block:: go + + lv := L.Get(-1) // get the value at the top of the stack + if str, ok := lv.(lua.LString); ok { + // lv is LString + fmt.Println(string(str)) + } + if lv.Type() != lua.LTString { + panic("string required.") + } + +.. code-block:: go + + lv := L.Get(-1) // get the value at the top of the stack + if tbl, ok := lv.(*lua.LTable); ok { + // lv is LTable + fmt.Println(L.ObjLen(tbl)) + } + +Note that ``LBool`` , ``LNumber`` , ``LString`` is not a pointer. + +To test ``LNilType`` and ``LBool``, You **must** use pre-defined constants. + +.. code-block:: go + + lv := L.Get(-1) // get the value at the top of the stack + + if lv == lua.LTrue { // correct + } + + if bl, ok := lv.(lua.LBool); ok && bool(bl) { // wrong + } + +In Lua, both ``nil`` and ``false`` make a condition false. ``LVIsFalse`` and ``LVAsBool`` implement this specification. + +.. code-block:: go + + lv := L.Get(-1) // get the value at the top of the stack + if lua.LVIsFalse(lv) { // lv is nil or false + } + + if lua.LVAsBool(lv) { // lv is neither nil nor false + } + +Objects that based on go structs(``LFunction``. ``LUserData``, ``LTable``) +have some public methods and fields. You can use these methods and fields for +performance and debugging, but there are some limitations. + +- Metatable does not work. +- No error handlings. + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Callstack & Registry size +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The size of an ``LState``'s callstack controls the maximum call depth for Lua functions within a script (Go function calls do not count). + +The registry of an ``LState`` implements stack storage for calling functions (both Lua and Go functions) and also for temporary variables in expressions. Its storage requirements will increase with callstack usage and also with code complexity. + +Both the registry and the callstack can be set to either a fixed size or to auto size. + +When you have a large number of ``LStates`` instantiated in a process, it's worth taking the time to tune the registry and callstack options. + ++++++++++ +Registry ++++++++++ + +The registry can have an initial size, a maximum size and a step size configured on a per ``LState`` basis. This will allow the registry to grow as needed. It will not shrink again after growing. + +.. code-block:: go + + L := lua.NewState(lua.Options{ + RegistrySize: 1024 * 20, // this is the initial size of the registry + RegistryMaxSize: 1024 * 80, // this is the maximum size that the registry can grow to. If set to `0` (the default) then the registry will not auto grow + RegistryGrowStep: 32, // this is how much to step up the registry by each time it runs out of space. The default is `32`. + }) + defer L.Close() + +A registry which is too small for a given script will ultimately result in a panic. A registry which is too big will waste memory (which can be significant if many ``LStates`` are instantiated). +Auto growing registries incur a small performance hit at the point they are resized but will not otherwise affect performance. + ++++++++++ +Callstack ++++++++++ + +The callstack can operate in two different modes, fixed or auto size. +A fixed size callstack has the highest performance and has a fixed memory overhead. +An auto sizing callstack will allocate and release callstack pages on demand which will ensure the minimum amount of memory is in use at any time. The downside is it will incur a small performance impact every time a new page of callframes is allocated. +By default an ``LState`` will allocate and free callstack frames in pages of 8, so the allocation overhead is not incurred on every function call. It is very likely that the performance impact of an auto resizing callstack will be negligible for most use cases. + +.. code-block:: go + + L := lua.NewState(lua.Options{ + CallStackSize: 120, // this is the maximum callstack size of this LState + MinimizeStackMemory: true, // Defaults to `false` if not specified. If set, the callstack will auto grow and shrink as needed up to a max of `CallStackSize`. If not set, the callstack will be fixed at `CallStackSize`. + }) + defer L.Close() + +++++++++++++++++ +Option defaults +++++++++++++++++ + +The above examples show how to customize the callstack and registry size on a per ``LState`` basis. You can also adjust some defaults for when options are not specified by altering the values of ``lua.RegistrySize``, ``lua.RegistryGrowStep`` and ``lua.CallStackSize``. + +An ``LState`` object that has been created by ``*LState#NewThread()`` inherits the callstack & registry size from the parent ``LState`` object. + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Miscellaneous lua.NewState options +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +- **Options.SkipOpenLibs bool(default false)** + - By default, GopherLua opens all built-in libraries when new LState is created. + - You can skip this behaviour by setting this to ``true`` . + - Using the various `OpenXXX(L *LState) int` functions you can open only those libraries that you require, for an example see below. +- **Options.IncludeGoStackTrace bool(default false)** + - By default, GopherLua does not show Go stack traces when panics occur. + - You can get Go stack traces by setting this to ``true`` . + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +API +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Refer to `Lua Reference Manual `_ and `Go doc(LState methods) `_ for further information. + ++++++++++++++++++++++++++++++++++++++++++ +Calling Go from Lua ++++++++++++++++++++++++++++++++++++++++++ + +.. code-block:: go + + func Double(L *lua.LState) int { + lv := L.ToInt(1) /* get argument */ + L.Push(lua.LNumber(lv * 2)) /* push result */ + return 1 /* number of results */ + } + + func main() { + L := lua.NewState() + defer L.Close() + L.SetGlobal("double", L.NewFunction(Double)) /* Original lua_setglobal uses stack... */ + } + +.. code-block:: lua + + print(double(20)) -- > "40" + +Any function registered with GopherLua is a ``lua.LGFunction``, defined in ``value.go`` + +.. code-block:: go + + type LGFunction func(*LState) int + +Working with coroutines. + +.. code-block:: go + + co, _ := L.NewThread() /* create a new thread */ + fn := L.GetGlobal("coro").(*lua.LFunction) /* get function from lua */ + for { + st, err, values := L.Resume(co, fn) + if st == lua.ResumeError { + fmt.Println("yield break(error)") + fmt.Println(err.Error()) + break + } + + for i, lv := range values { + fmt.Printf("%v : %v\n", i, lv) + } + + if st == lua.ResumeOK { + fmt.Println("yield break(ok)") + break + } + } + ++++++++++++++++++++++++++++++++++++++++++ +Opening a subset of builtin modules ++++++++++++++++++++++++++++++++++++++++++ + +The following demonstrates how to open a subset of the built-in modules in Lua, say for example to avoid enabling modules with access to local files or system calls. + +main.go + +.. code-block:: go + + func main() { + L := lua.NewState(lua.Options{SkipOpenLibs: true}) + defer L.Close() + for _, pair := range []struct { + n string + f lua.LGFunction + }{ + {lua.LoadLibName, lua.OpenPackage}, // Must be first + {lua.BaseLibName, lua.OpenBase}, + {lua.TabLibName, lua.OpenTable}, + } { + if err := L.CallByParam(lua.P{ + Fn: L.NewFunction(pair.f), + NRet: 0, + Protect: true, + }, lua.LString(pair.n)); err != nil { + panic(err) + } + } + if err := L.DoFile("main.lua"); err != nil { + panic(err) + } + } + ++++++++++++++++++++++++++++++++++++++++++ +Creating a module by Go ++++++++++++++++++++++++++++++++++++++++++ + +mymodule.go + +.. code-block:: go + + package mymodule + + import ( + "github.com/yuin/gopher-lua" + ) + + func Loader(L *lua.LState) int { + // register functions to the table + mod := L.SetFuncs(L.NewTable(), exports) + // register other stuff + L.SetField(mod, "name", lua.LString("value")) + + // returns the module + L.Push(mod) + return 1 + } + + var exports = map[string]lua.LGFunction{ + "myfunc": myfunc, + } + + func myfunc(L *lua.LState) int { + return 0 + } + +mymain.go + +.. code-block:: go + + package main + + import ( + "./mymodule" + "github.com/yuin/gopher-lua" + ) + + func main() { + L := lua.NewState() + defer L.Close() + L.PreloadModule("mymodule", mymodule.Loader) + if err := L.DoFile("main.lua"); err != nil { + panic(err) + } + } + +main.lua + +.. code-block:: lua + + local m = require("mymodule") + m.myfunc() + print(m.name) + + ++++++++++++++++++++++++++++++++++++++++++ +Calling Lua from Go ++++++++++++++++++++++++++++++++++++++++++ + +.. code-block:: go + + L := lua.NewState() + defer L.Close() + if err := L.DoFile("double.lua"); err != nil { + panic(err) + } + if err := L.CallByParam(lua.P{ + Fn: L.GetGlobal("double"), + NRet: 1, + Protect: true, + }, lua.LNumber(10)); err != nil { + panic(err) + } + ret := L.Get(-1) // returned value + L.Pop(1) // remove received value + +If ``Protect`` is false, GopherLua will panic instead of returning an ``error`` value. + ++++++++++++++++++++++++++++++++++++++++++ +User-Defined types ++++++++++++++++++++++++++++++++++++++++++ +You can extend GopherLua with new types written in Go. +``LUserData`` is provided for this purpose. + +.. code-block:: go + + type Person struct { + Name string + } + + const luaPersonTypeName = "person" + + // Registers my person type to given L. + func registerPersonType(L *lua.LState) { + mt := L.NewTypeMetatable(luaPersonTypeName) + L.SetGlobal("person", mt) + // static attributes + L.SetField(mt, "new", L.NewFunction(newPerson)) + // methods + L.SetField(mt, "__index", L.SetFuncs(L.NewTable(), personMethods)) + } + + // Constructor + func newPerson(L *lua.LState) int { + person := &Person{L.CheckString(1)} + ud := L.NewUserData() + ud.Value = person + L.SetMetatable(ud, L.GetTypeMetatable(luaPersonTypeName)) + L.Push(ud) + return 1 + } + + // Checks whether the first lua argument is a *LUserData with *Person and returns this *Person. + func checkPerson(L *lua.LState) *Person { + ud := L.CheckUserData(1) + if v, ok := ud.Value.(*Person); ok { + return v + } + L.ArgError(1, "person expected") + return nil + } + + var personMethods = map[string]lua.LGFunction{ + "name": personGetSetName, + } + + // Getter and setter for the Person#Name + func personGetSetName(L *lua.LState) int { + p := checkPerson(L) + if L.GetTop() == 2 { + p.Name = L.CheckString(2) + return 0 + } + L.Push(lua.LString(p.Name)) + return 1 + } + + func main() { + L := lua.NewState() + defer L.Close() + registerPersonType(L) + if err := L.DoString(` + p = person.new("Steeve") + print(p:name()) -- "Steeve" + p:name("Alice") + print(p:name()) -- "Alice" + `); err != nil { + panic(err) + } + } + ++++++++++++++++++++++++++++++++++++++++++ +Terminating a running LState ++++++++++++++++++++++++++++++++++++++++++ +GopherLua supports the `Go Concurrency Patterns: Context `_ . + + +.. code-block:: go + + L := lua.NewState() + defer L.Close() + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + // set the context to our LState + L.SetContext(ctx) + err := L.DoString(` + local clock = os.clock + function sleep(n) -- seconds + local t0 = clock() + while clock() - t0 <= n do end + end + sleep(3) + `) + // err.Error() contains "context deadline exceeded" + +With coroutines + +.. code-block:: go + + L := lua.NewState() + defer L.Close() + ctx, cancel := context.WithCancel(context.Background()) + L.SetContext(ctx) + defer cancel() + L.DoString(` + function coro() + local i = 0 + while true do + coroutine.yield(i) + i = i+1 + end + return i + end + `) + co, cocancel := L.NewThread() + defer cocancel() + fn := L.GetGlobal("coro").(*LFunction) + + _, err, values := L.Resume(co, fn) // err is nil + + cancel() // cancel the parent context + + _, err, values = L.Resume(co, fn) // err is NOT nil : child context was canceled + +**Note that using a context causes performance degradation.** + +.. code-block:: + + time ./glua-with-context.exe fib.lua + 9227465 + 0.01s user 0.11s system 1% cpu 7.505 total + + time ./glua-without-context.exe fib.lua + 9227465 + 0.01s user 0.01s system 0% cpu 5.306 total + ++++++++++++++++++++++++++++++++++++++++++ +Sharing Lua byte code between LStates ++++++++++++++++++++++++++++++++++++++++++ +Calling ``DoFile`` will load a Lua script, compile it to byte code and run the byte code in a ``LState``. + +If you have multiple ``LStates`` which are all required to run the same script, you can share the byte code between them, +which will save on memory. +Sharing byte code is safe as it is read only and cannot be altered by lua scripts. + +.. code-block:: go + + // CompileLua reads the passed lua file from disk and compiles it. + func CompileLua(filePath string) (*lua.FunctionProto, error) { + file, err := os.Open(filePath) + defer file.Close() + if err != nil { + return nil, err + } + reader := bufio.NewReader(file) + chunk, err := parse.Parse(reader, filePath) + if err != nil { + return nil, err + } + proto, err := lua.Compile(chunk, filePath) + if err != nil { + return nil, err + } + return proto, nil + } + + // DoCompiledFile takes a FunctionProto, as returned by CompileLua, and runs it in the LState. It is equivalent + // to calling DoFile on the LState with the original source file. + func DoCompiledFile(L *lua.LState, proto *lua.FunctionProto) error { + lfunc := L.NewFunctionFromProto(proto) + L.Push(lfunc) + return L.PCall(0, lua.MultRet, nil) + } + + // Example shows how to share the compiled byte code from a lua script between multiple VMs. + func Example() { + codeToShare := CompileLua("mylua.lua") + a := lua.NewState() + b := lua.NewState() + c := lua.NewState() + DoCompiledFile(a, codeToShare) + DoCompiledFile(b, codeToShare) + DoCompiledFile(c, codeToShare) + } + ++++++++++++++++++++++++++++++++++++++++++ +Goroutines ++++++++++++++++++++++++++++++++++++++++++ +The ``LState`` is not goroutine-safe. It is recommended to use one LState per goroutine and communicate between goroutines by using channels. + +Channels are represented by ``channel`` objects in GopherLua. And a ``channel`` table provides functions for performing channel operations. + +Some objects can not be sent over channels due to having non-goroutine-safe objects inside itself. + +- a thread(state) +- a function +- an userdata +- a table with a metatable + +You **must not** send these objects from Go APIs to channels. + + + +.. code-block:: go + + func receiver(ch, quit chan lua.LValue) { + L := lua.NewState() + defer L.Close() + L.SetGlobal("ch", lua.LChannel(ch)) + L.SetGlobal("quit", lua.LChannel(quit)) + if err := L.DoString(` + local exit = false + while not exit do + channel.select( + {"|<-", ch, function(ok, v) + if not ok then + print("channel closed") + exit = true + else + print("received:", v) + end + end}, + {"|<-", quit, function(ok, v) + print("quit") + exit = true + end} + ) + end + `); err != nil { + panic(err) + } + } + + func sender(ch, quit chan lua.LValue) { + L := lua.NewState() + defer L.Close() + L.SetGlobal("ch", lua.LChannel(ch)) + L.SetGlobal("quit", lua.LChannel(quit)) + if err := L.DoString(` + ch:send("1") + ch:send("2") + `); err != nil { + panic(err) + } + ch <- lua.LString("3") + quit <- lua.LTrue + } + + func main() { + ch := make(chan lua.LValue) + quit := make(chan lua.LValue) + go receiver(ch, quit) + go sender(ch, quit) + time.Sleep(3 * time.Second) + } + +''''''''''''''' +Go API +''''''''''''''' + +``ToChannel``, ``CheckChannel``, ``OptChannel`` are available. + +Refer to `Go doc(LState methods) `_ for further information. + +''''''''''''''' +Lua API +''''''''''''''' + +- **channel.make([buf:int]) -> ch:channel** + - Create new channel that has a buffer size of ``buf``. By default, ``buf`` is 0. + +- **channel.select(case:table [, case:table, case:table ...]) -> {index:int, recv:any, ok}** + - Same as the ``select`` statement in Go. It returns the index of the chosen case and, if that + case was a receive operation, the value received and a boolean indicating whether the channel has been closed. + - ``case`` is a table that outlined below. + - receiving: `{"|<-", ch:channel [, handler:func(ok, data:any)]}` + - sending: `{"<-|", ch:channel, data:any [, handler:func(data:any)]}` + - default: `{"default" [, handler:func()]}` + +``channel.select`` examples: + +.. code-block:: lua + + local idx, recv, ok = channel.select( + {"|<-", ch1}, + {"|<-", ch2} + ) + if not ok then + print("closed") + elseif idx == 1 then -- received from ch1 + print(recv) + elseif idx == 2 then -- received from ch2 + print(recv) + end + +.. code-block:: lua + + channel.select( + {"|<-", ch1, function(ok, data) + print(ok, data) + end}, + {"<-|", ch2, "value", function(data) + print(data) + end}, + {"default", function() + print("default action") + end} + ) + +- **channel:send(data:any)** + - Send ``data`` over the channel. +- **channel:receive() -> ok:bool, data:any** + - Receive some data over the channel. +- **channel:close()** + - Close the channel. + +'''''''''''''''''''''''''''''' +The LState pool pattern +'''''''''''''''''''''''''''''' +To create per-thread LState instances, You can use the ``sync.Pool`` like mechanism. + +.. code-block:: go + + type lStatePool struct { + m sync.Mutex + saved []*lua.LState + } + + func (pl *lStatePool) Get() *lua.LState { + pl.m.Lock() + defer pl.m.Unlock() + n := len(pl.saved) + if n == 0 { + return pl.New() + } + x := pl.saved[n-1] + pl.saved = pl.saved[0 : n-1] + return x + } + + func (pl *lStatePool) New() *lua.LState { + L := lua.NewState() + // setting the L up here. + // load scripts, set global variables, share channels, etc... + return L + } + + func (pl *lStatePool) Put(L *lua.LState) { + pl.m.Lock() + defer pl.m.Unlock() + pl.saved = append(pl.saved, L) + } + + func (pl *lStatePool) Shutdown() { + for _, L := range pl.saved { + L.Close() + } + } + + // Global LState pool + var luaPool = &lStatePool{ + saved: make([]*lua.LState, 0, 4), + } + +Now, you can get per-thread LState objects from the ``luaPool`` . + +.. code-block:: go + + func MyWorker() { + L := luaPool.Get() + defer luaPool.Put(L) + /* your code here */ + } + + func main() { + defer luaPool.Shutdown() + go MyWorker() + go MyWorker() + /* etc... */ + } + + +---------------------------------------------------------------- +Differences between Lua and GopherLua +---------------------------------------------------------------- +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Goroutines +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- GopherLua supports channel operations. + - GopherLua has a type named ``channel``. + - The ``channel`` table provides functions for performing channel operations. + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Unsupported functions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- ``string.dump`` +- ``os.setlocale`` +- ``lua_Debug.namewhat`` +- ``package.loadlib`` +- debug hooks + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Miscellaneous notes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- ``collectgarbage`` does not take any arguments and runs the garbage collector for the entire Go program. +- ``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)`` + +---------------------------------------------------------------- +Standalone interpreter +---------------------------------------------------------------- +Lua has an interpreter called ``lua`` . GopherLua has an interpreter called ``glua`` . + +.. code-block:: bash + + go get github.com/yuin/gopher-lua/cmd/glua + +``glua`` has same options as ``lua`` . + +---------------------------------------------------------------- +How to Contribute +---------------------------------------------------------------- +See `Guidlines for contributors `_ . + +---------------------------------------------------------------- +Libraries for GopherLua +---------------------------------------------------------------- + +- `gopher-luar `_ : Simplifies data passing to and from gopher-lua +- `gluamapper `_ : Mapping a Lua table to a Go struct +- `gluare `_ : Regular expressions for gopher-lua +- `gluahttp `_ : HTTP request module for gopher-lua +- `gopher-json `_ : A simple JSON encoder/decoder for gopher-lua +- `gluayaml `_ : Yaml parser for gopher-lua +- `glua-lfs `_ : Partially implements the luafilesystem module for gopher-lua +- `gluaurl `_ : A url parser/builder module for gopher-lua +- `gluahttpscrape `_ : A simple HTML scraper module for gopher-lua +- `gluaxmlpath `_ : An xmlpath module for gopher-lua +- `gmoonscript `_ : Moonscript Compiler for the Gopher Lua VM +- `loguago `_ : Zerolog wrapper for Gopher-Lua +- `gluacrypto `_ : A native Go implementation of crypto library for the GopherLua VM. +- `gluasql `_ : A native Go implementation of SQL client for the GopherLua VM. +- `purr `_ : A http mock testing tool. +- `vadv/gopher-lua-libs `_ : Some usefull libraries for GopherLua VM. +- `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 +---------------------------------------------------------------- +Donation +---------------------------------------------------------------- + +BTC: 1NEDSyUmo4SMTDP83JJQSWi1MvQUGGNMZB + +---------------------------------------------------------------- +License +---------------------------------------------------------------- +MIT + +---------------------------------------------------------------- +Author +---------------------------------------------------------------- +Yusuke Inuzuka diff --git a/vendor/github.com/yuin/gopher-lua/_state.go b/vendor/github.com/yuin/gopher-lua/_state.go new file mode 100644 index 00000000000..54af061536c --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/_state.go @@ -0,0 +1,2081 @@ +package lua + +import ( + "context" + "fmt" + "io" + "math" + "os" + "runtime" + "strings" + "sync" + "sync/atomic" + "time" + + "github.com/yuin/gopher-lua/parse" +) + +const MultRet = -1 +const RegistryIndex = -10000 +const EnvironIndex = -10001 +const GlobalsIndex = -10002 + +/* ApiError {{{ */ + +type ApiError struct { + Type ApiErrorType + Object LValue + StackTrace string + // Underlying error. This attribute is set only if the Type is ApiErrorFile or ApiErrorSyntax + Cause error +} + +func newApiError(code ApiErrorType, object LValue) *ApiError { + return &ApiError{code, object, "", nil} +} + +func newApiErrorS(code ApiErrorType, message string) *ApiError { + return newApiError(code, LString(message)) +} + +func newApiErrorE(code ApiErrorType, err error) *ApiError { + return &ApiError{code, LString(err.Error()), "", err} +} + +func (e *ApiError) Error() string { + if len(e.StackTrace) > 0 { + return fmt.Sprintf("%s\n%s", e.Object.String(), e.StackTrace) + } + return e.Object.String() +} + +type ApiErrorType int + +const ( + ApiErrorSyntax ApiErrorType = iota + ApiErrorFile + ApiErrorRun + ApiErrorError + ApiErrorPanic +) + +/* }}} */ + +/* ResumeState {{{ */ + +type ResumeState int + +const ( + ResumeOK ResumeState = iota + ResumeYield + ResumeError +) + +/* }}} */ + +/* P {{{ */ + +type P struct { + Fn LValue + NRet int + Protect bool + Handler *LFunction +} + +/* }}} */ + +/* Options {{{ */ + +// Options is a configuration that is used to create a new LState. +type Options struct { + // Call stack size. This defaults to `lua.CallStackSize`. + CallStackSize int + // Data stack size. This defaults to `lua.RegistrySize`. + RegistrySize int + // Allow the registry to grow from the registry size specified up to a value of RegistryMaxSize. A value of 0 + // indicates no growth is permitted. The registry will not shrink again after any growth. + RegistryMaxSize int + // If growth is enabled, step up by an additional `RegistryGrowStep` each time to avoid having to resize too often. + // This defaults to `lua.RegistryGrowStep` + RegistryGrowStep int + // Controls whether or not libraries are opened by default + SkipOpenLibs bool + // Tells whether a Go stacktrace should be included in a Lua stacktrace when panics occur. + IncludeGoStackTrace bool + // If `MinimizeStackMemory` is set, the call stack will be automatically grown or shrank up to a limit of + // `CallStackSize` in order to minimize memory usage. This does incur a slight performance penalty. + MinimizeStackMemory bool +} + +/* }}} */ + +/* Debug {{{ */ + +type Debug struct { + frame *callFrame + Name string + What string + Source string + CurrentLine int + NUpvalues int + LineDefined int + LastLineDefined int +} + +/* }}} */ + +/* callFrame {{{ */ + +type callFrame struct { + Idx int + Fn *LFunction + Parent *callFrame + Pc int + Base int + LocalBase int + ReturnBase int + NArgs int + NRet int + TailCall int +} + +type callFrameStack interface { + Push(v callFrame) + Pop() *callFrame + Last() *callFrame + + SetSp(sp int) + Sp() int + At(sp int) *callFrame + + IsFull() bool + IsEmpty() bool + + FreeAll() +} + +type fixedCallFrameStack struct { + array []callFrame + sp int +} + +func newFixedCallFrameStack(size int) callFrameStack { + return &fixedCallFrameStack{ + array: make([]callFrame, size), + sp: 0, + } +} + +func (cs *fixedCallFrameStack) IsEmpty() bool { return cs.sp == 0 } + +func (cs *fixedCallFrameStack) IsFull() bool { return cs.sp == len(cs.array) } + +func (cs *fixedCallFrameStack) Clear() { + cs.sp = 0 +} + +func (cs *fixedCallFrameStack) Push(v callFrame) { + cs.array[cs.sp] = v + cs.array[cs.sp].Idx = cs.sp + cs.sp++ +} + +func (cs *fixedCallFrameStack) Sp() int { + return cs.sp +} + +func (cs *fixedCallFrameStack) SetSp(sp int) { + cs.sp = sp +} + +func (cs *fixedCallFrameStack) Last() *callFrame { + if cs.sp == 0 { + return nil + } + return &cs.array[cs.sp-1] +} + +func (cs *fixedCallFrameStack) At(sp int) *callFrame { + return &cs.array[sp] +} + +func (cs *fixedCallFrameStack) Pop() *callFrame { + cs.sp-- + return &cs.array[cs.sp] +} + +func (cs *fixedCallFrameStack) FreeAll() { + // nothing to do for fixed callframestack +} + +// FramesPerSegment should be a power of 2 constant for performance reasons. It will allow the go compiler to change +// the divs and mods into bitshifts. Max is 256 due to current use of uint8 to count how many frames in a segment are +// used. +const FramesPerSegment = 8 + +type callFrameStackSegment struct { + array [FramesPerSegment]callFrame +} +type segIdx uint16 +type autoGrowingCallFrameStack struct { + segments []*callFrameStackSegment + segIdx segIdx + // segSp is the number of frames in the current segment which are used. Full 'sp' value is segIdx * FramesPerSegment + segSp. + // It points to the next stack slot to use, so 0 means to use the 0th element in the segment, and a value of + // FramesPerSegment indicates that the segment is full and cannot accommodate another frame. + segSp uint8 +} + +var segmentPool sync.Pool + +func newCallFrameStackSegment() *callFrameStackSegment { + seg := segmentPool.Get() + if seg == nil { + return &callFrameStackSegment{} + } + return seg.(*callFrameStackSegment) +} + +func freeCallFrameStackSegment(seg *callFrameStackSegment) { + segmentPool.Put(seg) +} + +// newCallFrameStack allocates a new stack for a lua state, which will auto grow up to a max size of at least maxSize. +// it will actually grow up to the next segment size multiple after maxSize, where the segment size is dictated by +// FramesPerSegment. +func newAutoGrowingCallFrameStack(maxSize int) callFrameStack { + cs := &autoGrowingCallFrameStack{ + segments: make([]*callFrameStackSegment, (maxSize+(FramesPerSegment-1))/FramesPerSegment), + segIdx: 0, + } + cs.segments[0] = newCallFrameStackSegment() + return cs +} + +func (cs *autoGrowingCallFrameStack) IsEmpty() bool { + return cs.segIdx == 0 && cs.segSp == 0 +} + +// IsFull returns true if the stack cannot receive any more stack pushes without overflowing +func (cs *autoGrowingCallFrameStack) IsFull() bool { + return int(cs.segIdx) == len(cs.segments) && cs.segSp >= FramesPerSegment +} + +func (cs *autoGrowingCallFrameStack) Clear() { + for i := segIdx(1); i <= cs.segIdx; i++ { + freeCallFrameStackSegment(cs.segments[i]) + cs.segments[i] = nil + } + cs.segIdx = 0 + cs.segSp = 0 +} + +func (cs *autoGrowingCallFrameStack) FreeAll() { + for i := segIdx(0); i <= cs.segIdx; i++ { + freeCallFrameStackSegment(cs.segments[i]) + cs.segments[i] = nil + } +} + +// Push pushes the passed callFrame onto the stack. it panics if the stack is full, caller should call IsFull() before +// invoking this to avoid this. +func (cs *autoGrowingCallFrameStack) Push(v callFrame) { + curSeg := cs.segments[cs.segIdx] + if cs.segSp >= FramesPerSegment { + // segment full, push new segment if allowed + if cs.segIdx < segIdx(len(cs.segments)-1) { + curSeg = newCallFrameStackSegment() + cs.segIdx++ + cs.segments[cs.segIdx] = curSeg + cs.segSp = 0 + } else { + panic("lua callstack overflow") + } + } + curSeg.array[cs.segSp] = v + curSeg.array[cs.segSp].Idx = int(cs.segSp) + FramesPerSegment*int(cs.segIdx) + cs.segSp++ +} + +// Sp retrieves the current stack depth, which is the number of frames currently pushed on the stack. +func (cs *autoGrowingCallFrameStack) Sp() int { + return int(cs.segSp) + int(cs.segIdx)*FramesPerSegment +} + +// SetSp can be used to rapidly unwind the stack, freeing all stack frames on the way. It should not be used to +// allocate new stack space, use Push() for that. +func (cs *autoGrowingCallFrameStack) SetSp(sp int) { + desiredSegIdx := segIdx(sp / FramesPerSegment) + desiredFramesInLastSeg := uint8(sp % FramesPerSegment) + for { + if cs.segIdx <= desiredSegIdx { + break + } + freeCallFrameStackSegment(cs.segments[cs.segIdx]) + cs.segments[cs.segIdx] = nil + cs.segIdx-- + } + cs.segSp = desiredFramesInLastSeg +} + +func (cs *autoGrowingCallFrameStack) Last() *callFrame { + curSeg := cs.segments[cs.segIdx] + segSp := cs.segSp + if segSp == 0 { + if cs.segIdx == 0 { + return nil + } + curSeg = cs.segments[cs.segIdx-1] + segSp = FramesPerSegment + } + return &curSeg.array[segSp-1] +} + +func (cs *autoGrowingCallFrameStack) At(sp int) *callFrame { + segIdx := segIdx(sp / FramesPerSegment) + frameIdx := uint8(sp % FramesPerSegment) + return &cs.segments[segIdx].array[frameIdx] +} + +// Pop pops off the most recent stack frame and returns it +func (cs *autoGrowingCallFrameStack) Pop() *callFrame { + curSeg := cs.segments[cs.segIdx] + if cs.segSp == 0 { + if cs.segIdx == 0 { + // stack empty + return nil + } + freeCallFrameStackSegment(curSeg) + cs.segments[cs.segIdx] = nil + cs.segIdx-- + cs.segSp = FramesPerSegment + curSeg = cs.segments[cs.segIdx] + } + cs.segSp-- + return &curSeg.array[cs.segSp] +} + +/* }}} */ + +/* registry {{{ */ + +type registryHandler interface { + registryOverflow() +} +type registry struct { + array []LValue + top int + growBy int + maxSize int + alloc *allocator + handler registryHandler +} + +func newRegistry(handler registryHandler, initialSize int, growBy int, maxSize int, alloc *allocator) *registry { + return ®istry{make([]LValue, initialSize), 0, growBy, maxSize, alloc, handler} +} + +func (rg *registry) checkSize(requiredSize int) { // +inline-start + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } +} // +inline-end + +func (rg *registry) resize(requiredSize int) { // +inline-start + newSize := requiredSize + rg.growBy // give some padding + if newSize > rg.maxSize { + newSize = rg.maxSize + } + if newSize < requiredSize { + rg.handler.registryOverflow() + return + } + rg.forceResize(newSize) +} // +inline-end + +func (rg *registry) forceResize(newSize int) { + newSlice := make([]LValue, newSize) + 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++ { + 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] + for i := range nilRange { + nilRange[i] = nil + } + } + //for i := rg.top; i < oldtop; i++ { + // rg.array[i] = LNil + //} +} + +func (rg *registry) Top() int { + return rg.top +} + +func (rg *registry) Push(v LValue) { + newSize := rg.top + 1 + // +inline-call rg.checkSize newSize + rg.array[rg.top] = v + rg.top++ +} + +func (rg *registry) Pop() LValue { + v := rg.array[rg.top-1] + rg.array[rg.top-1] = LNil + rg.top-- + return v +} + +func (rg *registry) Get(reg int) LValue { + return rg.array[reg] +} + +// CopyRange will move a section of values from index `start` to index `regv` +// It will move `n` values. +// `limit` specifies the maximum end range that can be copied from. If it's set to -1, then it defaults to stopping at +// the top of the registry (values beyond the top are not initialized, so if specifying an alternative `limit` you should +// pass a value <= rg.top. +// If start+n is beyond the limit, then nil values will be copied to the destination slots. +// After the copy, the registry is truncated to be at the end of the copied range, ie the original of the copied values +// are nilled out. (So top will be regv+n) +// CopyRange should ideally be renamed to MoveRange. +func (rg *registry) CopyRange(regv, start, limit, n int) { // +inline-start + newSize := regv + n + // +inline-call rg.checkSize newSize + if limit == -1 || limit > rg.top { + limit = rg.top + } + for i := 0; i < n; i++ { + srcIdx := start + i + if srcIdx >= limit || srcIdx < 0 { + rg.array[regv+i] = LNil + } else { + rg.array[regv+i] = rg.array[srcIdx] + } + } + + // 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 + oldtop := rg.top + rg.top = regv + n + if rg.top < oldtop { + nilRange := rg.array[rg.top:oldtop] + for i := range nilRange { + nilRange[i] = nil + } + } +} // +inline-end + +// FillNil fills the registry with nil values from regm to regm+n and then sets the registry top to regm+n +func (rg *registry) FillNil(regm, n int) { // +inline-start + newSize := regm + n + // +inline-call rg.checkSize newSize + for i := 0; i < n; i++ { + rg.array[regm+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 + oldtop := rg.top + rg.top = regm + n + if rg.top < oldtop { + nilRange := rg.array[rg.top:oldtop] + for i := range nilRange { + nilRange[i] = nil + } + } +} // +inline-end + +func (rg *registry) Insert(value LValue, reg int) { + top := rg.Top() + if reg >= top { + 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)) + } + rg.Set(reg, value) +} + +func (rg *registry) Set(reg int, val LValue) { + newSize := reg + 1 + // +inline-call rg.checkSize newSize + rg.array[reg] = val + if reg >= rg.top { + rg.top = reg + 1 + } +} + +func (rg *registry) SetNumber(reg int, val LNumber) { + newSize := reg + 1 + // +inline-call rg.checkSize newSize + rg.array[reg] = rg.alloc.LNumber2I(val) + if reg >= rg.top { + rg.top = reg + 1 + } +} + +func (rg *registry) IsFull() bool { + return rg.top >= cap(rg.array) +} + +/* }}} */ + +/* Global {{{ */ + +func newGlobal() *Global { + return &Global{ + MainThread: nil, + Registry: newLTable(0, 32), + Global: newLTable(0, 64), + builtinMts: make(map[int]LValue), + tempFiles: make([]*os.File, 0, 10), + } +} + +/* }}} */ + +/* package local methods {{{ */ + +func panicWithTraceback(L *LState) { + err := newApiError(ApiErrorRun, L.Get(-1)) + err.StackTrace = L.stackTrace(0) + panic(err) +} + +func panicWithoutTraceback(L *LState) { + err := newApiError(ApiErrorRun, L.Get(-1)) + panic(err) +} + +func newLState(options Options) *LState { + al := newAllocator(32) + ls := &LState{ + G: newGlobal(), + Parent: nil, + Panic: panicWithTraceback, + Dead: false, + Options: options, + + stop: 0, + alloc: al, + currentFrame: nil, + wrapped: false, + uvcache: nil, + hasErrorFunc: false, + mainLoop: mainLoop, + ctx: nil, + } + if options.MinimizeStackMemory { + ls.stack = newAutoGrowingCallFrameStack(options.CallStackSize) + } else { + ls.stack = newFixedCallFrameStack(options.CallStackSize) + } + ls.reg = newRegistry(ls, options.RegistrySize, options.RegistryGrowStep, options.RegistryMaxSize, al) + ls.Env = ls.G.Global + return ls +} + +func (ls *LState) printReg() { + println("-------------------------") + println("thread:", ls) + println("top:", ls.reg.Top()) + if ls.currentFrame != nil { + println("function base:", ls.currentFrame.Base) + println("return base:", ls.currentFrame.ReturnBase) + } else { + println("(vm not started)") + } + println("local base:", ls.currentLocalBase()) + for i := 0; i < ls.reg.Top(); i++ { + println(i, ls.reg.Get(i).String()) + } + println("-------------------------") +} + +func (ls *LState) printCallStack() { + println("-------------------------") + for i := 0; i < ls.stack.Sp(); i++ { + print(i) + print(" ") + frame := ls.stack.At(i) + if frame == nil { + break + } + if frame.Fn.IsG { + println("IsG:", true, "Frame:", frame, "Fn:", frame.Fn) + } else { + println("IsG:", false, "Frame:", frame, "Fn:", frame.Fn, "pc:", frame.Pc) + } + } + println("-------------------------") +} + +func (ls *LState) closeAllUpvalues() { // +inline-start + for cf := ls.currentFrame; cf != nil; cf = cf.Parent { + if !cf.Fn.IsG { + ls.closeUpvalues(cf.LocalBase) + } + } +} // +inline-end + +func (ls *LState) raiseError(level int, format string, args ...interface{}) { + if !ls.hasErrorFunc { + ls.closeAllUpvalues() + } + message := format + if len(args) > 0 { + message = fmt.Sprintf(format, args...) + } + if level > 0 { + message = fmt.Sprintf("%v %v", ls.where(level-1, true), message) + } + if ls.reg.IsFull() { + // if the registry is full then it won't be possible to push a value, in this case, force a larger size + ls.reg.forceResize(ls.reg.Top() + 1) + } + ls.reg.Push(LString(message)) + ls.Panic(ls) +} + +func (ls *LState) findLocal(frame *callFrame, no int) string { + fn := frame.Fn + if !fn.IsG { + if name, ok := fn.LocalName(no, frame.Pc-1); ok { + return name + } + } + var top int + if ls.currentFrame == frame { + top = ls.reg.Top() + } else if frame.Idx+1 < ls.stack.Sp() { + top = ls.stack.At(frame.Idx + 1).Base + } else { + return "" + } + if top-frame.LocalBase >= no { + return "(*temporary)" + } + return "" +} + +func (ls *LState) where(level int, skipg bool) string { + dbg, ok := ls.GetStack(level) + if !ok { + return "" + } + cf := dbg.frame + proto := cf.Fn.Proto + sourcename := "[G]" + if proto != nil { + sourcename = proto.SourceName + } else if skipg { + return ls.where(level+1, skipg) + } + line := "" + if proto != nil { + line = fmt.Sprintf("%v:", proto.DbgSourcePositions[cf.Pc-1]) + } + return fmt.Sprintf("%v:%v", sourcename, line) +} + +func (ls *LState) stackTrace(level int) string { + buf := []string{} + header := "stack traceback:" + if ls.currentFrame != nil { + i := 0 + for dbg, ok := ls.GetStack(i); ok; dbg, ok = ls.GetStack(i) { + cf := dbg.frame + buf = append(buf, fmt.Sprintf("\t%v in %v", ls.Where(i), ls.formattedFrameFuncName(cf))) + if !cf.Fn.IsG && cf.TailCall > 0 { + for tc := cf.TailCall; tc > 0; tc-- { + buf = append(buf, "\t(tailcall): ?") + i++ + } + } + i++ + } + } + buf = append(buf, fmt.Sprintf("\t%v: %v", "[G]", "?")) + buf = buf[intMax(0, intMin(level, len(buf))):len(buf)] + if len(buf) > 20 { + newbuf := make([]string, 0, 20) + newbuf = append(newbuf, buf[0:7]...) + newbuf = append(newbuf, "\t...") + newbuf = append(newbuf, buf[len(buf)-7:len(buf)]...) + buf = newbuf + } + return fmt.Sprintf("%s\n%s", header, strings.Join(buf, "\n")) +} + +func (ls *LState) formattedFrameFuncName(fr *callFrame) string { + name, ischunk := ls.frameFuncName(fr) + if ischunk { + return name + } + if name[0] != '(' && name[0] != '<' { + return fmt.Sprintf("function '%s'", name) + } + return fmt.Sprintf("function %s", name) +} + +func (ls *LState) rawFrameFuncName(fr *callFrame) string { + name, _ := ls.frameFuncName(fr) + return name +} + +func (ls *LState) frameFuncName(fr *callFrame) (string, bool) { + frame := fr.Parent + if frame == nil { + if ls.Parent == nil { + return "main chunk", true + } else { + return "corountine", true + } + } + if !frame.Fn.IsG { + pc := frame.Pc - 1 + for _, call := range frame.Fn.Proto.DbgCalls { + if call.Pc == pc { + name := call.Name + if (name == "?" || fr.TailCall > 0) && !fr.Fn.IsG { + name = fmt.Sprintf("<%v:%v>", fr.Fn.Proto.SourceName, fr.Fn.Proto.LineDefined) + } + return name, false + } + } + } + if !fr.Fn.IsG { + return fmt.Sprintf("<%v:%v>", fr.Fn.Proto.SourceName, fr.Fn.Proto.LineDefined), false + } + return "(anonymous)", false +} + +func (ls *LState) isStarted() bool { + return ls.currentFrame != nil +} + +func (ls *LState) kill() { + ls.Dead = true +} + +func (ls *LState) indexToReg(idx int) int { + base := ls.currentLocalBase() + if idx > 0 { + return base + idx - 1 + } else if idx == 0 { + return -1 + } else { + tidx := ls.reg.Top() + idx + if tidx < base { + return -1 + } + return tidx + } +} + +func (ls *LState) currentLocalBase() int { + base := 0 + if ls.currentFrame != nil { + base = ls.currentFrame.LocalBase + } + return base +} + +func (ls *LState) currentEnv() *LTable { + return ls.Env + /* + if ls.currentFrame == nil { + return ls.Env + } + return ls.currentFrame.Fn.Env + */ +} + +func (ls *LState) rkValue(idx int) LValue { + /* + if OpIsK(idx) { + return ls.currentFrame.Fn.Proto.Constants[opIndexK(idx)] + } + return ls.reg.Get(ls.currentFrame.LocalBase + idx) + */ + if (idx & opBitRk) != 0 { + return ls.currentFrame.Fn.Proto.Constants[idx & ^opBitRk] + } + return ls.reg.array[ls.currentFrame.LocalBase+idx] +} + +func (ls *LState) rkString(idx int) string { + if (idx & opBitRk) != 0 { + return ls.currentFrame.Fn.Proto.stringConstants[idx & ^opBitRk] + } + return string(ls.reg.array[ls.currentFrame.LocalBase+idx].(LString)) +} + +func (ls *LState) closeUpvalues(idx int) { // +inline-start + if ls.uvcache != nil { + var prev *Upvalue + for uv := ls.uvcache; uv != nil; uv = uv.next { + if uv.index >= idx { + if prev != nil { + prev.next = nil + } else { + ls.uvcache = nil + } + uv.Close() + } + prev = uv + } + } +} // +inline-end + +func (ls *LState) findUpvalue(idx int) *Upvalue { + var prev *Upvalue + var next *Upvalue + if ls.uvcache != nil { + for uv := ls.uvcache; uv != nil; uv = uv.next { + if uv.index == idx { + return uv + } + if uv.index > idx { + next = uv + break + } + prev = uv + } + } + uv := &Upvalue{reg: ls.reg, index: idx, closed: false} + if prev != nil { + prev.next = uv + } else { + ls.uvcache = uv + } + if next != nil { + uv.next = next + } + return uv +} + +func (ls *LState) metatable(lvalue LValue, rawget bool) LValue { + var metatable LValue = LNil + switch obj := lvalue.(type) { + case *LTable: + metatable = obj.Metatable + case *LUserData: + metatable = obj.Metatable + default: + if table, ok := ls.G.builtinMts[int(obj.Type())]; ok { + metatable = table + } + } + + if !rawget && metatable != LNil { + oldmt := metatable + if tb, ok := metatable.(*LTable); ok { + metatable = tb.RawGetString("__metatable") + if metatable == LNil { + metatable = oldmt + } + } + } + + return metatable +} + +func (ls *LState) metaOp1(lvalue LValue, event string) LValue { + if mt := ls.metatable(lvalue, true); mt != LNil { + if tb, ok := mt.(*LTable); ok { + return tb.RawGetString(event) + } + } + return LNil +} + +func (ls *LState) metaOp2(value1, value2 LValue, event string) LValue { + if mt := ls.metatable(value1, true); mt != LNil { + if tb, ok := mt.(*LTable); ok { + if ret := tb.RawGetString(event); ret != LNil { + return ret + } + } + } + if mt := ls.metatable(value2, true); mt != LNil { + if tb, ok := mt.(*LTable); ok { + return tb.RawGetString(event) + } + } + return LNil +} + +func (ls *LState) metaCall(lvalue LValue) (*LFunction, bool) { + if fn, ok := lvalue.(*LFunction); ok { + return fn, false + } + if fn, ok := ls.metaOp1(lvalue, "__call").(*LFunction); ok { + return fn, true + } + return nil, false +} + +func (ls *LState) initCallFrame(cf *callFrame) { // +inline-start + if cf.Fn.IsG { + ls.reg.SetTop(cf.LocalBase + cf.NArgs) + } else { + proto := cf.Fn.Proto + nargs := cf.NArgs + np := int(proto.NumParameters) + newSize := cf.LocalBase + np + // +inline-call ls.reg.checkSize newSize + for i := nargs; i < np; i++ { + ls.reg.array[cf.LocalBase+i] = LNil + nargs = np + } + + if (proto.IsVarArg & VarArgIsVarArg) == 0 { + if nargs < int(proto.NumUsedRegisters) { + nargs = int(proto.NumUsedRegisters) + } + newSize = cf.LocalBase + nargs + // +inline-call ls.reg.checkSize newSize + for i := np; i < nargs; i++ { + ls.reg.array[cf.LocalBase+i] = LNil + } + ls.reg.top = cf.LocalBase + int(proto.NumUsedRegisters) + } else { + /* swap vararg positions: + closure + namedparam1 <- lbase + namedparam2 + vararg1 + vararg2 + + TO + + closure + nil + nil + vararg1 + vararg2 + namedparam1 <- lbase + namedparam2 + */ + nvarargs := nargs - np + if nvarargs < 0 { + nvarargs = 0 + } + + ls.reg.SetTop(cf.LocalBase + nargs + np) + for i := 0; i < np; i++ { + //ls.reg.Set(cf.LocalBase+nargs+i, ls.reg.Get(cf.LocalBase+i)) + ls.reg.array[cf.LocalBase+nargs+i] = ls.reg.array[cf.LocalBase+i] + //ls.reg.Set(cf.LocalBase+i, LNil) + ls.reg.array[cf.LocalBase+i] = LNil + } + + if CompatVarArg { + ls.reg.SetTop(cf.LocalBase + nargs + np + 1) + if (proto.IsVarArg & VarArgNeedsArg) != 0 { + argtb := newLTable(nvarargs, 0) + for i := 0; i < nvarargs; i++ { + argtb.RawSetInt(i+1, ls.reg.Get(cf.LocalBase+np+i)) + } + argtb.RawSetString("n", LNumber(nvarargs)) + //ls.reg.Set(cf.LocalBase+nargs+np, argtb) + ls.reg.array[cf.LocalBase+nargs+np] = argtb + } else { + ls.reg.array[cf.LocalBase+nargs+np] = LNil + } + } + cf.LocalBase += nargs + maxreg := cf.LocalBase + int(proto.NumUsedRegisters) + ls.reg.SetTop(maxreg) + } + } +} // +inline-end + +func (ls *LState) pushCallFrame(cf callFrame, fn LValue, meta bool) { // +inline-start + if meta { + cf.NArgs++ + ls.reg.Insert(fn, cf.LocalBase) + } + if cf.Fn == nil { + ls.RaiseError("attempt to call a non-function object") + } + if ls.stack.IsFull() { + ls.RaiseError("stack overflow") + } + ls.stack.Push(cf) + newcf := ls.stack.Last() + // +inline-call ls.initCallFrame newcf + ls.currentFrame = newcf +} // +inline-end + +func (ls *LState) callR(nargs, nret, rbase int) { + base := ls.reg.Top() - nargs - 1 + if rbase < 0 { + rbase = base + } + lv := ls.reg.Get(base) + fn, meta := ls.metaCall(lv) + ls.pushCallFrame(callFrame{ + Fn: fn, + Pc: 0, + Base: base, + LocalBase: base + 1, + ReturnBase: rbase, + NArgs: nargs, + NRet: nret, + Parent: ls.currentFrame, + TailCall: 0, + }, lv, meta) + if ls.G.MainThread == nil { + ls.G.MainThread = ls + ls.G.CurrentThread = ls + ls.mainLoop(ls, nil) + } else { + ls.mainLoop(ls, ls.currentFrame) + } + if nret != MultRet { + ls.reg.SetTop(rbase + nret) + } +} + +func (ls *LState) getField(obj LValue, key LValue) LValue { + curobj := obj + for i := 0; i < MaxTableGetLoop; i++ { + tb, istable := curobj.(*LTable) + if istable { + ret := tb.RawGet(key) + if ret != LNil { + return ret + } + } + metaindex := ls.metaOp1(curobj, "__index") + if metaindex == LNil { + if !istable { + ls.RaiseError("attempt to index a non-table object(%v) with key '%s'", curobj.Type().String(), key.String()) + } + return LNil + } + if metaindex.Type() == LTFunction { + ls.reg.Push(metaindex) + ls.reg.Push(curobj) + ls.reg.Push(key) + ls.Call(2, 1) + return ls.reg.Pop() + } else { + curobj = metaindex + } + } + ls.RaiseError("too many recursions in gettable") + return nil +} + +func (ls *LState) getFieldString(obj LValue, key string) LValue { + curobj := obj + for i := 0; i < MaxTableGetLoop; i++ { + tb, istable := curobj.(*LTable) + if istable { + ret := tb.RawGetString(key) + if ret != LNil { + return ret + } + } + metaindex := ls.metaOp1(curobj, "__index") + if metaindex == LNil { + if !istable { + ls.RaiseError("attempt to index a non-table object(%v) with key '%s'", curobj.Type().String(), key) + } + return LNil + } + if metaindex.Type() == LTFunction { + ls.reg.Push(metaindex) + ls.reg.Push(curobj) + ls.reg.Push(LString(key)) + ls.Call(2, 1) + return ls.reg.Pop() + } else { + curobj = metaindex + } + } + ls.RaiseError("too many recursions in gettable") + return nil +} + +func (ls *LState) setField(obj LValue, key LValue, value LValue) { + curobj := obj + for i := 0; i < MaxTableGetLoop; i++ { + tb, istable := curobj.(*LTable) + if istable { + if tb.RawGet(key) != LNil { + ls.RawSet(tb, key, value) + return + } + } + metaindex := ls.metaOp1(curobj, "__newindex") + if metaindex == LNil { + if !istable { + ls.RaiseError("attempt to index a non-table object(%v) with key '%s'", curobj.Type().String(), key.String()) + } + ls.RawSet(tb, key, value) + return + } + if metaindex.Type() == LTFunction { + ls.reg.Push(metaindex) + ls.reg.Push(curobj) + ls.reg.Push(key) + ls.reg.Push(value) + ls.Call(3, 0) + return + } else { + curobj = metaindex + } + } + ls.RaiseError("too many recursions in settable") +} + +func (ls *LState) setFieldString(obj LValue, key string, value LValue) { + curobj := obj + for i := 0; i < MaxTableGetLoop; i++ { + tb, istable := curobj.(*LTable) + if istable { + if tb.RawGetString(key) != LNil { + tb.RawSetString(key, value) + return + } + } + metaindex := ls.metaOp1(curobj, "__newindex") + if metaindex == LNil { + if !istable { + ls.RaiseError("attempt to index a non-table object(%v) with key '%s'", curobj.Type().String(), key) + } + tb.RawSetString(key, value) + return + } + if metaindex.Type() == LTFunction { + ls.reg.Push(metaindex) + ls.reg.Push(curobj) + ls.reg.Push(LString(key)) + ls.reg.Push(value) + ls.Call(3, 0) + return + } else { + curobj = metaindex + } + } + ls.RaiseError("too many recursions in settable") +} + +/* }}} */ + +/* api methods {{{ */ + +func NewState(opts ...Options) *LState { + var ls *LState + if len(opts) == 0 { + ls = newLState(Options{ + CallStackSize: CallStackSize, + RegistrySize: RegistrySize, + }) + ls.OpenLibs() + } else { + if opts[0].CallStackSize < 1 { + opts[0].CallStackSize = CallStackSize + } + if opts[0].RegistrySize < 128 { + opts[0].RegistrySize = RegistrySize + } + if opts[0].RegistryMaxSize < opts[0].RegistrySize { + opts[0].RegistryMaxSize = 0 // disable growth if max size is smaller than initial size + } else { + // if growth enabled, grow step is set + if opts[0].RegistryGrowStep < 1 { + opts[0].RegistryGrowStep = RegistryGrowStep + } + } + ls = newLState(opts[0]) + if !opts[0].SkipOpenLibs { + ls.OpenLibs() + } + } + return ls +} + +func (ls *LState) IsClosed() bool { + return ls.stack == nil +} + +func (ls *LState) Close() { + atomic.AddInt32(&ls.stop, 1) + for _, file := range ls.G.tempFiles { + // ignore errors in these operations + file.Close() + os.Remove(file.Name()) + } + ls.stack.FreeAll() + ls.stack = nil +} + +/* registry operations {{{ */ + +func (ls *LState) GetTop() int { + return ls.reg.Top() - ls.currentLocalBase() +} + +func (ls *LState) SetTop(idx int) { + base := ls.currentLocalBase() + newtop := ls.indexToReg(idx) + 1 + if newtop < base { + ls.reg.SetTop(base) + } else { + ls.reg.SetTop(newtop) + } +} + +func (ls *LState) Replace(idx int, value LValue) { + base := ls.currentLocalBase() + if idx > 0 { + reg := base + idx - 1 + if reg < ls.reg.Top() { + ls.reg.Set(reg, value) + } + } else if idx == 0 { + } else if idx > RegistryIndex { + if tidx := ls.reg.Top() + idx; tidx >= base { + ls.reg.Set(tidx, value) + } + } else { + switch idx { + case RegistryIndex: + if tb, ok := value.(*LTable); ok { + ls.G.Registry = tb + } else { + ls.RaiseError("registry must be a table(%v)", value.Type().String()) + } + case EnvironIndex: + if ls.currentFrame == nil { + ls.RaiseError("no calling environment") + } + if tb, ok := value.(*LTable); ok { + ls.currentFrame.Fn.Env = tb + } else { + ls.RaiseError("environment must be a table(%v)", value.Type().String()) + } + case GlobalsIndex: + if tb, ok := value.(*LTable); ok { + ls.G.Global = tb + } else { + ls.RaiseError("_G must be a table(%v)", value.Type().String()) + } + default: + fn := ls.currentFrame.Fn + index := GlobalsIndex - idx - 1 + if index < len(fn.Upvalues) { + fn.Upvalues[index].SetValue(value) + } + } + } +} + +func (ls *LState) Get(idx int) LValue { + base := ls.currentLocalBase() + if idx > 0 { + reg := base + idx - 1 + if reg < ls.reg.Top() { + return ls.reg.Get(reg) + } + return LNil + } else if idx == 0 { + return LNil + } else if idx > RegistryIndex { + tidx := ls.reg.Top() + idx + if tidx < base { + return LNil + } + return ls.reg.Get(tidx) + } else { + switch idx { + case RegistryIndex: + return ls.G.Registry + case EnvironIndex: + if ls.currentFrame == nil { + return ls.Env + } + return ls.currentFrame.Fn.Env + case GlobalsIndex: + return ls.G.Global + default: + fn := ls.currentFrame.Fn + index := GlobalsIndex - idx - 1 + if index < len(fn.Upvalues) { + return fn.Upvalues[index].Value() + } + return LNil + } + } + return LNil +} + +func (ls *LState) Push(value LValue) { + ls.reg.Push(value) +} + +func (ls *LState) Pop(n int) { + for i := 0; i < n; i++ { + if ls.GetTop() == 0 { + ls.RaiseError("register underflow") + } + ls.reg.Pop() + } +} + +func (ls *LState) Insert(value LValue, index int) { + reg := ls.indexToReg(index) + top := ls.reg.Top() + if reg >= top { + ls.reg.Set(reg, value) + return + } + if reg <= ls.currentLocalBase() { + reg = ls.currentLocalBase() + } + top-- + for ; top >= reg; top-- { + ls.reg.Set(top+1, ls.reg.Get(top)) + } + ls.reg.Set(reg, value) +} + +func (ls *LState) Remove(index int) { + reg := ls.indexToReg(index) + top := ls.reg.Top() + switch { + case reg >= top: + return + case reg < ls.currentLocalBase(): + return + case reg == top-1: + ls.Pop(1) + return + } + for i := reg; i < top-1; i++ { + ls.reg.Set(i, ls.reg.Get(i+1)) + } + ls.reg.SetTop(top - 1) +} + +/* }}} */ + +/* object allocation {{{ */ + +func (ls *LState) NewTable() *LTable { + return newLTable(defaultArrayCap, defaultHashCap) +} + +func (ls *LState) CreateTable(acap, hcap int) *LTable { + return newLTable(acap, hcap) +} + +// NewThread returns a new LState that shares with the original state all global objects. +// If the original state has context.Context, the new state has a new child context of the original state and this function returns its cancel function. +func (ls *LState) NewThread() (*LState, context.CancelFunc) { + thread := newLState(ls.Options) + thread.G = ls.G + thread.Env = ls.Env + var f context.CancelFunc = nil + if ls.ctx != nil { + thread.mainLoop = mainLoopWithContext + thread.ctx, f = context.WithCancel(ls.ctx) + } + return thread, f +} + +func (ls *LState) NewFunctionFromProto(proto *FunctionProto) *LFunction { + return newLFunctionL(proto, ls.Env, int(proto.NumUpvalues)) +} + +func (ls *LState) NewUserData() *LUserData { + return &LUserData{ + Env: ls.currentEnv(), + Metatable: LNil, + } +} + +func (ls *LState) NewFunction(fn LGFunction) *LFunction { + return newLFunctionG(fn, ls.currentEnv(), 0) +} + +func (ls *LState) NewClosure(fn LGFunction, upvalues ...LValue) *LFunction { + cl := newLFunctionG(fn, ls.currentEnv(), len(upvalues)) + for i, lv := range upvalues { + cl.Upvalues[i] = &Upvalue{} + cl.Upvalues[i].Close() + cl.Upvalues[i].SetValue(lv) + } + return cl +} + +/* }}} */ + +/* toType {{{ */ + +func (ls *LState) ToBool(n int) bool { + return LVAsBool(ls.Get(n)) +} + +func (ls *LState) ToInt(n int) int { + if lv, ok := ls.Get(n).(LNumber); ok { + return int(lv) + } + if lv, ok := ls.Get(n).(LString); ok { + if num, err := parseNumber(string(lv)); err == nil { + return int(num) + } + } + return 0 +} + +func (ls *LState) ToInt64(n int) int64 { + if lv, ok := ls.Get(n).(LNumber); ok { + return int64(lv) + } + if lv, ok := ls.Get(n).(LString); ok { + if num, err := parseNumber(string(lv)); err == nil { + return int64(num) + } + } + return 0 +} + +func (ls *LState) ToNumber(n int) LNumber { + return LVAsNumber(ls.Get(n)) +} + +func (ls *LState) ToString(n int) string { + return LVAsString(ls.Get(n)) +} + +func (ls *LState) ToTable(n int) *LTable { + if lv, ok := ls.Get(n).(*LTable); ok { + return lv + } + return nil +} + +func (ls *LState) ToFunction(n int) *LFunction { + if lv, ok := ls.Get(n).(*LFunction); ok { + return lv + } + return nil +} + +func (ls *LState) ToUserData(n int) *LUserData { + if lv, ok := ls.Get(n).(*LUserData); ok { + return lv + } + return nil +} + +func (ls *LState) ToThread(n int) *LState { + if lv, ok := ls.Get(n).(*LState); ok { + return lv + } + return nil +} + +/* }}} */ + +/* error & debug operations {{{ */ + +func (ls *LState) registryOverflow() { + ls.RaiseError("registry overflow") +} + +// This function is equivalent to luaL_error( http://www.lua.org/manual/5.1/manual.html#luaL_error ). +func (ls *LState) RaiseError(format string, args ...interface{}) { + ls.raiseError(1, format, args...) +} + +// This function is equivalent to lua_error( http://www.lua.org/manual/5.1/manual.html#lua_error ). +func (ls *LState) Error(lv LValue, level int) { + if str, ok := lv.(LString); ok { + ls.raiseError(level, string(str)) + } else { + if !ls.hasErrorFunc { + ls.closeAllUpvalues() + } + ls.Push(lv) + ls.Panic(ls) + } +} + +func (ls *LState) GetInfo(what string, dbg *Debug, fn LValue) (LValue, error) { + if !strings.HasPrefix(what, ">") { + fn = dbg.frame.Fn + } else { + what = what[1:] + } + f, ok := fn.(*LFunction) + if !ok { + return LNil, newApiErrorS(ApiErrorRun, "can not get debug info(an object in not a function)") + } + + retfn := false + for _, c := range what { + switch c { + case 'f': + retfn = true + case 'S': + if dbg.frame != nil && dbg.frame.Parent == nil { + dbg.What = "main" + } else if f.IsG { + dbg.What = "G" + } else if dbg.frame != nil && dbg.frame.TailCall > 0 { + dbg.What = "tail" + } else { + dbg.What = "Lua" + } + if !f.IsG { + dbg.Source = f.Proto.SourceName + dbg.LineDefined = f.Proto.LineDefined + dbg.LastLineDefined = f.Proto.LastLineDefined + } + case 'l': + if !f.IsG && dbg.frame != nil { + if dbg.frame.Pc > 0 { + dbg.CurrentLine = f.Proto.DbgSourcePositions[dbg.frame.Pc-1] + } + } else { + dbg.CurrentLine = -1 + } + case 'u': + dbg.NUpvalues = len(f.Upvalues) + case 'n': + if dbg.frame != nil { + dbg.Name = ls.rawFrameFuncName(dbg.frame) + } + default: + return LNil, newApiErrorS(ApiErrorRun, "invalid what: "+string(c)) + } + } + + if retfn { + return f, nil + } + return LNil, nil + +} + +func (ls *LState) GetStack(level int) (*Debug, bool) { + frame := ls.currentFrame + for ; level > 0 && frame != nil; frame = frame.Parent { + level-- + if !frame.Fn.IsG { + level -= frame.TailCall + } + } + + if level == 0 && frame != nil { + return &Debug{frame: frame}, true + } else if level < 0 && ls.stack.Sp() > 0 { + return &Debug{frame: ls.stack.At(0)}, true + } + return &Debug{}, false +} + +func (ls *LState) GetLocal(dbg *Debug, no int) (string, LValue) { + frame := dbg.frame + if name := ls.findLocal(frame, no); len(name) > 0 { + return name, ls.reg.Get(frame.LocalBase + no - 1) + } + return "", LNil +} + +func (ls *LState) SetLocal(dbg *Debug, no int, lv LValue) string { + frame := dbg.frame + if name := ls.findLocal(frame, no); len(name) > 0 { + ls.reg.Set(frame.LocalBase+no-1, lv) + return name + } + return "" +} + +func (ls *LState) GetUpvalue(fn *LFunction, no int) (string, LValue) { + if fn.IsG { + return "", LNil + } + + no-- + if no >= 0 && no < len(fn.Upvalues) { + return fn.Proto.DbgUpvalues[no], fn.Upvalues[no].Value() + } + return "", LNil +} + +func (ls *LState) SetUpvalue(fn *LFunction, no int, lv LValue) string { + if fn.IsG { + return "" + } + + no-- + if no >= 0 && no < len(fn.Upvalues) { + fn.Upvalues[no].SetValue(lv) + return fn.Proto.DbgUpvalues[no] + } + return "" +} + +/* }}} */ + +/* env operations {{{ */ + +func (ls *LState) GetFEnv(obj LValue) LValue { + switch lv := obj.(type) { + case *LFunction: + return lv.Env + case *LUserData: + return lv.Env + case *LState: + return lv.Env + } + return LNil +} + +func (ls *LState) SetFEnv(obj LValue, env LValue) { + tb, ok := env.(*LTable) + if !ok { + ls.RaiseError("cannot use %v as an environment", env.Type().String()) + } + + switch lv := obj.(type) { + case *LFunction: + lv.Env = tb + case *LUserData: + lv.Env = tb + case *LState: + lv.Env = tb + } + /* do nothing */ +} + +/* }}} */ + +/* table operations {{{ */ + +func (ls *LState) RawGet(tb *LTable, key LValue) LValue { + return tb.RawGet(key) +} + +func (ls *LState) RawGetInt(tb *LTable, key int) LValue { + return tb.RawGetInt(key) +} + +func (ls *LState) GetField(obj LValue, skey string) LValue { + return ls.getFieldString(obj, skey) +} + +func (ls *LState) GetTable(obj LValue, key LValue) LValue { + return ls.getField(obj, key) +} + +func (ls *LState) RawSet(tb *LTable, key LValue, value LValue) { + if n, ok := key.(LNumber); ok && math.IsNaN(float64(n)) { + ls.RaiseError("table index is NaN") + } else if key == LNil { + ls.RaiseError("table index is nil") + } + tb.RawSet(key, value) +} + +func (ls *LState) RawSetInt(tb *LTable, key int, value LValue) { + tb.RawSetInt(key, value) +} + +func (ls *LState) SetField(obj LValue, key string, value LValue) { + ls.setFieldString(obj, key, value) +} + +func (ls *LState) SetTable(obj LValue, key LValue, value LValue) { + ls.setField(obj, key, value) +} + +func (ls *LState) ForEach(tb *LTable, cb func(LValue, LValue)) { + tb.ForEach(cb) +} + +func (ls *LState) GetGlobal(name string) LValue { + return ls.GetField(ls.Get(GlobalsIndex), name) +} + +func (ls *LState) SetGlobal(name string, value LValue) { + ls.SetField(ls.Get(GlobalsIndex), name, value) +} + +func (ls *LState) Next(tb *LTable, key LValue) (LValue, LValue) { + return tb.Next(key) +} + +/* }}} */ + +/* unary operations {{{ */ + +func (ls *LState) ObjLen(v1 LValue) int { + if v1.Type() == LTString { + return len(string(v1.(LString))) + } + op := ls.metaOp1(v1, "__len") + if op.Type() == LTFunction { + ls.Push(op) + ls.Push(v1) + ls.Call(1, 1) + ret := ls.reg.Pop() + if ret.Type() == LTNumber { + return int(ret.(LNumber)) + } + } else if v1.Type() == LTTable { + return v1.(*LTable).Len() + } + return 0 +} + +/* }}} */ + +/* binary operations {{{ */ + +func (ls *LState) Concat(values ...LValue) string { + top := ls.reg.Top() + for _, value := range values { + ls.reg.Push(value) + } + ret := stringConcat(ls, len(values), ls.reg.Top()-1) + ls.reg.SetTop(top) + return LVAsString(ret) +} + +func (ls *LState) LessThan(lhs, rhs LValue) bool { + return lessThan(ls, lhs, rhs) +} + +func (ls *LState) Equal(lhs, rhs LValue) bool { + return equals(ls, lhs, rhs, false) +} + +func (ls *LState) RawEqual(lhs, rhs LValue) bool { + return equals(ls, lhs, rhs, true) +} + +/* }}} */ + +/* register operations {{{ */ + +func (ls *LState) Register(name string, fn LGFunction) { + ls.SetGlobal(name, ls.NewFunction(fn)) +} + +/* }}} */ + +/* load and function call operations {{{ */ + +func (ls *LState) Load(reader io.Reader, name string) (*LFunction, error) { + chunk, err := parse.Parse(reader, name) + if err != nil { + return nil, newApiErrorE(ApiErrorSyntax, err) + } + proto, err := Compile(chunk, name) + if err != nil { + return nil, newApiErrorE(ApiErrorSyntax, err) + } + return newLFunctionL(proto, ls.currentEnv(), 0), nil +} + +func (ls *LState) Call(nargs, nret int) { + ls.callR(nargs, nret, -1) +} + +func (ls *LState) PCall(nargs, nret int, errfunc *LFunction) (err error) { + err = nil + sp := ls.stack.Sp() + base := ls.reg.Top() - nargs - 1 + oldpanic := ls.Panic + ls.Panic = panicWithoutTraceback + if errfunc != nil { + ls.hasErrorFunc = true + } + defer func() { + ls.Panic = oldpanic + ls.hasErrorFunc = false + rcv := recover() + if rcv != nil { + if _, ok := rcv.(*ApiError); !ok { + err = newApiErrorS(ApiErrorPanic, fmt.Sprint(rcv)) + if ls.Options.IncludeGoStackTrace { + buf := make([]byte, 4096) + runtime.Stack(buf, false) + err.(*ApiError).StackTrace = strings.Trim(string(buf), "\000") + "\n" + ls.stackTrace(0) + } + } else { + err = rcv.(*ApiError) + } + if errfunc != nil { + ls.Push(errfunc) + ls.Push(err.(*ApiError).Object) + ls.Panic = panicWithoutTraceback + defer func() { + ls.Panic = oldpanic + rcv := recover() + if rcv != nil { + if _, ok := rcv.(*ApiError); !ok { + err = newApiErrorS(ApiErrorPanic, fmt.Sprint(rcv)) + if ls.Options.IncludeGoStackTrace { + buf := make([]byte, 4096) + runtime.Stack(buf, false) + err.(*ApiError).StackTrace = strings.Trim(string(buf), "\000") + ls.stackTrace(0) + } + } else { + err = rcv.(*ApiError) + err.(*ApiError).StackTrace = ls.stackTrace(0) + } + } + }() + ls.Call(1, 1) + err = newApiError(ApiErrorError, ls.Get(-1)) + } else if len(err.(*ApiError).StackTrace) == 0 { + err.(*ApiError).StackTrace = ls.stackTrace(0) + } + ls.stack.SetSp(sp) + ls.currentFrame = ls.stack.Last() + ls.reg.SetTop(base) + } + ls.stack.SetSp(sp) + if sp == 0 { + ls.currentFrame = nil + } + }() + + ls.Call(nargs, nret) + + return +} + +func (ls *LState) GPCall(fn LGFunction, data LValue) error { + ls.Push(newLFunctionG(fn, ls.currentEnv(), 0)) + ls.Push(data) + return ls.PCall(1, MultRet, nil) +} + +func (ls *LState) CallByParam(cp P, args ...LValue) error { + ls.Push(cp.Fn) + for _, arg := range args { + ls.Push(arg) + } + + if cp.Protect { + return ls.PCall(len(args), cp.NRet, cp.Handler) + } + ls.Call(len(args), cp.NRet) + return nil +} + +/* }}} */ + +/* metatable operations {{{ */ + +func (ls *LState) GetMetatable(obj LValue) LValue { + return ls.metatable(obj, false) +} + +func (ls *LState) SetMetatable(obj LValue, mt LValue) { + switch mt.(type) { + case *LNilType, *LTable: + default: + ls.RaiseError("metatable must be a table or nil, but got %v", mt.Type().String()) + } + + switch v := obj.(type) { + case *LTable: + v.Metatable = mt + case *LUserData: + v.Metatable = mt + default: + ls.G.builtinMts[int(obj.Type())] = mt + } +} + +/* }}} */ + +/* coroutine operations {{{ */ + +func (ls *LState) Status(th *LState) string { + status := "suspended" + if th.Dead { + status = "dead" + } else if ls.G.CurrentThread == th { + status = "running" + } else if ls.Parent == th { + status = "normal" + } + return status +} + +func (ls *LState) Resume(th *LState, fn *LFunction, args ...LValue) (ResumeState, error, []LValue) { + isstarted := th.isStarted() + if !isstarted { + base := 0 + th.stack.Push(callFrame{ + Fn: fn, + Pc: 0, + Base: base, + LocalBase: base + 1, + ReturnBase: base, + NArgs: 0, + NRet: MultRet, + Parent: nil, + TailCall: 0, + }) + } + + if ls.G.CurrentThread == th { + return ResumeError, newApiErrorS(ApiErrorRun, "can not resume a running thread"), nil + } + if th.Dead { + return ResumeError, newApiErrorS(ApiErrorRun, "can not resume a dead thread"), nil + } + th.Parent = ls + ls.G.CurrentThread = th + if !isstarted { + cf := th.stack.Last() + th.currentFrame = cf + th.SetTop(0) + for _, arg := range args { + th.Push(arg) + } + cf.NArgs = len(args) + th.initCallFrame(cf) + th.Panic = panicWithoutTraceback + } else { + for _, arg := range args { + th.Push(arg) + } + } + top := ls.GetTop() + threadRun(th) + haserror := LVIsFalse(ls.Get(top + 1)) + ret := make([]LValue, 0, ls.GetTop()) + for idx := top + 2; idx <= ls.GetTop(); idx++ { + ret = append(ret, ls.Get(idx)) + } + if len(ret) == 0 { + ret = append(ret, LNil) + } + ls.SetTop(top) + + if haserror { + return ResumeError, newApiError(ApiErrorRun, ret[0]), nil + } else if th.stack.IsEmpty() { + return ResumeOK, nil, ret + } + return ResumeYield, nil, ret +} + +func (ls *LState) Yield(values ...LValue) int { + ls.SetTop(0) + for _, lv := range values { + ls.Push(lv) + } + return -1 +} + +func (ls *LState) XMoveTo(other *LState, n int) { + if ls == other { + return + } + top := ls.GetTop() + n = intMin(n, top) + for i := n; i > 0; i-- { + other.Push(ls.Get(top - i + 1)) + } + ls.SetTop(top - n) +} + +/* }}} */ + +/* GopherLua original APIs {{{ */ + +// Set maximum memory size. This function can only be called from the main thread. +func (ls *LState) SetMx(mx int) { + if ls.Parent != nil { + ls.RaiseError("sub threads are not allowed to set a memory limit") + } + go func() { + limit := uint64(mx * 1024 * 1024) //MB + var s runtime.MemStats + for atomic.LoadInt32(&ls.stop) == 0 { + runtime.ReadMemStats(&s) + if s.Alloc >= limit { + fmt.Println("out of memory") + os.Exit(3) + } + time.Sleep(100 * time.Millisecond) + } + }() +} + +// SetContext set a context ctx to this LState. The provided ctx must be non-nil. +func (ls *LState) SetContext(ctx context.Context) { + ls.mainLoop = mainLoopWithContext + ls.ctx = ctx +} + +// Context returns the LState's context. To change the context, use WithContext. +func (ls *LState) Context() context.Context { + return ls.ctx +} + +// RemoveContext removes the context associated with this LState and returns this context. +func (ls *LState) RemoveContext() context.Context { + oldctx := ls.ctx + ls.mainLoop = mainLoop + ls.ctx = nil + return oldctx +} + +// Converts the Lua value at the given acceptable index to the chan LValue. +func (ls *LState) ToChannel(n int) chan LValue { + if lv, ok := ls.Get(n).(LChannel); ok { + return (chan LValue)(lv) + } + return nil +} + +// RemoveCallerFrame removes the stack frame above the current stack frame. This is useful in tail calls. It returns +// the new current frame. +func (ls *LState) RemoveCallerFrame() *callFrame { + cs := ls.stack + sp := cs.Sp() + parentFrame := cs.At(sp - 2) + currentFrame := cs.At(sp - 1) + parentsParentFrame := parentFrame.Parent + *parentFrame = *currentFrame + parentFrame.Parent = parentsParentFrame + parentFrame.Idx = sp - 2 + cs.Pop() + return parentFrame +} + +/* }}} */ + +/* }}} */ + +// diff --git a/vendor/github.com/yuin/gopher-lua/_vm.go b/vendor/github.com/yuin/gopher-lua/_vm.go new file mode 100644 index 00000000000..874ed9aa4a7 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/_vm.go @@ -0,0 +1,1033 @@ +package lua + +import ( + "fmt" + "math" + "strings" +) + +func mainLoop(L *LState, baseframe *callFrame) { + var inst uint32 + var cf *callFrame + + if L.stack.IsEmpty() { + return + } + + L.currentFrame = L.stack.Last() + if L.currentFrame.Fn.IsG { + callGFunction(L, false) + return + } + + for { + cf = L.currentFrame + inst = cf.Fn.Proto.Code[cf.Pc] + cf.Pc++ + if jumpTable[int(inst>>26)](L, inst, baseframe) == 1 { + return + } + } +} + +func mainLoopWithContext(L *LState, baseframe *callFrame) { + var inst uint32 + var cf *callFrame + + if L.stack.IsEmpty() { + return + } + + L.currentFrame = L.stack.Last() + if L.currentFrame.Fn.IsG { + callGFunction(L, false) + return + } + + for { + cf = L.currentFrame + inst = cf.Fn.Proto.Code[cf.Pc] + cf.Pc++ + select { + case <-L.ctx.Done(): + L.RaiseError(L.ctx.Err().Error()) + return + default: + if jumpTable[int(inst>>26)](L, inst, baseframe) == 1 { + return + } + } + } +} + +// regv is the first target register to copy the return values to. +// It can be reg.top, indicating that the copied values are going into new registers, or it can be below reg.top +// Indicating that the values should be within the existing registers. +// b is the available number of return values + 1. +// n is the desired number of return values. +// If n more than the available return values then the extra values are set to nil. +// When this function returns the top of the registry will be set to regv+n. +func copyReturnValues(L *LState, regv, start, n, b int) { // +inline-start + if b == 1 { + // +inline-call L.reg.FillNil regv n + } else { + // +inline-call L.reg.CopyRange regv start -1 n + if b > 1 && n > (b-1) { + // +inline-call L.reg.FillNil regv+b-1 n-(b-1) + } + } +} // +inline-end + +func switchToParentThread(L *LState, nargs int, haserror bool, kill bool) { + parent := L.Parent + if parent == nil { + L.RaiseError("can not yield from outside of a coroutine") + } + L.G.CurrentThread = parent + L.Parent = nil + if !L.wrapped { + if haserror { + parent.Push(LFalse) + } else { + parent.Push(LTrue) + } + } + L.XMoveTo(parent, nargs) + L.stack.Pop() + offset := L.currentFrame.LocalBase - L.currentFrame.ReturnBase + L.currentFrame = L.stack.Last() + L.reg.SetTop(L.reg.Top() - offset) // remove 'yield' function(including tailcalled functions) + if kill { + L.kill() + } +} + +func callGFunction(L *LState, tailcall bool) bool { + frame := L.currentFrame + gfnret := frame.Fn.GFunction(L) + if tailcall { + L.currentFrame = L.RemoveCallerFrame() + } + + if gfnret < 0 { + switchToParentThread(L, L.GetTop(), false, false) + return true + } + + wantret := frame.NRet + if wantret == MultRet { + wantret = gfnret + } + + if tailcall && L.Parent != nil && L.stack.Sp() == 1 { + switchToParentThread(L, wantret, false, true) + return true + } + + // +inline-call L.reg.CopyRange frame.ReturnBase L.reg.Top()-gfnret -1 wantret + L.stack.Pop() + L.currentFrame = L.stack.Last() + return false +} + +func threadRun(L *LState) { + if L.stack.IsEmpty() { + return + } + + defer func() { + if rcv := recover(); rcv != nil { + var lv LValue + if v, ok := rcv.(*ApiError); ok { + lv = v.Object + } else { + lv = LString(fmt.Sprint(rcv)) + } + if parent := L.Parent; parent != nil { + if L.wrapped { + L.Push(lv) + parent.Panic(L) + } else { + L.SetTop(0) + L.Push(lv) + switchToParentThread(L, 1, true, true) + } + } else { + panic(rcv) + } + } + }() + L.mainLoop(L, nil) +} + +type instFunc func(*LState, uint32, *callFrame) int + +var jumpTable [opCodeMax + 1]instFunc + +func init() { + jumpTable = [opCodeMax + 1]instFunc{ + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_MOVE + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + reg.Set(RA, reg.Get(lbase+B)) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_MOVEN + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + 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)) + code := cf.Fn.Proto.Code + pc := cf.Pc + for i := 0; i < C; i++ { + inst = code[pc] + pc++ + A = int(inst>>18) & 0xff //GETA + B = int(inst & 0x1ff) //GETB + reg.Set(lbase+A, reg.Get(lbase+B)) + } + cf.Pc = pc + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_LOADK + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + Bx := int(inst & 0x3ffff) //GETBX + reg.Set(RA, cf.Fn.Proto.Constants[Bx]) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_LOADBOOL + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + if B != 0 { + reg.Set(RA, LTrue) + } else { + reg.Set(RA, LFalse) + } + if C != 0 { + cf.Pc++ + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_LOADNIL + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + for i := RA; i <= lbase+B; i++ { + reg.Set(i, LNil) + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_GETUPVAL + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + reg.Set(RA, cf.Fn.Upvalues[B].Value()) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_GETGLOBAL + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + 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])) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_GETTABLE + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + 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))) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_GETTABLEKS + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + 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))) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_SETGLOBAL + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + Bx := int(inst & 0x3ffff) //GETBX + //L.setField(cf.Fn.Env, cf.Fn.Proto.Constants[Bx], reg.Get(RA)) + L.setFieldString(cf.Fn.Env, cf.Fn.Proto.stringConstants[Bx], reg.Get(RA)) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_SETUPVAL + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + cf.Fn.Upvalues[B].SetValue(reg.Get(RA)) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_SETTABLE + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + L.setField(reg.Get(RA), L.rkValue(B), L.rkValue(C)) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_SETTABLEKS + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + L.setFieldString(reg.Get(RA), L.rkString(B), L.rkValue(C)) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_NEWTABLE + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + reg.Set(RA, newLTable(B, C)) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_SELF + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + 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) + return 0 + }, + opArith, // OP_ADD + opArith, // OP_SUB + opArith, // OP_MUL + opArith, // OP_DIV + opArith, // OP_MOD + opArith, // OP_POW + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_UNM + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + unaryv := L.rkValue(B) + if nm, ok := unaryv.(LNumber); ok { + reg.SetNumber(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()) + } else if str, ok1 := unaryv.(LString); ok1 { + if num, err := parseNumber(string(str)); err == nil { + reg.Set(RA, -num) + } else { + L.RaiseError("__unm undefined") + } + } else { + L.RaiseError("__unm undefined") + } + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_NOT + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + if LVIsFalse(reg.Get(lbase + B)) { + reg.Set(RA, LTrue) + } else { + reg.Set(RA, LFalse) + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_LEN + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + switch lv := L.rkValue(B).(type) { + case LString: + reg.SetNumber(RA, LNumber(len(lv))) + default: + op := L.metaOp1(lv, "__len") + if op.Type() == LTFunction { + reg.Push(op) + reg.Push(lv) + L.Call(1, 1) + ret := reg.Pop() + if ret.Type() == LTNumber { + reg.SetNumber(RA, ret.(LNumber)) + } else { + reg.SetNumber(RA, LNumber(0)) + } + } else if lv.Type() == LTTable { + reg.SetNumber(RA, LNumber(lv.(*LTable).Len())) + } else { + L.RaiseError("__len undefined") + } + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_CONCAT + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + RC := lbase + C + RB := lbase + B + reg.Set(RA, stringConcat(L, RC-RB+1, RC)) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_JMP + cf := L.currentFrame + Sbx := int(inst&0x3ffff) - opMaxArgSbx //GETSBX + cf.Pc += Sbx + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_EQ + cf := L.currentFrame + A := int(inst>>18) & 0xff //GETA + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + ret := equals(L, L.rkValue(B), L.rkValue(C), false) + v := 1 + if ret { + v = 0 + } + if v == A { + cf.Pc++ + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_LT + cf := L.currentFrame + A := int(inst>>18) & 0xff //GETA + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + ret := lessThan(L, L.rkValue(B), L.rkValue(C)) + v := 1 + if ret { + v = 0 + } + if v == A { + cf.Pc++ + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_LE + cf := L.currentFrame + A := int(inst>>18) & 0xff //GETA + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + lhs := L.rkValue(B) + rhs := L.rkValue(C) + ret := false + + if v1, ok1 := lhs.assertFloat64(); ok1 { + if v2, ok2 := rhs.assertFloat64(); ok2 { + ret = v1 <= v2 + } else { + L.RaiseError("attempt to compare %v with %v", lhs.Type().String(), rhs.Type().String()) + } + } else { + if lhs.Type() != rhs.Type() { + L.RaiseError("attempt to compare %v with %v", lhs.Type().String(), rhs.Type().String()) + } + switch lhs.Type() { + case LTString: + ret = strCmp(string(lhs.(LString)), string(rhs.(LString))) <= 0 + default: + switch objectRational(L, lhs, rhs, "__le") { + case 1: + ret = true + case 0: + ret = false + default: + ret = !objectRationalWithError(L, rhs, lhs, "__lt") + } + } + } + + v := 1 + if ret { + v = 0 + } + if v == A { + cf.Pc++ + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_TEST + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + C := int(inst>>9) & 0x1ff //GETC + if LVAsBool(reg.Get(RA)) == (C == 0) { + cf.Pc++ + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_TESTSET + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + 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) + } else { + cf.Pc++ + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_CALL + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + nargs := B - 1 + if B == 0 { + nargs = reg.Top() - (RA + 1) + } + lv := reg.Get(RA) + nret := C - 1 + var callable *LFunction + var meta bool + if fn, ok := lv.assertFunction(); ok { + callable = fn + meta = false + } else { + callable, meta = L.metaCall(lv) + } + // +inline-call L.pushCallFrame callFrame{Fn:callable,Pc:0,Base:RA,LocalBase:RA+1,ReturnBase:RA,NArgs:nargs,NRet:nret,Parent:cf,TailCall:0} lv meta + if callable.IsG && callGFunction(L, false) { + return 1 + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_TAILCALL + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + nargs := B - 1 + if B == 0 { + nargs = reg.Top() - (RA + 1) + } + lv := reg.Get(RA) + var callable *LFunction + var meta bool + if fn, ok := lv.assertFunction(); ok { + callable = fn + meta = false + } else { + callable, meta = L.metaCall(lv) + } + if callable == nil { + L.RaiseError("attempt to call a non-function object") + } + // +inline-call L.closeUpvalues lbase + if callable.IsG { + luaframe := cf + L.pushCallFrame(callFrame{ + Fn: callable, + Pc: 0, + Base: RA, + LocalBase: RA + 1, + ReturnBase: cf.ReturnBase, + NArgs: nargs, + NRet: cf.NRet, + Parent: cf, + TailCall: 0, + }, lv, meta) + if callGFunction(L, true) { + return 1 + } + if L.currentFrame == nil || L.currentFrame.Fn.IsG || luaframe == baseframe { + return 1 + } + } else { + base := cf.Base + cf.Fn = callable + cf.Pc = 0 + cf.Base = RA + cf.LocalBase = RA + 1 + cf.ReturnBase = cf.ReturnBase + cf.NArgs = nargs + cf.NRet = cf.NRet + cf.TailCall++ + lbase := cf.LocalBase + if meta { + cf.NArgs++ + L.reg.Insert(lv, cf.LocalBase) + } + // +inline-call L.initCallFrame cf + // +inline-call L.reg.CopyRange base RA -1 reg.Top()-RA-1 + cf.Base = base + cf.LocalBase = base + (cf.LocalBase - lbase + 1) + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_RETURN + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + // +inline-call L.closeUpvalues lbase + nret := B - 1 + if B == 0 { + nret = reg.Top() - RA + } + n := cf.NRet + if cf.NRet == MultRet { + n = nret + } + + if L.Parent != nil && L.stack.Sp() == 1 { + // +inline-call copyReturnValues L reg.Top() RA n B + switchToParentThread(L, n, false, true) + return 1 + } + islast := baseframe == L.stack.Pop() || L.stack.IsEmpty() + // +inline-call copyReturnValues L cf.ReturnBase RA n B + L.currentFrame = L.stack.Last() + if islast || L.currentFrame == nil || L.currentFrame.Fn.IsG { + return 1 + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_FORLOOP + reg := L.reg + cf := L.currentFrame + 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 { + init += step + reg.SetNumber(RA, LNumber(init)) + if (step > 0 && init <= limit) || (step <= 0 && init >= limit) { + Sbx := int(inst&0x3ffff) - opMaxArgSbx //GETSBX + cf.Pc += Sbx + reg.SetNumber(RA+3, LNumber(init)) + } else { + reg.SetTop(RA + 1) + } + } else { + L.RaiseError("for statement step must be a number") + } + } else { + L.RaiseError("for statement limit must be a number") + } + } else { + L.RaiseError("for statement init must be a number") + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_FORPREP + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + 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)) + } else { + L.RaiseError("for statement step must be a number") + } + } else { + L.RaiseError("for statement init must be a number") + } + cf.Pc += Sbx + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_TFORLOOP + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + 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)) + L.callR(2, nret, RA+3) + if value := reg.Get(RA + 3); value != LNil { + reg.Set(RA+2, value) + pc := cf.Fn.Proto.Code[cf.Pc] + cf.Pc += int(pc&0x3ffff) - opMaxArgSbx + } + cf.Pc++ + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_SETLIST + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + if C == 0 { + C = int(cf.Fn.Proto.Code[cf.Pc]) + cf.Pc++ + } + offset := (C - 1) * FieldsPerFlush + table := reg.Get(RA).(*LTable) + nelem := B + if B == 0 { + nelem = reg.Top() - RA - 1 + } + for i := 1; i <= nelem; i++ { + table.RawSetInt(offset+i, reg.Get(RA+i)) + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_CLOSE + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + // +inline-call L.closeUpvalues RA + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_CLOSURE + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + Bx := int(inst & 0x3ffff) //GETBX + proto := cf.Fn.Proto.FunctionPrototypes[Bx] + closure := newLFunctionL(proto, cf.Fn.Env, int(proto.NumUpvalues)) + reg.Set(RA, closure) + for i := 0; i < int(proto.NumUpvalues); i++ { + inst = cf.Fn.Proto.Code[cf.Pc] + cf.Pc++ + B := opGetArgB(inst) + switch opGetOpCode(inst) { + case OP_MOVE: + closure.Upvalues[i] = L.findUpvalue(lbase + B) + case OP_GETUPVAL: + closure.Upvalues[i] = cf.Fn.Upvalues[B] + } + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_VARARG + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + nparams := int(cf.Fn.Proto.NumParameters) + nvarargs := cf.NArgs - nparams + if nvarargs < 0 { + nvarargs = 0 + } + nwant := B - 1 + if B == 0 { + nwant = nvarargs + } + // +inline-call reg.CopyRange RA cf.Base+nparams+1 cf.LocalBase nwant + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_NOP + return 0 + }, + } +} + +func opArith(L *LState, inst uint32, baseframe *callFrame) int { //OP_ADD, OP_SUB, OP_MUL, OP_DIV, OP_MOD, OP_POW + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + opcode := int(inst >> 26) //GETOPCODE + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + lhs := L.rkValue(B) + rhs := L.rkValue(C) + v1, ok1 := lhs.assertFloat64() + v2, ok2 := rhs.assertFloat64() + if ok1 && ok2 { + reg.SetNumber(RA, numberArith(L, opcode, LNumber(v1), LNumber(v2))) + } else { + reg.Set(RA, objectArith(L, opcode, lhs, rhs)) + } + return 0 +} + +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) { + v += frhs + } + return LNumber(v) +} + +func numberArith(L *LState, opcode int, lhs, rhs LNumber) LNumber { + switch opcode { + case OP_ADD: + return lhs + rhs + case OP_SUB: + return lhs - rhs + case OP_MUL: + return lhs * rhs + case OP_DIV: + return lhs / rhs + case OP_MOD: + return luaModulo(lhs, rhs) + case OP_POW: + flhs := float64(lhs) + frhs := float64(rhs) + return LNumber(math.Pow(flhs, frhs)) + } + panic("should not reach here") + return LNumber(0) +} + +func objectArith(L *LState, opcode int, lhs, rhs LValue) LValue { + event := "" + switch opcode { + case OP_ADD: + event = "__add" + case OP_SUB: + event = "__sub" + case OP_MUL: + event = "__mul" + case OP_DIV: + event = "__div" + case OP_MOD: + event = "__mod" + case OP_POW: + event = "__pow" + } + op := L.metaOp2(lhs, rhs, event) + if op.Type() == LTFunction { + L.reg.Push(op) + L.reg.Push(lhs) + L.reg.Push(rhs) + L.Call(2, 1) + return L.reg.Pop() + } + if str, ok := lhs.(LString); ok { + if lnum, err := parseNumber(string(str)); err == nil { + lhs = lnum + } + } + if str, ok := rhs.(LString); ok { + if rnum, err := parseNumber(string(str)); err == nil { + rhs = rnum + } + } + if v1, ok1 := lhs.assertFloat64(); ok1 { + if v2, ok2 := rhs.assertFloat64(); ok2 { + return numberArith(L, opcode, LNumber(v1), LNumber(v2)) + } + } + L.RaiseError(fmt.Sprintf("cannot perform %v operation between %v and %v", + strings.TrimLeft(event, "_"), lhs.Type().String(), rhs.Type().String())) + + return LNil +} + +func stringConcat(L *LState, total, last int) LValue { + rhs := L.reg.Get(last) + total-- + for i := last - 1; total > 0; { + lhs := L.reg.Get(i) + if !(LVCanConvToString(lhs) && LVCanConvToString(rhs)) { + op := L.metaOp2(lhs, rhs, "__concat") + if op.Type() == LTFunction { + L.reg.Push(op) + L.reg.Push(lhs) + L.reg.Push(rhs) + L.Call(2, 1) + rhs = L.reg.Pop() + total-- + i-- + } else { + L.RaiseError("cannot perform concat operation between %v and %v", lhs.Type().String(), rhs.Type().String()) + return LNil + } + } else { + buf := make([]string, total+1) + buf[total] = LVAsString(rhs) + for total > 0 { + lhs = L.reg.Get(i) + if !LVCanConvToString(lhs) { + break + } + buf[total-1] = LVAsString(lhs) + i-- + total-- + } + rhs = LString(strings.Join(buf, "")) + } + } + return rhs +} + +func lessThan(L *LState, lhs, rhs LValue) bool { + // optimization for numbers + if v1, ok1 := lhs.assertFloat64(); ok1 { + if v2, ok2 := rhs.assertFloat64(); ok2 { + return v1 < v2 + } + L.RaiseError("attempt to compare %v with %v", lhs.Type().String(), rhs.Type().String()) + } + if lhs.Type() != rhs.Type() { + L.RaiseError("attempt to compare %v with %v", lhs.Type().String(), rhs.Type().String()) + return false + } + ret := false + switch lhs.Type() { + case LTString: + ret = strCmp(string(lhs.(LString)), string(rhs.(LString))) < 0 + default: + ret = objectRationalWithError(L, lhs, rhs, "__lt") + } + return ret +} + +func equals(L *LState, lhs, rhs LValue, raw bool) bool { + if lhs.Type() != rhs.Type() { + return false + } + + ret := false + switch lhs.Type() { + case LTNil: + ret = true + case LTNumber: + v1, _ := lhs.assertFloat64() + v2, _ := rhs.assertFloat64() + ret = v1 == v2 + case LTBool: + ret = bool(lhs.(LBool)) == bool(rhs.(LBool)) + case LTString: + ret = string(lhs.(LString)) == string(rhs.(LString)) + case LTUserData, LTTable: + if lhs == rhs { + ret = true + } else if !raw { + switch objectRational(L, lhs, rhs, "__eq") { + case 1: + ret = true + default: + ret = false + } + } + default: + ret = lhs == rhs + } + return ret +} + +func objectRationalWithError(L *LState, lhs, rhs LValue, event string) bool { + switch objectRational(L, lhs, rhs, event) { + case 1: + return true + case 0: + return false + } + L.RaiseError("attempt to compare %v with %v", lhs.Type().String(), rhs.Type().String()) + return false +} + +func objectRational(L *LState, lhs, rhs LValue, event string) int { + m1 := L.metaOp1(lhs, event) + m2 := L.metaOp1(rhs, event) + if m1.Type() == LTFunction && m1 == m2 { + L.reg.Push(m1) + L.reg.Push(lhs) + L.reg.Push(rhs) + L.Call(2, 1) + if LVAsBool(L.reg.Pop()) { + return 1 + } + return 0 + } + return -1 +} diff --git a/vendor/github.com/yuin/gopher-lua/alloc.go b/vendor/github.com/yuin/gopher-lua/alloc.go new file mode 100644 index 00000000000..7a8cd63ac10 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/alloc.go @@ -0,0 +1,79 @@ +package lua + +import ( + "reflect" + "unsafe" +) + +// iface is an internal representation of the go-interface. +type iface struct { + itab unsafe.Pointer + word unsafe.Pointer +} + +const preloadLimit LNumber = 128 + +var _fv float64 +var _uv uintptr + +var preloads [int(preloadLimit)]LValue + +func init() { + for i := 0; i < int(preloadLimit); i++ { + preloads[i] = LNumber(i) + } +} + +// allocator is a fast bulk memory allocator for the LValue. +type allocator struct { + size int + fptrs []float64 + fheader *reflect.SliceHeader + + scratchValue LValue + scratchValueP *iface +} + +func newAllocator(size int) *allocator { + al := &allocator{ + size: size, + fptrs: make([]float64, 0, size), + fheader: nil, + } + al.fheader = (*reflect.SliceHeader)(unsafe.Pointer(&al.fptrs)) + al.scratchValue = LNumber(0) + al.scratchValueP = (*iface)(unsafe.Pointer(&al.scratchValue)) + + return al +} + +// LNumber2I takes a number value and returns an interface LValue representing the same number. +// Converting an LNumber to a LValue naively, by doing: +// `var val LValue = myLNumber` +// will result in an individual heap alloc of 8 bytes for the float value. LNumber2I amortizes the cost and memory +// overhead of these allocs by allocating blocks of floats instead. +// The downside of this is that all of the floats on a given block have to become eligible for gc before the block +// as a whole can be gc-ed. +func (al *allocator) LNumber2I(v LNumber) LValue { + // first check for shared preloaded numbers + if v >= 0 && v < preloadLimit && float64(v) == float64(int64(v)) { + return preloads[int(v)] + } + + // check if we need a new alloc page + if cap(al.fptrs) == len(al.fptrs) { + al.fptrs = make([]float64, 0, al.size) + al.fheader = (*reflect.SliceHeader)(unsafe.Pointer(&al.fptrs)) + } + + // alloc a new float, and store our value into it + al.fptrs = append(al.fptrs, float64(v)) + fptr := &al.fptrs[len(al.fptrs)-1] + + // hack our scratch LValue to point to our allocated value + // this scratch lvalue is copied when this function returns meaning the scratch value can be reused + // on the next call + al.scratchValueP.word = unsafe.Pointer(fptr) + + return al.scratchValue +} diff --git a/vendor/github.com/yuin/gopher-lua/ast/ast.go b/vendor/github.com/yuin/gopher-lua/ast/ast.go new file mode 100644 index 00000000000..f337a294732 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/ast/ast.go @@ -0,0 +1,29 @@ +package ast + +type PositionHolder interface { + Line() int + SetLine(int) + LastLine() int + SetLastLine(int) +} + +type Node struct { + line int + lastline int +} + +func (self *Node) Line() int { + return self.line +} + +func (self *Node) SetLine(line int) { + self.line = line +} + +func (self *Node) LastLine() int { + return self.lastline +} + +func (self *Node) SetLastLine(line int) { + self.lastline = line +} diff --git a/vendor/github.com/yuin/gopher-lua/ast/expr.go b/vendor/github.com/yuin/gopher-lua/ast/expr.go new file mode 100644 index 00000000000..ccda3279101 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/ast/expr.go @@ -0,0 +1,137 @@ +package ast + +type Expr interface { + PositionHolder + exprMarker() +} + +type ExprBase struct { + Node +} + +func (expr *ExprBase) exprMarker() {} + +/* ConstExprs {{{ */ + +type ConstExpr interface { + Expr + constExprMarker() +} + +type ConstExprBase struct { + ExprBase +} + +func (expr *ConstExprBase) constExprMarker() {} + +type TrueExpr struct { + ConstExprBase +} + +type FalseExpr struct { + ConstExprBase +} + +type NilExpr struct { + ConstExprBase +} + +type NumberExpr struct { + ConstExprBase + + Value string +} + +type StringExpr struct { + ConstExprBase + + Value string +} + +/* ConstExprs }}} */ + +type Comma3Expr struct { + ExprBase +} + +type IdentExpr struct { + ExprBase + + Value string +} + +type AttrGetExpr struct { + ExprBase + + Object Expr + Key Expr +} + +type TableExpr struct { + ExprBase + + Fields []*Field +} + +type FuncCallExpr struct { + ExprBase + + Func Expr + Receiver Expr + Method string + Args []Expr + AdjustRet bool +} + +type LogicalOpExpr struct { + ExprBase + + Operator string + Lhs Expr + Rhs Expr +} + +type RelationalOpExpr struct { + ExprBase + + Operator string + Lhs Expr + Rhs Expr +} + +type StringConcatOpExpr struct { + ExprBase + + Lhs Expr + Rhs Expr +} + +type ArithmeticOpExpr struct { + ExprBase + + Operator string + Lhs Expr + Rhs Expr +} + +type UnaryMinusOpExpr struct { + ExprBase + Expr Expr +} + +type UnaryNotOpExpr struct { + ExprBase + Expr Expr +} + +type UnaryLenOpExpr struct { + ExprBase + Expr Expr +} + +type FunctionExpr struct { + ExprBase + + ParList *ParList + Stmts []Stmt +} diff --git a/vendor/github.com/yuin/gopher-lua/ast/misc.go b/vendor/github.com/yuin/gopher-lua/ast/misc.go new file mode 100644 index 00000000000..d811c042aa0 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/ast/misc.go @@ -0,0 +1,17 @@ +package ast + +type Field struct { + Key Expr + Value Expr +} + +type ParList struct { + HasVargs bool + Names []string +} + +type FuncName struct { + Func Expr + Receiver Expr + Method string +} diff --git a/vendor/github.com/yuin/gopher-lua/ast/stmt.go b/vendor/github.com/yuin/gopher-lua/ast/stmt.go new file mode 100644 index 00000000000..56ea6d1a23a --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/ast/stmt.go @@ -0,0 +1,95 @@ +package ast + +type Stmt interface { + PositionHolder + stmtMarker() +} + +type StmtBase struct { + Node +} + +func (stmt *StmtBase) stmtMarker() {} + +type AssignStmt struct { + StmtBase + + Lhs []Expr + Rhs []Expr +} + +type LocalAssignStmt struct { + StmtBase + + Names []string + Exprs []Expr +} + +type FuncCallStmt struct { + StmtBase + + Expr Expr +} + +type DoBlockStmt struct { + StmtBase + + Stmts []Stmt +} + +type WhileStmt struct { + StmtBase + + Condition Expr + Stmts []Stmt +} + +type RepeatStmt struct { + StmtBase + + Condition Expr + Stmts []Stmt +} + +type IfStmt struct { + StmtBase + + Condition Expr + Then []Stmt + Else []Stmt +} + +type NumberForStmt struct { + StmtBase + + Name string + Init Expr + Limit Expr + Step Expr + Stmts []Stmt +} + +type GenericForStmt struct { + StmtBase + + Names []string + Exprs []Expr + Stmts []Stmt +} + +type FuncDefStmt struct { + StmtBase + + Name *FuncName + Func *FunctionExpr +} + +type ReturnStmt struct { + StmtBase + + Exprs []Expr +} + +type BreakStmt struct { + StmtBase +} diff --git a/vendor/github.com/yuin/gopher-lua/ast/token.go b/vendor/github.com/yuin/gopher-lua/ast/token.go new file mode 100644 index 00000000000..820467c9a86 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/ast/token.go @@ -0,0 +1,22 @@ +package ast + +import ( + "fmt" +) + +type Position struct { + Source string + Line int + Column int +} + +type Token struct { + Type int + Name string + Str string + Pos Position +} + +func (self *Token) String() string { + return fmt.Sprintf("", self.Name, self.Str) +} diff --git a/vendor/github.com/yuin/gopher-lua/auxlib.go b/vendor/github.com/yuin/gopher-lua/auxlib.go new file mode 100644 index 00000000000..61a3b8b6100 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/auxlib.go @@ -0,0 +1,460 @@ +package lua + +import ( + "bufio" + "fmt" + "io" + "os" + "strings" +) + +/* checkType {{{ */ + +func (ls *LState) CheckAny(n int) LValue { + if n > ls.GetTop() { + ls.ArgError(n, "value expected") + } + return ls.Get(n) +} + +func (ls *LState) CheckInt(n int) int { + v := ls.Get(n) + if intv, ok := v.(LNumber); ok { + return int(intv) + } + ls.TypeError(n, LTNumber) + return 0 +} + +func (ls *LState) CheckInt64(n int) int64 { + v := ls.Get(n) + if intv, ok := v.(LNumber); ok { + return int64(intv) + } + ls.TypeError(n, LTNumber) + return 0 +} + +func (ls *LState) CheckNumber(n int) LNumber { + v := ls.Get(n) + if lv, ok := v.(LNumber); ok { + return lv + } + ls.TypeError(n, LTNumber) + return 0 +} + +func (ls *LState) CheckString(n int) string { + v := ls.Get(n) + if lv, ok := v.(LString); ok { + return string(lv) + } else if LVCanConvToString(v) { + return ls.ToString(n) + } + ls.TypeError(n, LTString) + return "" +} + +func (ls *LState) CheckBool(n int) bool { + v := ls.Get(n) + if lv, ok := v.(LBool); ok { + return bool(lv) + } + ls.TypeError(n, LTBool) + return false +} + +func (ls *LState) CheckTable(n int) *LTable { + v := ls.Get(n) + if lv, ok := v.(*LTable); ok { + return lv + } + ls.TypeError(n, LTTable) + return nil +} + +func (ls *LState) CheckFunction(n int) *LFunction { + v := ls.Get(n) + if lv, ok := v.(*LFunction); ok { + return lv + } + ls.TypeError(n, LTFunction) + return nil +} + +func (ls *LState) CheckUserData(n int) *LUserData { + v := ls.Get(n) + if lv, ok := v.(*LUserData); ok { + return lv + } + ls.TypeError(n, LTUserData) + return nil +} + +func (ls *LState) CheckThread(n int) *LState { + v := ls.Get(n) + if lv, ok := v.(*LState); ok { + return lv + } + ls.TypeError(n, LTThread) + return nil +} + +func (ls *LState) CheckType(n int, typ LValueType) { + v := ls.Get(n) + if v.Type() != typ { + ls.TypeError(n, typ) + } +} + +func (ls *LState) CheckTypes(n int, typs ...LValueType) { + vt := ls.Get(n).Type() + for _, typ := range typs { + if vt == typ { + return + } + } + buf := []string{} + for _, typ := range typs { + buf = append(buf, typ.String()) + } + ls.ArgError(n, strings.Join(buf, " or ")+" expected, got "+ls.Get(n).Type().String()) +} + +func (ls *LState) CheckOption(n int, options []string) int { + str := ls.CheckString(n) + for i, v := range options { + if v == str { + return i + } + } + ls.ArgError(n, fmt.Sprintf("invalid option: %s (must be one of %s)", str, strings.Join(options, ","))) + return 0 +} + +/* }}} */ + +/* optType {{{ */ + +func (ls *LState) OptInt(n int, d int) int { + v := ls.Get(n) + if v == LNil { + return d + } + if intv, ok := v.(LNumber); ok { + return int(intv) + } + ls.TypeError(n, LTNumber) + return 0 +} + +func (ls *LState) OptInt64(n int, d int64) int64 { + v := ls.Get(n) + if v == LNil { + return d + } + if intv, ok := v.(LNumber); ok { + return int64(intv) + } + ls.TypeError(n, LTNumber) + return 0 +} + +func (ls *LState) OptNumber(n int, d LNumber) LNumber { + v := ls.Get(n) + if v == LNil { + return d + } + if lv, ok := v.(LNumber); ok { + return lv + } + ls.TypeError(n, LTNumber) + return 0 +} + +func (ls *LState) OptString(n int, d string) string { + v := ls.Get(n) + if v == LNil { + return d + } + if lv, ok := v.(LString); ok { + return string(lv) + } + ls.TypeError(n, LTString) + return "" +} + +func (ls *LState) OptBool(n int, d bool) bool { + v := ls.Get(n) + if v == LNil { + return d + } + if lv, ok := v.(LBool); ok { + return bool(lv) + } + ls.TypeError(n, LTBool) + return false +} + +func (ls *LState) OptTable(n int, d *LTable) *LTable { + v := ls.Get(n) + if v == LNil { + return d + } + if lv, ok := v.(*LTable); ok { + return lv + } + ls.TypeError(n, LTTable) + return nil +} + +func (ls *LState) OptFunction(n int, d *LFunction) *LFunction { + v := ls.Get(n) + if v == LNil { + return d + } + if lv, ok := v.(*LFunction); ok { + return lv + } + ls.TypeError(n, LTFunction) + return nil +} + +func (ls *LState) OptUserData(n int, d *LUserData) *LUserData { + v := ls.Get(n) + if v == LNil { + return d + } + if lv, ok := v.(*LUserData); ok { + return lv + } + ls.TypeError(n, LTUserData) + return nil +} + +/* }}} */ + +/* error operations {{{ */ + +func (ls *LState) ArgError(n int, message string) { + ls.RaiseError("bad argument #%v to %v (%v)", n, ls.rawFrameFuncName(ls.currentFrame), message) +} + +func (ls *LState) TypeError(n int, typ LValueType) { + ls.RaiseError("bad argument #%v to %v (%v expected, got %v)", n, ls.rawFrameFuncName(ls.currentFrame), typ.String(), ls.Get(n).Type().String()) +} + +/* }}} */ + +/* debug operations {{{ */ + +func (ls *LState) Where(level int) string { + return ls.where(level, false) +} + +/* }}} */ + +/* table operations {{{ */ + +func (ls *LState) FindTable(obj *LTable, n string, size int) LValue { + names := strings.Split(n, ".") + curobj := obj + for _, name := range names { + if curobj.Type() != LTTable { + return LNil + } + nextobj := ls.RawGet(curobj, LString(name)) + if nextobj == LNil { + tb := ls.CreateTable(0, size) + ls.RawSet(curobj, LString(name), tb) + curobj = tb + } else if nextobj.Type() != LTTable { + return LNil + } else { + curobj = nextobj.(*LTable) + } + } + return curobj +} + +/* }}} */ + +/* register operations {{{ */ + +func (ls *LState) RegisterModule(name string, funcs map[string]LGFunction) LValue { + tb := ls.FindTable(ls.Get(RegistryIndex).(*LTable), "_LOADED", 1) + mod := ls.GetField(tb, name) + if mod.Type() != LTTable { + newmod := ls.FindTable(ls.Get(GlobalsIndex).(*LTable), name, len(funcs)) + if newmodtb, ok := newmod.(*LTable); !ok { + ls.RaiseError("name conflict for module(%v)", name) + } else { + for fname, fn := range funcs { + newmodtb.RawSetString(fname, ls.NewFunction(fn)) + } + ls.SetField(tb, name, newmodtb) + return newmodtb + } + } + return mod +} + +func (ls *LState) SetFuncs(tb *LTable, funcs map[string]LGFunction, upvalues ...LValue) *LTable { + for fname, fn := range funcs { + tb.RawSetString(fname, ls.NewClosure(fn, upvalues...)) + } + return tb +} + +/* }}} */ + +/* metatable operations {{{ */ + +func (ls *LState) NewTypeMetatable(typ string) *LTable { + regtable := ls.Get(RegistryIndex) + mt := ls.GetField(regtable, typ) + if tb, ok := mt.(*LTable); ok { + return tb + } + mtnew := ls.NewTable() + ls.SetField(regtable, typ, mtnew) + return mtnew +} + +func (ls *LState) GetMetaField(obj LValue, event string) LValue { + return ls.metaOp1(obj, event) +} + +func (ls *LState) GetTypeMetatable(typ string) LValue { + return ls.GetField(ls.Get(RegistryIndex), typ) +} + +func (ls *LState) CallMeta(obj LValue, event string) LValue { + op := ls.metaOp1(obj, event) + if op.Type() == LTFunction { + ls.reg.Push(op) + ls.reg.Push(obj) + ls.Call(1, 1) + return ls.reg.Pop() + } + return LNil +} + +/* }}} */ + +/* load and function call operations {{{ */ + +func (ls *LState) LoadFile(path string) (*LFunction, error) { + var file *os.File + var err error + if len(path) == 0 { + file = os.Stdin + } else { + file, err = os.Open(path) + defer file.Close() + if err != nil { + return nil, newApiErrorE(ApiErrorFile, err) + } + } + + reader := bufio.NewReader(file) + // get the first character. + c, err := reader.ReadByte() + if err != nil && err != io.EOF { + return nil, newApiErrorE(ApiErrorFile, err) + } + if c == byte('#') { + // Unix exec. file? + // skip first line + _, err, _ = readBufioLine(reader) + if err != nil { + return nil, newApiErrorE(ApiErrorFile, err) + } + } + + if err != io.EOF { + // if the file is not empty, + // unread the first character of the file or newline character(readBufioLine's last byte). + err = reader.UnreadByte() + if err != nil { + return nil, newApiErrorE(ApiErrorFile, err) + } + } + + return ls.Load(reader, path) +} + +func (ls *LState) LoadString(source string) (*LFunction, error) { + return ls.Load(strings.NewReader(source), "") +} + +func (ls *LState) DoFile(path string) error { + if fn, err := ls.LoadFile(path); err != nil { + return err + } else { + ls.Push(fn) + return ls.PCall(0, MultRet, nil) + } +} + +func (ls *LState) DoString(source string) error { + if fn, err := ls.LoadString(source); err != nil { + return err + } else { + ls.Push(fn) + return ls.PCall(0, MultRet, nil) + } +} + +/* }}} */ + +/* GopherLua original APIs {{{ */ + +// 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 { + ls.Push(fn) + ls.Push(lv) + ls.Call(1, 1) + return ls.reg.Pop() + } else { + return LString(lv.String()) + } +} + +// Set a module loader to the package.preload table. +func (ls *LState) PreloadModule(name string, loader LGFunction) { + preload := ls.GetField(ls.GetField(ls.Get(EnvironIndex), "package"), "preload") + if _, ok := preload.(*LTable); !ok { + ls.RaiseError("package.preload must be a table") + } + ls.SetField(preload, name, ls.NewFunction(loader)) +} + +// Checks whether the given index is an LChannel and returns this channel. +func (ls *LState) CheckChannel(n int) chan LValue { + v := ls.Get(n) + if ch, ok := v.(LChannel); ok { + return (chan LValue)(ch) + } + ls.TypeError(n, LTChannel) + return nil +} + +// If the given index is a LChannel, returns this channel. If this argument is absent or is nil, returns ch. Otherwise, raises an error. +func (ls *LState) OptChannel(n int, ch chan LValue) chan LValue { + v := ls.Get(n) + if v == LNil { + return ch + } + if ch, ok := v.(LChannel); ok { + return (chan LValue)(ch) + } + ls.TypeError(n, LTChannel) + return nil +} + +/* }}} */ + +// diff --git a/vendor/github.com/yuin/gopher-lua/baselib.go b/vendor/github.com/yuin/gopher-lua/baselib.go new file mode 100644 index 00000000000..06c90619eef --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/baselib.go @@ -0,0 +1,592 @@ +package lua + +import ( + "fmt" + "io" + "os" + "runtime" + "strconv" + "strings" +) + +/* basic functions {{{ */ + +func OpenBase(L *LState) int { + global := L.Get(GlobalsIndex).(*LTable) + L.SetGlobal("_G", global) + L.SetGlobal("_VERSION", LString(LuaVersion)) + L.SetGlobal("_GOPHER_LUA_VERSION", LString(PackageName+" "+PackageVersion)) + basemod := L.RegisterModule("_G", baseFuncs) + global.RawSetString("ipairs", L.NewClosure(baseIpairs, L.NewFunction(ipairsaux))) + global.RawSetString("pairs", L.NewClosure(basePairs, L.NewFunction(pairsaux))) + L.Push(basemod) + return 1 +} + +var baseFuncs = map[string]LGFunction{ + "assert": baseAssert, + "collectgarbage": baseCollectGarbage, + "dofile": baseDoFile, + "error": baseError, + "getfenv": baseGetFEnv, + "getmetatable": baseGetMetatable, + "load": baseLoad, + "loadfile": baseLoadFile, + "loadstring": baseLoadString, + "next": baseNext, + "pcall": basePCall, + "print": basePrint, + "rawequal": baseRawEqual, + "rawget": baseRawGet, + "rawset": baseRawSet, + "select": baseSelect, + "_printregs": base_PrintRegs, + "setfenv": baseSetFEnv, + "setmetatable": baseSetMetatable, + "tonumber": baseToNumber, + "tostring": baseToString, + "type": baseType, + "unpack": baseUnpack, + "xpcall": baseXPCall, + // loadlib + "module": loModule, + "require": loRequire, + // hidden features + "newproxy": baseNewProxy, +} + +func baseAssert(L *LState) int { + if !L.ToBool(1) { + L.RaiseError(L.OptString(2, "assertion failed!")) + return 0 + } + return L.GetTop() +} + +func baseCollectGarbage(L *LState) int { + runtime.GC() + return 0 +} + +func baseDoFile(L *LState) int { + src := L.ToString(1) + top := L.GetTop() + fn, err := L.LoadFile(src) + if err != nil { + L.Push(LString(err.Error())) + L.Panic(L) + } + L.Push(fn) + L.Call(0, MultRet) + return L.GetTop() - top +} + +func baseError(L *LState) int { + obj := L.CheckAny(1) + level := L.OptInt(2, 1) + L.Error(obj, level) + return 0 +} + +func baseGetFEnv(L *LState) int { + var value LValue + if L.GetTop() == 0 { + value = LNumber(1) + } else { + value = L.Get(1) + } + + if fn, ok := value.(*LFunction); ok { + if !fn.IsG { + L.Push(fn.Env) + } else { + L.Push(L.G.Global) + } + return 1 + } + + if number, ok := value.(LNumber); ok { + level := int(float64(number)) + if level <= 0 { + L.Push(L.Env) + } else { + cf := L.currentFrame + for i := 0; i < level && cf != nil; i++ { + cf = cf.Parent + } + if cf == nil || cf.Fn.IsG { + L.Push(L.G.Global) + } else { + L.Push(cf.Fn.Env) + } + } + return 1 + } + + L.Push(L.G.Global) + return 1 +} + +func baseGetMetatable(L *LState) int { + L.Push(L.GetMetatable(L.CheckAny(1))) + return 1 +} + +func ipairsaux(L *LState) int { + tb := L.CheckTable(1) + i := L.CheckInt(2) + i++ + v := tb.RawGetInt(i) + if v == LNil { + return 0 + } else { + L.Pop(1) + L.Push(LNumber(i)) + L.Push(LNumber(i)) + L.Push(v) + return 2 + } +} + +func baseIpairs(L *LState) int { + tb := L.CheckTable(1) + L.Push(L.Get(UpvalueIndex(1))) + L.Push(tb) + L.Push(LNumber(0)) + return 3 +} + +func loadaux(L *LState, reader io.Reader, chunkname string) int { + if fn, err := L.Load(reader, chunkname); err != nil { + L.Push(LNil) + L.Push(LString(err.Error())) + return 2 + } else { + L.Push(fn) + return 1 + } +} + +func baseLoad(L *LState) int { + fn := L.CheckFunction(1) + chunkname := L.OptString(2, "?") + top := L.GetTop() + buf := []string{} + for { + L.SetTop(top) + L.Push(fn) + L.Call(0, 1) + ret := L.reg.Pop() + if ret == LNil { + break + } else if LVCanConvToString(ret) { + str := ret.String() + if len(str) > 0 { + buf = append(buf, string(str)) + } else { + break + } + } else { + L.Push(LNil) + L.Push(LString("reader function must return a string")) + return 2 + } + } + return loadaux(L, strings.NewReader(strings.Join(buf, "")), chunkname) +} + +func baseLoadFile(L *LState) int { + var reader io.Reader + var chunkname string + var err error + if L.GetTop() < 1 { + reader = os.Stdin + chunkname = "" + } else { + chunkname = L.CheckString(1) + reader, err = os.Open(chunkname) + if err != nil { + L.Push(LNil) + L.Push(LString(fmt.Sprintf("can not open file: %v", chunkname))) + return 2 + } + defer reader.(*os.File).Close() + } + return loadaux(L, reader, chunkname) +} + +func baseLoadString(L *LState) int { + return loadaux(L, strings.NewReader(L.CheckString(1)), L.OptString(2, "")) +} + +func baseNext(L *LState) int { + tb := L.CheckTable(1) + index := LNil + if L.GetTop() >= 2 { + index = L.Get(2) + } + key, value := tb.Next(index) + if key == LNil { + L.Push(LNil) + return 1 + } + L.Push(key) + L.Push(value) + return 2 +} + +func pairsaux(L *LState) int { + tb := L.CheckTable(1) + key, value := tb.Next(L.Get(2)) + if key == LNil { + return 0 + } else { + L.Pop(1) + L.Push(key) + L.Push(key) + L.Push(value) + return 2 + } +} + +func basePairs(L *LState) int { + tb := L.CheckTable(1) + L.Push(L.Get(UpvalueIndex(1))) + L.Push(tb) + L.Push(LNil) + return 3 +} + +func basePCall(L *LState) int { + L.CheckAny(1) + v := L.Get(1) + if v.Type() != LTFunction { + L.Push(LFalse) + L.Push(LString("attempt to call a " + v.Type().String() + " value")) + return 2 + } + nargs := L.GetTop() - 1 + if err := L.PCall(nargs, MultRet, nil); err != nil { + L.Push(LFalse) + if aerr, ok := err.(*ApiError); ok { + L.Push(aerr.Object) + } else { + L.Push(LString(err.Error())) + } + return 2 + } else { + L.Insert(LTrue, 1) + return L.GetTop() + } +} + +func basePrint(L *LState) int { + top := L.GetTop() + for i := 1; i <= top; i++ { + fmt.Print(L.ToStringMeta(L.Get(i)).String()) + if i != top { + fmt.Print("\t") + } + } + fmt.Println("") + return 0 +} + +func base_PrintRegs(L *LState) int { + L.printReg() + return 0 +} + +func baseRawEqual(L *LState) int { + if L.CheckAny(1) == L.CheckAny(2) { + L.Push(LTrue) + } else { + L.Push(LFalse) + } + return 1 +} + +func baseRawGet(L *LState) int { + L.Push(L.RawGet(L.CheckTable(1), L.CheckAny(2))) + return 1 +} + +func baseRawSet(L *LState) int { + L.RawSet(L.CheckTable(1), L.CheckAny(2), L.CheckAny(3)) + return 0 +} + +func baseSelect(L *LState) int { + L.CheckTypes(1, LTNumber, LTString) + switch lv := L.Get(1).(type) { + case LNumber: + idx := int(lv) + num := L.reg.Top() - L.indexToReg(int(lv)) - 1 + if idx < 0 { + num++ + } + return num + case LString: + if string(lv) != "#" { + L.ArgError(1, "invalid string '"+string(lv)+"'") + } + L.Push(LNumber(L.GetTop() - 1)) + return 1 + } + return 0 +} + +func baseSetFEnv(L *LState) int { + var value LValue + if L.GetTop() == 0 { + value = LNumber(1) + } else { + value = L.Get(1) + } + env := L.CheckTable(2) + + if fn, ok := value.(*LFunction); ok { + if fn.IsG { + L.RaiseError("cannot change the environment of given object") + } else { + fn.Env = env + L.Push(fn) + return 1 + } + } + + if number, ok := value.(LNumber); ok { + level := int(float64(number)) + if level <= 0 { + L.Env = env + return 0 + } + + cf := L.currentFrame + for i := 0; i < level && cf != nil; i++ { + cf = cf.Parent + } + if cf == nil || cf.Fn.IsG { + L.RaiseError("cannot change the environment of given object") + } else { + cf.Fn.Env = env + L.Push(cf.Fn) + return 1 + } + } + + L.RaiseError("cannot change the environment of given object") + return 0 +} + +func baseSetMetatable(L *LState) int { + L.CheckTypes(2, LTNil, LTTable) + obj := L.Get(1) + if obj == LNil { + L.RaiseError("cannot set metatable to a nil object.") + } + mt := L.Get(2) + if m := L.metatable(obj, true); m != LNil { + if tb, ok := m.(*LTable); ok && tb.RawGetString("__metatable") != LNil { + L.RaiseError("cannot change a protected metatable") + } + } + L.SetMetatable(obj, mt) + L.SetTop(1) + return 1 +} + +func baseToNumber(L *LState) int { + base := L.OptInt(2, 10) + noBase := L.Get(2) == LNil + + switch lv := L.CheckAny(1).(type) { + case LNumber: + L.Push(lv) + case LString: + str := strings.Trim(string(lv), " \n\t") + if strings.Index(str, ".") > -1 { + if v, err := strconv.ParseFloat(str, LNumberBit); err != nil { + L.Push(LNil) + } else { + L.Push(LNumber(v)) + } + } else { + if noBase && strings.HasPrefix(strings.ToLower(str), "0x") { + base, str = 16, str[2:] // Hex number + } + if v, err := strconv.ParseInt(str, base, LNumberBit); err != nil { + L.Push(LNil) + } else { + L.Push(LNumber(v)) + } + } + default: + L.Push(LNil) + } + return 1 +} + +func baseToString(L *LState) int { + v1 := L.CheckAny(1) + L.Push(L.ToStringMeta(v1)) + return 1 +} + +func baseType(L *LState) int { + L.Push(LString(L.CheckAny(1).Type().String())) + return 1 +} + +func baseUnpack(L *LState) int { + tb := L.CheckTable(1) + start := L.OptInt(2, 1) + end := L.OptInt(3, tb.Len()) + for i := start; i <= end; i++ { + L.Push(tb.RawGetInt(i)) + } + ret := end - start + 1 + if ret < 0 { + return 0 + } + return ret +} + +func baseXPCall(L *LState) int { + fn := L.CheckFunction(1) + errfunc := L.CheckFunction(2) + + top := L.GetTop() + L.Push(fn) + if err := L.PCall(0, MultRet, errfunc); err != nil { + L.Push(LFalse) + if aerr, ok := err.(*ApiError); ok { + L.Push(aerr.Object) + } else { + L.Push(LString(err.Error())) + } + return 2 + } else { + L.Insert(LTrue, top+1) + return L.GetTop() - top + } +} + +/* }}} */ + +/* load lib {{{ */ + +func loModule(L *LState) int { + name := L.CheckString(1) + loaded := L.GetField(L.Get(RegistryIndex), "_LOADED") + tb := L.GetField(loaded, name) + if _, ok := tb.(*LTable); !ok { + tb = L.FindTable(L.Get(GlobalsIndex).(*LTable), name, 1) + if tb == LNil { + L.RaiseError("name conflict for module: %v", name) + } + L.SetField(loaded, name, tb) + } + if L.GetField(tb, "_NAME") == LNil { + L.SetField(tb, "_M", tb) + L.SetField(tb, "_NAME", LString(name)) + names := strings.Split(name, ".") + pname := "" + if len(names) > 1 { + pname = strings.Join(names[:len(names)-1], ".") + "." + } + L.SetField(tb, "_PACKAGE", LString(pname)) + } + + caller := L.currentFrame.Parent + if caller == nil { + L.RaiseError("no calling stack.") + } else if caller.Fn.IsG { + L.RaiseError("module() can not be called from GFunctions.") + } + L.SetFEnv(caller.Fn, tb) + + top := L.GetTop() + for i := 2; i <= top; i++ { + L.Push(L.Get(i)) + L.Push(tb) + L.Call(1, 0) + } + L.Push(tb) + return 1 +} + +var loopdetection = &LUserData{} + +func loRequire(L *LState) int { + name := L.CheckString(1) + loaded := L.GetField(L.Get(RegistryIndex), "_LOADED") + lv := L.GetField(loaded, name) + if LVAsBool(lv) { + if lv == loopdetection { + L.RaiseError("loop or previous error loading module: %s", name) + } + L.Push(lv) + return 1 + } + loaders, ok := L.GetField(L.Get(RegistryIndex), "_LOADERS").(*LTable) + if !ok { + L.RaiseError("package.loaders must be a table") + } + messages := []string{} + var modasfunc LValue + for i := 1; ; i++ { + loader := L.RawGetInt(loaders, i) + if loader == LNil { + L.RaiseError("module %s not found:\n\t%s, ", name, strings.Join(messages, "\n\t")) + } + L.Push(loader) + L.Push(LString(name)) + L.Call(1, 1) + ret := L.reg.Pop() + switch retv := ret.(type) { + case *LFunction: + modasfunc = retv + goto loopbreak + case LString: + messages = append(messages, string(retv)) + } + } +loopbreak: + L.SetField(loaded, name, loopdetection) + L.Push(modasfunc) + L.Push(LString(name)) + L.Call(1, 1) + ret := L.reg.Pop() + modv := L.GetField(loaded, name) + if ret != LNil && modv == loopdetection { + L.SetField(loaded, name, ret) + L.Push(ret) + } else if modv == loopdetection { + L.SetField(loaded, name, LTrue) + L.Push(LTrue) + } else { + L.Push(modv) + } + return 1 +} + +/* }}} */ + +/* hidden features {{{ */ + +func baseNewProxy(L *LState) int { + ud := L.NewUserData() + L.SetTop(1) + if L.Get(1) == LTrue { + L.SetMetatable(ud, L.NewTable()) + } else if d, ok := L.Get(1).(*LUserData); ok { + L.SetMetatable(ud, L.GetMetatable(d)) + } + L.Push(ud) + return 1 +} + +/* }}} */ + +// diff --git a/vendor/github.com/yuin/gopher-lua/channellib.go b/vendor/github.com/yuin/gopher-lua/channellib.go new file mode 100644 index 00000000000..a92bf72cd0e --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/channellib.go @@ -0,0 +1,184 @@ +package lua + +import ( + "reflect" +) + +func checkChannel(L *LState, idx int) reflect.Value { + ch := L.CheckChannel(idx) + return reflect.ValueOf(ch) +} + +func checkGoroutineSafe(L *LState, idx int) LValue { + v := L.CheckAny(2) + if !isGoroutineSafe(v) { + L.ArgError(2, "can not send a function, userdata, thread or table that has a metatable") + } + return v +} + +func OpenChannel(L *LState) int { + var mod LValue + //_, ok := L.G.builtinMts[int(LTChannel)] + // if !ok { + mod = L.RegisterModule(ChannelLibName, channelFuncs) + mt := L.SetFuncs(L.NewTable(), channelMethods) + mt.RawSetString("__index", mt) + L.G.builtinMts[int(LTChannel)] = mt + // } + L.Push(mod) + return 1 +} + +var channelFuncs = map[string]LGFunction{ + "make": channelMake, + "select": channelSelect, +} + +func channelMake(L *LState) int { + buffer := L.OptInt(1, 0) + L.Push(LChannel(make(chan LValue, buffer))) + return 1 +} + +func channelSelect(L *LState) int { + //TODO check case table size + cases := make([]reflect.SelectCase, L.GetTop()) + top := L.GetTop() + for i := 0; i < top; i++ { + cas := reflect.SelectCase{ + Dir: reflect.SelectSend, + Chan: reflect.ValueOf(nil), + Send: reflect.ValueOf(nil), + } + tbl := L.CheckTable(i + 1) + dir, ok1 := tbl.RawGetInt(1).(LString) + if !ok1 { + L.ArgError(i+1, "invalid select case") + } + switch string(dir) { + case "<-|": + ch, ok := tbl.RawGetInt(2).(LChannel) + if !ok { + L.ArgError(i+1, "invalid select case") + } + cas.Chan = reflect.ValueOf((chan LValue)(ch)) + v := tbl.RawGetInt(3) + if !isGoroutineSafe(v) { + L.ArgError(i+1, "can not send a function, userdata, thread or table that has a metatable") + } + cas.Send = reflect.ValueOf(v) + case "|<-": + ch, ok := tbl.RawGetInt(2).(LChannel) + if !ok { + L.ArgError(i+1, "invalid select case") + } + cas.Chan = reflect.ValueOf((chan LValue)(ch)) + cas.Dir = reflect.SelectRecv + case "default": + cas.Dir = reflect.SelectDefault + default: + L.ArgError(i+1, "invalid channel direction:"+string(dir)) + } + cases[i] = cas + } + + if L.ctx != nil { + cases = append(cases, reflect.SelectCase{ + Dir: reflect.SelectRecv, + Chan: reflect.ValueOf(L.ctx.Done()), + Send: reflect.ValueOf(nil), + }) + } + + pos, recv, rok := reflect.Select(cases) + + if L.ctx != nil && pos == L.GetTop() { + return 0 + } + + lv := LNil + if recv.Kind() != 0 { + lv, _ = recv.Interface().(LValue) + if lv == nil { + lv = LNil + } + } + tbl := L.Get(pos + 1).(*LTable) + last := tbl.RawGetInt(tbl.Len()) + if last.Type() == LTFunction { + L.Push(last) + switch cases[pos].Dir { + case reflect.SelectRecv: + if rok { + L.Push(LTrue) + } else { + L.Push(LFalse) + } + L.Push(lv) + L.Call(2, 0) + case reflect.SelectSend: + L.Push(tbl.RawGetInt(3)) + L.Call(1, 0) + case reflect.SelectDefault: + L.Call(0, 0) + } + } + L.Push(LNumber(pos + 1)) + L.Push(lv) + if rok { + L.Push(LTrue) + } else { + L.Push(LFalse) + } + return 3 +} + +var channelMethods = map[string]LGFunction{ + "receive": channelReceive, + "send": channelSend, + "close": channelClose, +} + +func channelReceive(L *LState) int { + rch := checkChannel(L, 1) + var v reflect.Value + var ok bool + if L.ctx != nil { + cases := []reflect.SelectCase{{ + Dir: reflect.SelectRecv, + Chan: reflect.ValueOf(L.ctx.Done()), + Send: reflect.ValueOf(nil), + }, { + Dir: reflect.SelectRecv, + Chan: rch, + Send: reflect.ValueOf(nil), + }} + _, v, ok = reflect.Select(cases) + } else { + v, ok = rch.Recv() + } + if ok { + L.Push(LTrue) + L.Push(v.Interface().(LValue)) + } else { + L.Push(LFalse) + L.Push(LNil) + } + return 2 +} + +func channelSend(L *LState) int { + rch := checkChannel(L, 1) + v := checkGoroutineSafe(L, 2) + rch.Send(reflect.ValueOf(v)) + return 0 +} + +func channelClose(L *LState) int { + rch := checkChannel(L, 1) + rch.Close() + return 0 +} + +// diff --git a/vendor/github.com/yuin/gopher-lua/compile.go b/vendor/github.com/yuin/gopher-lua/compile.go new file mode 100644 index 00000000000..d3c665ae57c --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/compile.go @@ -0,0 +1,1672 @@ +package lua + +import ( + "fmt" + "github.com/yuin/gopher-lua/ast" + "math" + "reflect" +) + +/* internal constants & structs {{{ */ + +const maxRegisters = 200 + +type expContextType int + +const ( + ecGlobal expContextType = iota + ecUpvalue + ecLocal + ecTable + ecVararg + ecMethod + ecNone +) + +const regNotDefined = opMaxArgsA + 1 +const labelNoJump = 0 + +type expcontext struct { + ctype expContextType + reg int + // varargopt >= 0: wants varargopt+1 results, i.e a = func() + // varargopt = -1: ignore results i.e func() + // varargopt = -2: receive all results i.e a = {func()} + varargopt int +} + +type assigncontext struct { + ec *expcontext + keyrk int + valuerk int + keyks bool + needmove bool +} + +type lblabels struct { + t int + f int + e int + b bool +} + +type constLValueExpr struct { + ast.ExprBase + + Value LValue +} + +// }}} + +/* utilities {{{ */ +var _ecnone0 = &expcontext{ecNone, regNotDefined, 0} +var _ecnonem1 = &expcontext{ecNone, regNotDefined, -1} +var _ecnonem2 = &expcontext{ecNone, regNotDefined, -2} +var ecfuncdef = &expcontext{ecMethod, regNotDefined, 0} + +func ecupdate(ec *expcontext, ctype expContextType, reg, varargopt int) { + if ec == _ecnone0 || ec == _ecnonem1 || ec == _ecnonem2 { + panic("can not update ec cache") + } + ec.ctype = ctype + ec.reg = reg + ec.varargopt = varargopt +} + +func ecnone(varargopt int) *expcontext { + switch varargopt { + case 0: + return _ecnone0 + case -1: + return _ecnonem1 + case -2: + return _ecnonem2 + } + return &expcontext{ecNone, regNotDefined, varargopt} +} + +func shouldmove(ec *expcontext, reg int) bool { + return ec.ctype == ecLocal && ec.reg != regNotDefined && ec.reg != reg +} + +func sline(pos ast.PositionHolder) int { + return pos.Line() +} + +func eline(pos ast.PositionHolder) int { + return pos.LastLine() +} + +func savereg(ec *expcontext, reg int) int { + if ec.ctype != ecLocal || ec.reg == regNotDefined { + return reg + } + return ec.reg +} + +func raiseCompileError(context *funcContext, line int, format string, args ...interface{}) { + msg := fmt.Sprintf(format, args...) + panic(&CompileError{context: context, Line: line, Message: msg}) +} + +func isVarArgReturnExpr(expr ast.Expr) bool { + switch ex := expr.(type) { + case *ast.FuncCallExpr: + return !ex.AdjustRet + case *ast.Comma3Expr: + return true + } + return false +} + +func lnumberValue(expr ast.Expr) (LNumber, bool) { + if ex, ok := expr.(*ast.NumberExpr); ok { + lv, err := parseNumber(ex.Value) + if err != nil { + lv = LNumber(math.NaN()) + } + return lv, true + } else if ex, ok := expr.(*constLValueExpr); ok { + return ex.Value.(LNumber), true + } + return 0, false +} + +/* utilities }}} */ + +type CompileError struct { // {{{ + context *funcContext + Line int + Message string +} + +func (e *CompileError) Error() string { + return fmt.Sprintf("compile error near line(%v) %v: %v", e.Line, e.context.Proto.SourceName, e.Message) +} // }}} + +type codeStore struct { // {{{ + codes []uint32 + lines []int + pc int +} + +func (cd *codeStore) Add(inst uint32, line int) { + if l := len(cd.codes); l <= 0 || cd.pc == l { + cd.codes = append(cd.codes, inst) + cd.lines = append(cd.lines, line) + } else { + cd.codes[cd.pc] = inst + cd.lines[cd.pc] = line + } + cd.pc++ +} + +func (cd *codeStore) AddABC(op int, a int, b int, c int, line int) { + cd.Add(opCreateABC(op, a, b, c), line) +} + +func (cd *codeStore) AddABx(op int, a int, bx int, line int) { + cd.Add(opCreateABx(op, a, bx), line) +} + +func (cd *codeStore) AddASbx(op int, a int, sbx int, line int) { + cd.Add(opCreateASbx(op, a, sbx), line) +} + +func (cd *codeStore) PropagateKMV(top int, save *int, reg *int, inc int) { + lastinst := cd.Last() + if opGetArgA(lastinst) >= top { + switch opGetOpCode(lastinst) { + case OP_LOADK: + cindex := opGetArgBx(lastinst) + if cindex <= opMaxIndexRk { + cd.Pop() + *save = opRkAsk(cindex) + return + } + case OP_MOVE: + cd.Pop() + *save = opGetArgB(lastinst) + return + } + } + *save = *reg + *reg = *reg + inc +} + +func (cd *codeStore) PropagateMV(top int, save *int, reg *int, inc int) { + lastinst := cd.Last() + if opGetArgA(lastinst) >= top { + switch opGetOpCode(lastinst) { + case OP_MOVE: + cd.Pop() + *save = opGetArgB(lastinst) + return + } + } + *save = *reg + *reg = *reg + inc +} + +func (cd *codeStore) AddLoadNil(a, b, line int) { + last := cd.Last() + if opGetOpCode(last) == OP_LOADNIL && (opGetArgA(last)+opGetArgB(last)) == a { + cd.SetB(cd.LastPC(), b) + } else { + cd.AddABC(OP_LOADNIL, a, b, 0, line) + } +} + +func (cd *codeStore) SetOpCode(pc int, v int) { + opSetOpCode(&cd.codes[pc], v) +} + +func (cd *codeStore) SetA(pc int, v int) { + opSetArgA(&cd.codes[pc], v) +} + +func (cd *codeStore) SetB(pc int, v int) { + opSetArgB(&cd.codes[pc], v) +} + +func (cd *codeStore) SetC(pc int, v int) { + opSetArgC(&cd.codes[pc], v) +} + +func (cd *codeStore) SetBx(pc int, v int) { + opSetArgBx(&cd.codes[pc], v) +} + +func (cd *codeStore) SetSbx(pc int, v int) { + opSetArgSbx(&cd.codes[pc], v) +} + +func (cd *codeStore) At(pc int) uint32 { + return cd.codes[pc] +} + +func (cd *codeStore) List() []uint32 { + return cd.codes[:cd.pc] +} + +func (cd *codeStore) PosList() []int { + return cd.lines[:cd.pc] +} + +func (cd *codeStore) LastPC() int { + return cd.pc - 1 +} + +func (cd *codeStore) Last() uint32 { + if cd.pc == 0 { + return opInvalidInstruction + } + return cd.codes[cd.pc-1] +} + +func (cd *codeStore) Pop() { + cd.pc-- +} /* }}} Code */ + +/* {{{ VarNamePool */ + +type varNamePoolValue struct { + Index int + Name string +} + +type varNamePool struct { + names []string + offset int +} + +func newVarNamePool(offset int) *varNamePool { + return &varNamePool{make([]string, 0, 16), offset} +} + +func (vp *varNamePool) Names() []string { + return vp.names +} + +func (vp *varNamePool) List() []varNamePoolValue { + result := make([]varNamePoolValue, len(vp.names), len(vp.names)) + for i, name := range vp.names { + result[i].Index = i + vp.offset + result[i].Name = name + } + return result +} + +func (vp *varNamePool) LastIndex() int { + return vp.offset + len(vp.names) +} + +func (vp *varNamePool) Find(name string) int { + for i := len(vp.names) - 1; i >= 0; i-- { + if vp.names[i] == name { + return i + vp.offset + } + } + return -1 +} + +func (vp *varNamePool) RegisterUnique(name string) int { + index := vp.Find(name) + if index < 0 { + return vp.Register(name) + } + return index +} + +func (vp *varNamePool) Register(name string) int { + vp.names = append(vp.names, name) + return len(vp.names) - 1 + vp.offset +} + +/* }}} VarNamePool */ + +/* FuncContext {{{ */ + +type codeBlock struct { + LocalVars *varNamePool + BreakLabel int + Parent *codeBlock + RefUpvalue bool + LineStart int + LastLine int +} + +func newCodeBlock(localvars *varNamePool, blabel int, parent *codeBlock, pos ast.PositionHolder) *codeBlock { + bl := &codeBlock{localvars, blabel, parent, false, 0, 0} + if pos != nil { + bl.LineStart = pos.Line() + bl.LastLine = pos.LastLine() + } + return bl +} + +type funcContext struct { + Proto *FunctionProto + Code *codeStore + Parent *funcContext + Upvalues *varNamePool + Block *codeBlock + Blocks []*codeBlock + regTop int + labelId int + labelPc map[int]int +} + +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{}, + } + fc.Blocks = []*codeBlock{fc.Block} + return fc +} + +func (fc *funcContext) NewLabel() int { + ret := fc.labelId + fc.labelId++ + return ret +} + +func (fc *funcContext) SetLabelPc(label int, pc int) { + fc.labelPc[label] = pc +} + +func (fc *funcContext) GetLabelPc(label int) int { + return fc.labelPc[label] +} + +func (fc *funcContext) ConstIndex(value LValue) int { + ctype := value.Type() + for i, lv := range fc.Proto.Constants { + if lv.Type() == ctype && lv == value { + return i + } + } + fc.Proto.Constants = append(fc.Proto.Constants, value) + v := len(fc.Proto.Constants) - 1 + if v > opMaxArgBx { + raiseCompileError(fc, fc.Proto.LineDefined, "too many constants") + } + return v +} + +func (fc *funcContext) RegisterLocalVar(name string) int { + ret := fc.Block.LocalVars.Register(name) + fc.Proto.DbgLocals = append(fc.Proto.DbgLocals, &DbgLocalInfo{Name: name, StartPc: fc.Code.LastPC() + 1}) + fc.SetRegTop(fc.RegTop() + 1) + return ret +} + +func (fc *funcContext) FindLocalVarAndBlock(name string) (int, *codeBlock) { + for block := fc.Block; block != nil; block = block.Parent { + if index := block.LocalVars.Find(name); index > -1 { + return index, block + } + } + return -1, nil +} + +func (fc *funcContext) FindLocalVar(name string) int { + idx, _ := fc.FindLocalVarAndBlock(name) + return idx +} + +func (fc *funcContext) LocalVars() []varNamePoolValue { + result := make([]varNamePoolValue, 0, 32) + for _, block := range fc.Blocks { + result = append(result, block.LocalVars.List()...) + } + return result +} + +func (fc *funcContext) EnterBlock(blabel int, pos ast.PositionHolder) { + fc.Block = newCodeBlock(newVarNamePool(fc.RegTop()), blabel, fc.Block, pos) + fc.Blocks = append(fc.Blocks, fc.Block) +} + +func (fc *funcContext) CloseUpvalues() int { + n := -1 + if fc.Block.RefUpvalue { + n = fc.Block.Parent.LocalVars.LastIndex() + fc.Code.AddABC(OP_CLOSE, n, 0, 0, fc.Block.LastLine) + } + return n +} + +func (fc *funcContext) LeaveBlock() int { + closed := fc.CloseUpvalues() + fc.EndScope() + fc.Block = fc.Block.Parent + fc.SetRegTop(fc.Block.LocalVars.LastIndex()) + return closed +} + +func (fc *funcContext) EndScope() { + for _, vr := range fc.Block.LocalVars.List() { + fc.Proto.DbgLocals[vr.Index].EndPc = fc.Code.LastPC() + } +} + +func (fc *funcContext) SetRegTop(top int) { + if top > maxRegisters { + raiseCompileError(fc, fc.Proto.LineDefined, "too many local variables") + } + fc.regTop = top +} + +func (fc *funcContext) RegTop() int { + return fc.regTop +} + +/* FuncContext }}} */ + +func compileChunk(context *funcContext, chunk []ast.Stmt) { // {{{ + for _, stmt := range chunk { + compileStmt(context, stmt) + } +} // }}} + +func compileBlock(context *funcContext, chunk []ast.Stmt) { // {{{ + if len(chunk) == 0 { + return + } + ph := &ast.Node{} + ph.SetLine(sline(chunk[0])) + ph.SetLastLine(eline(chunk[len(chunk)-1])) + context.EnterBlock(labelNoJump, ph) + for _, stmt := range chunk { + compileStmt(context, stmt) + } + context.LeaveBlock() +} // }}} + +func compileStmt(context *funcContext, stmt ast.Stmt) { // {{{ + switch st := stmt.(type) { + case *ast.AssignStmt: + compileAssignStmt(context, st) + case *ast.LocalAssignStmt: + compileLocalAssignStmt(context, st) + case *ast.FuncCallStmt: + compileFuncCallExpr(context, context.RegTop(), st.Expr.(*ast.FuncCallExpr), ecnone(-1)) + case *ast.DoBlockStmt: + context.EnterBlock(labelNoJump, st) + compileChunk(context, st.Stmts) + context.LeaveBlock() + case *ast.WhileStmt: + compileWhileStmt(context, st) + case *ast.RepeatStmt: + compileRepeatStmt(context, st) + case *ast.FuncDefStmt: + compileFuncDefStmt(context, st) + case *ast.ReturnStmt: + compileReturnStmt(context, st) + case *ast.IfStmt: + compileIfStmt(context, st) + case *ast.BreakStmt: + compileBreakStmt(context, st) + case *ast.NumberForStmt: + compileNumberForStmt(context, st) + case *ast.GenericForStmt: + compileGenericForStmt(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 + switch st := lhs.(type) { + case *ast.IdentExpr: + identtype := getIdentRefType(context, context, st) + ec := &expcontext{identtype, regNotDefined, 0} + switch identtype { + case ecGlobal: + context.ConstIndex(LString(st.Value)) + case ecUpvalue: + context.Upvalues.RegisterUnique(st.Value) + case ecLocal: + if islast { + ec.reg = context.FindLocalVar(st.Value) + } + } + acs = append(acs, &assigncontext{ec, 0, 0, false, false}) + case *ast.AttrGetExpr: + ac := &assigncontext{&expcontext{ecTable, regNotDefined, 0}, 0, 0, false, false} + compileExprWithKMVPropagation(context, st.Object, ®, &ac.ec.reg) + ac.keyrk = reg + reg += compileExpr(context, reg, st.Key, ecnone(0)) + if _, ok := st.Key.(*ast.StringExpr); ok { + ac.keyks = true + } + acs = append(acs, ac) + + default: + panic("invalid left expression.") + } + } + return reg, acs +} // }}} + +func compileAssignStmtRight(context *funcContext, stmt *ast.AssignStmt, reg int, acs []*assigncontext) (int, []*assigncontext) { // {{{ + lennames := len(stmt.Lhs) + lenexprs := len(stmt.Rhs) + namesassigned := 0 + + for namesassigned < lennames { + ac := acs[namesassigned] + ec := ac.ec + var expr ast.Expr = nil + if namesassigned >= lenexprs { + expr = &ast.NilExpr{} + expr.SetLine(sline(stmt.Lhs[namesassigned])) + expr.SetLastLine(eline(stmt.Lhs[namesassigned])) + } else if isVarArgReturnExpr(stmt.Rhs[namesassigned]) && (lenexprs-namesassigned-1) <= 0 { + varargopt := lennames - namesassigned - 1 + regstart := reg + reginc := compileExpr(context, reg, stmt.Rhs[namesassigned], ecnone(varargopt)) + reg += reginc + for i := namesassigned; i < namesassigned+int(reginc); i++ { + acs[i].needmove = true + if acs[i].ec.ctype == ecTable { + acs[i].valuerk = regstart + (i - namesassigned) + } + } + namesassigned = lennames + continue + } + + if expr == nil { + expr = stmt.Rhs[namesassigned] + } + idx := reg + reginc := compileExpr(context, reg, expr, ec) + if ec.ctype == ecTable { + if _, ok := expr.(*ast.LogicalOpExpr); !ok { + context.Code.PropagateKMV(context.RegTop(), &ac.valuerk, ®, reginc) + } else { + ac.valuerk = idx + reg += reginc + } + } else { + ac.needmove = reginc != 0 + reg += reginc + } + namesassigned += 1 + } + + rightreg := reg - 1 + + // extra right exprs + for i := namesassigned; i < lenexprs; i++ { + varargopt := -1 + if i != lenexprs-1 { + varargopt = 0 + } + reg += compileExpr(context, reg, stmt.Rhs[i], ecnone(varargopt)) + } + return rightreg, acs +} // }}} + +func compileAssignStmt(context *funcContext, stmt *ast.AssignStmt) { // {{{ + code := context.Code + lennames := len(stmt.Lhs) + reg, acs := compileAssignStmtLeft(context, stmt) + reg, acs = compileAssignStmtRight(context, stmt, reg, acs) + + for i := lennames - 1; i >= 0; i-- { + ex := stmt.Lhs[i] + switch acs[i].ec.ctype { + case ecLocal: + if acs[i].needmove { + code.AddABC(OP_MOVE, context.FindLocalVar(ex.(*ast.IdentExpr).Value), reg, 0, sline(ex)) + reg -= 1 + } + case ecGlobal: + code.AddABx(OP_SETGLOBAL, reg, context.ConstIndex(LString(ex.(*ast.IdentExpr).Value)), sline(ex)) + reg -= 1 + case ecUpvalue: + code.AddABC(OP_SETUPVAL, reg, context.Upvalues.RegisterUnique(ex.(*ast.IdentExpr).Value), 0, sline(ex)) + reg -= 1 + case ecTable: + opcode := OP_SETTABLE + if acs[i].keyks { + opcode = OP_SETTABLEKS + } + code.AddABC(opcode, acs[i].ec.reg, acs[i].keyrk, acs[i].valuerk, sline(ex)) + if !opIsK(acs[i].valuerk) { + reg -= 1 + } + } + } +} // }}} + +func compileRegAssignment(context *funcContext, names []string, exprs []ast.Expr, reg int, nvars int, line int) { // {{{ + lennames := len(names) + lenexprs := len(exprs) + namesassigned := 0 + ec := &expcontext{} + + for namesassigned < lennames && namesassigned < lenexprs { + if isVarArgReturnExpr(exprs[namesassigned]) && (lenexprs-namesassigned-1) <= 0 { + + varargopt := nvars - namesassigned + ecupdate(ec, ecVararg, reg, varargopt-1) + compileExpr(context, reg, exprs[namesassigned], ec) + reg += varargopt + namesassigned = lennames + } else { + ecupdate(ec, ecLocal, reg, 0) + compileExpr(context, reg, exprs[namesassigned], ec) + reg += 1 + namesassigned += 1 + } + } + + // extra left names + if lennames > namesassigned { + restleft := lennames - namesassigned - 1 + context.Code.AddLoadNil(reg, reg+restleft, line) + reg += restleft + } + + // extra right exprs + for i := namesassigned; i < lenexprs; i++ { + varargopt := -1 + if i != lenexprs-1 { + varargopt = 0 + } + ecupdate(ec, ecNone, reg, varargopt) + reg += compileExpr(context, reg, exprs[i], ec) + } +} // }}} + +func compileLocalAssignStmt(context *funcContext, stmt *ast.LocalAssignStmt) { // {{{ + reg := context.RegTop() + if len(stmt.Names) == 1 && len(stmt.Exprs) == 1 { + if _, ok := stmt.Exprs[0].(*ast.FunctionExpr); ok { + context.RegisterLocalVar(stmt.Names[0]) + compileRegAssignment(context, stmt.Names, stmt.Exprs, reg, len(stmt.Names), sline(stmt)) + return + } + } + + compileRegAssignment(context, stmt.Names, stmt.Exprs, reg, len(stmt.Names), sline(stmt)) + for _, name := range stmt.Names { + context.RegisterLocalVar(name) + } +} // }}} + +func compileReturnStmt(context *funcContext, stmt *ast.ReturnStmt) { // {{{ + lenexprs := len(stmt.Exprs) + code := context.Code + reg := context.RegTop() + a := reg + lastisvaarg := false + + if lenexprs == 1 { + switch ex := stmt.Exprs[0].(type) { + case *ast.IdentExpr: + if idx := context.FindLocalVar(ex.Value); idx > -1 { + code.AddABC(OP_RETURN, idx, 2, 0, sline(stmt)) + return + } + case *ast.FuncCallExpr: + reg += compileExpr(context, reg, ex, ecnone(-2)) + code.SetOpCode(code.LastPC(), OP_TAILCALL) + code.AddABC(OP_RETURN, a, 0, 0, sline(stmt)) + return + } + } + + for i, expr := range stmt.Exprs { + if i == lenexprs-1 && isVarArgReturnExpr(expr) { + compileExpr(context, reg, expr, ecnone(-2)) + lastisvaarg = true + } else { + reg += compileExpr(context, reg, expr, ecnone(0)) + } + } + count := reg - a + 1 + if lastisvaarg { + count = 0 + } + context.Code.AddABC(OP_RETURN, a, count, 0, sline(stmt)) +} // }}} + +func compileIfStmt(context *funcContext, stmt *ast.IfStmt) { // {{{ + thenlabel := context.NewLabel() + elselabel := context.NewLabel() + endlabel := context.NewLabel() + + compileBranchCondition(context, context.RegTop(), stmt.Condition, thenlabel, elselabel, false) + context.SetLabelPc(thenlabel, context.Code.LastPC()) + compileBlock(context, stmt.Then) + if len(stmt.Else) > 0 { + context.Code.AddASbx(OP_JMP, 0, endlabel, sline(stmt)) + } + context.SetLabelPc(elselabel, context.Code.LastPC()) + if len(stmt.Else) > 0 { + compileBlock(context, stmt.Else) + context.SetLabelPc(endlabel, context.Code.LastPC()) + } + +} // }}} + +func compileBranchCondition(context *funcContext, reg int, expr ast.Expr, thenlabel, elselabel int, hasnextcond bool) { // {{{ + // TODO folding constants? + code := context.Code + flip := 0 + jumplabel := elselabel + if hasnextcond { + flip = 1 + jumplabel = thenlabel + } + + switch ex := expr.(type) { + case *ast.FalseExpr, *ast.NilExpr: + if !hasnextcond { + code.AddASbx(OP_JMP, 0, elselabel, sline(expr)) + return + } + case *ast.TrueExpr, *ast.NumberExpr, *ast.StringExpr: + if !hasnextcond { + return + } + case *ast.UnaryNotOpExpr: + compileBranchCondition(context, reg, ex.Expr, elselabel, thenlabel, !hasnextcond) + return + case *ast.LogicalOpExpr: + switch ex.Operator { + case "and": + nextcondlabel := context.NewLabel() + compileBranchCondition(context, reg, ex.Lhs, nextcondlabel, elselabel, false) + context.SetLabelPc(nextcondlabel, context.Code.LastPC()) + compileBranchCondition(context, reg, ex.Rhs, thenlabel, elselabel, hasnextcond) + case "or": + nextcondlabel := context.NewLabel() + compileBranchCondition(context, reg, ex.Lhs, thenlabel, nextcondlabel, true) + context.SetLabelPc(nextcondlabel, context.Code.LastPC()) + compileBranchCondition(context, reg, ex.Rhs, thenlabel, elselabel, hasnextcond) + } + return + case *ast.RelationalOpExpr: + compileRelationalOpExprAux(context, reg, ex, flip, jumplabel) + return + } + + a := reg + compileExprWithMVPropagation(context, expr, ®, &a) + code.AddABC(OP_TEST, a, 0, 0^flip, sline(expr)) + code.AddASbx(OP_JMP, 0, jumplabel, sline(expr)) +} // }}} + +func compileWhileStmt(context *funcContext, stmt *ast.WhileStmt) { // {{{ + thenlabel := context.NewLabel() + elselabel := context.NewLabel() + condlabel := context.NewLabel() + + context.SetLabelPc(condlabel, context.Code.LastPC()) + compileBranchCondition(context, context.RegTop(), stmt.Condition, thenlabel, elselabel, false) + context.SetLabelPc(thenlabel, context.Code.LastPC()) + context.EnterBlock(elselabel, stmt) + compileChunk(context, stmt.Stmts) + context.CloseUpvalues() + context.Code.AddASbx(OP_JMP, 0, condlabel, eline(stmt)) + context.LeaveBlock() + context.SetLabelPc(elselabel, context.Code.LastPC()) +} // }}} + +func compileRepeatStmt(context *funcContext, stmt *ast.RepeatStmt) { // {{{ + initlabel := context.NewLabel() + thenlabel := context.NewLabel() + elselabel := context.NewLabel() + + context.SetLabelPc(initlabel, context.Code.LastPC()) + context.SetLabelPc(elselabel, context.Code.LastPC()) + context.EnterBlock(thenlabel, stmt) + compileChunk(context, stmt.Stmts) + compileBranchCondition(context, context.RegTop(), stmt.Condition, thenlabel, elselabel, false) + + context.SetLabelPc(thenlabel, context.Code.LastPC()) + n := context.LeaveBlock() + + if n > -1 { + label := context.NewLabel() + context.Code.AddASbx(OP_JMP, 0, label, eline(stmt)) + context.SetLabelPc(elselabel, context.Code.LastPC()) + context.Code.AddABC(OP_CLOSE, n, 0, 0, eline(stmt)) + context.Code.AddASbx(OP_JMP, 0, initlabel, eline(stmt)) + context.SetLabelPc(label, context.Code.LastPC()) + } + +} // }}} + +func compileBreakStmt(context *funcContext, stmt *ast.BreakStmt) { // {{{ + for block := context.Block; block != nil; block = block.Parent { + if label := block.BreakLabel; label != labelNoJump { + if block.RefUpvalue { + context.Code.AddABC(OP_CLOSE, block.Parent.LocalVars.LastIndex(), 0, 0, sline(stmt)) + } + context.Code.AddASbx(OP_JMP, 0, label, sline(stmt)) + return + } + } + raiseCompileError(context, sline(stmt), "no loop to break") +} // }}} + +func compileFuncDefStmt(context *funcContext, stmt *ast.FuncDefStmt) { // {{{ + if stmt.Name.Func == nil { + reg := context.RegTop() + var treg, kreg int + compileExprWithKMVPropagation(context, stmt.Name.Receiver, ®, &treg) + kreg = loadRk(context, ®, stmt.Func, LString(stmt.Name.Method)) + compileExpr(context, reg, stmt.Func, ecfuncdef) + context.Code.AddABC(OP_SETTABLE, treg, kreg, reg, sline(stmt.Name.Receiver)) + } else { + astmt := &ast.AssignStmt{Lhs: []ast.Expr{stmt.Name.Func}, Rhs: []ast.Expr{stmt.Func}} + astmt.SetLine(sline(stmt.Func)) + astmt.SetLastLine(eline(stmt.Func)) + compileAssignStmt(context, astmt) + } +} // }}} + +func compileNumberForStmt(context *funcContext, stmt *ast.NumberForStmt) { // {{{ + code := context.Code + endlabel := context.NewLabel() + ec := &expcontext{} + + context.EnterBlock(endlabel, stmt) + reg := context.RegTop() + rindex := context.RegisterLocalVar("(for index)") + ecupdate(ec, ecLocal, rindex, 0) + compileExpr(context, reg, stmt.Init, ec) + + reg = context.RegTop() + rlimit := context.RegisterLocalVar("(for limit)") + ecupdate(ec, ecLocal, rlimit, 0) + compileExpr(context, reg, stmt.Limit, ec) + + reg = context.RegTop() + rstep := context.RegisterLocalVar("(for step)") + if stmt.Step == nil { + stmt.Step = &ast.NumberExpr{Value: "1"} + stmt.Step.SetLine(sline(stmt.Init)) + } + ecupdate(ec, ecLocal, rstep, 0) + compileExpr(context, reg, stmt.Step, ec) + + code.AddASbx(OP_FORPREP, rindex, 0, sline(stmt)) + + context.RegisterLocalVar(stmt.Name) + + bodypc := code.LastPC() + compileChunk(context, stmt.Stmts) + + context.LeaveBlock() + + flpc := code.LastPC() + code.AddASbx(OP_FORLOOP, rindex, bodypc-(flpc+1), sline(stmt)) + + context.SetLabelPc(endlabel, code.LastPC()) + code.SetSbx(bodypc, flpc-bodypc) + +} // }}} + +func compileGenericForStmt(context *funcContext, stmt *ast.GenericForStmt) { // {{{ + code := context.Code + endlabel := context.NewLabel() + bodylabel := context.NewLabel() + fllabel := context.NewLabel() + nnames := len(stmt.Names) + + context.EnterBlock(endlabel, stmt) + rgen := context.RegisterLocalVar("(for generator)") + context.RegisterLocalVar("(for state)") + context.RegisterLocalVar("(for control)") + + compileRegAssignment(context, stmt.Names, stmt.Exprs, context.RegTop()-3, 3, sline(stmt)) + + code.AddASbx(OP_JMP, 0, fllabel, sline(stmt)) + + for _, name := range stmt.Names { + context.RegisterLocalVar(name) + } + + context.SetLabelPc(bodylabel, code.LastPC()) + compileChunk(context, stmt.Stmts) + + context.LeaveBlock() + + context.SetLabelPc(fllabel, code.LastPC()) + code.AddABC(OP_TFORLOOP, rgen, 0, nnames, sline(stmt)) + code.AddASbx(OP_JMP, 0, bodylabel, sline(stmt)) + + context.SetLabelPc(endlabel, code.LastPC()) +} // }}} + +func compileExpr(context *funcContext, reg int, expr ast.Expr, ec *expcontext) int { // {{{ + code := context.Code + sreg := savereg(ec, reg) + sused := 1 + if sreg < reg { + sused = 0 + } + + switch ex := expr.(type) { + case *ast.StringExpr: + code.AddABx(OP_LOADK, sreg, context.ConstIndex(LString(ex.Value)), sline(ex)) + return sused + case *ast.NumberExpr: + num, err := parseNumber(ex.Value) + if err != nil { + num = LNumber(math.NaN()) + } + code.AddABx(OP_LOADK, sreg, context.ConstIndex(num), sline(ex)) + return sused + case *constLValueExpr: + code.AddABx(OP_LOADK, sreg, context.ConstIndex(ex.Value), sline(ex)) + return sused + case *ast.NilExpr: + code.AddLoadNil(sreg, sreg, sline(ex)) + return sused + case *ast.FalseExpr: + code.AddABC(OP_LOADBOOL, sreg, 0, 0, sline(ex)) + return sused + case *ast.TrueExpr: + code.AddABC(OP_LOADBOOL, sreg, 1, 0, sline(ex)) + return sused + case *ast.IdentExpr: + switch getIdentRefType(context, context, ex) { + case ecGlobal: + code.AddABx(OP_GETGLOBAL, sreg, context.ConstIndex(LString(ex.Value)), sline(ex)) + case ecUpvalue: + code.AddABC(OP_GETUPVAL, sreg, context.Upvalues.RegisterUnique(ex.Value), 0, sline(ex)) + case ecLocal: + b := context.FindLocalVar(ex.Value) + code.AddABC(OP_MOVE, sreg, b, 0, sline(ex)) + } + return sused + case *ast.Comma3Expr: + if context.Proto.IsVarArg == 0 { + raiseCompileError(context, sline(ex), "cannot use '...' outside a vararg function") + } + context.Proto.IsVarArg &= ^VarArgNeedsArg + code.AddABC(OP_VARARG, sreg, 2+ec.varargopt, 0, sline(ex)) + if context.RegTop() > (sreg+2+ec.varargopt) || ec.varargopt < -1 { + return 0 + } + return (sreg + 1 + ec.varargopt) - reg + case *ast.AttrGetExpr: + a := sreg + b := reg + compileExprWithMVPropagation(context, ex.Object, ®, &b) + c := reg + compileExprWithKMVPropagation(context, ex.Key, ®, &c) + opcode := OP_GETTABLE + if _, ok := ex.Key.(*ast.StringExpr); ok { + opcode = OP_GETTABLEKS + } + code.AddABC(opcode, a, b, c, sline(ex)) + return sused + case *ast.TableExpr: + compileTableExpr(context, reg, ex, ec) + return 1 + case *ast.ArithmeticOpExpr: + compileArithmeticOpExpr(context, reg, ex, ec) + return sused + case *ast.StringConcatOpExpr: + compileStringConcatOpExpr(context, reg, ex, ec) + return sused + case *ast.UnaryMinusOpExpr, *ast.UnaryNotOpExpr, *ast.UnaryLenOpExpr: + compileUnaryOpExpr(context, reg, ex, ec) + return sused + case *ast.RelationalOpExpr: + compileRelationalOpExpr(context, reg, ex, ec) + return sused + case *ast.LogicalOpExpr: + compileLogicalOpExpr(context, reg, ex, ec) + return sused + case *ast.FuncCallExpr: + return compileFuncCallExpr(context, reg, ex, ec) + case *ast.FunctionExpr: + childcontext := newFuncContext(context.Proto.SourceName, context) + compileFunctionExpr(childcontext, ex, ec) + protono := len(context.Proto.FunctionPrototypes) + context.Proto.FunctionPrototypes = append(context.Proto.FunctionPrototypes, childcontext.Proto) + code.AddABx(OP_CLOSURE, sreg, protono, sline(ex)) + for _, upvalue := range childcontext.Upvalues.List() { + localidx, block := context.FindLocalVarAndBlock(upvalue.Name) + if localidx > -1 { + code.AddABC(OP_MOVE, 0, localidx, 0, sline(ex)) + block.RefUpvalue = true + } else { + upvalueidx := context.Upvalues.Find(upvalue.Name) + if upvalueidx < 0 { + upvalueidx = context.Upvalues.RegisterUnique(upvalue.Name) + } + code.AddABC(OP_GETUPVAL, 0, upvalueidx, 0, sline(ex)) + } + } + return sused + default: + panic(fmt.Sprintf("expr %v not implemented.", reflect.TypeOf(ex).Elem().Name())) + } + +} // }}} + +func compileExprWithPropagation(context *funcContext, expr ast.Expr, reg *int, save *int, propergator func(int, *int, *int, int)) { // {{{ + reginc := compileExpr(context, *reg, expr, ecnone(0)) + if _, ok := expr.(*ast.LogicalOpExpr); ok { + *save = *reg + *reg = *reg + reginc + } else { + propergator(context.RegTop(), save, reg, reginc) + } +} // }}} + +func compileExprWithKMVPropagation(context *funcContext, expr ast.Expr, reg *int, save *int) { // {{{ + compileExprWithPropagation(context, expr, reg, save, context.Code.PropagateKMV) +} // }}} + +func compileExprWithMVPropagation(context *funcContext, expr ast.Expr, reg *int, save *int) { // {{{ + compileExprWithPropagation(context, expr, reg, save, context.Code.PropagateMV) +} // }}} + +func constFold(exp ast.Expr) ast.Expr { // {{{ + switch expr := exp.(type) { + case *ast.ArithmeticOpExpr: + lvalue, lisconst := lnumberValue(constFold(expr.Lhs)) + rvalue, risconst := lnumberValue(constFold(expr.Rhs)) + if lisconst && risconst { + switch expr.Operator { + case "+": + return &constLValueExpr{Value: lvalue + rvalue} + case "-": + return &constLValueExpr{Value: lvalue - rvalue} + case "*": + return &constLValueExpr{Value: lvalue * rvalue} + case "/": + return &constLValueExpr{Value: lvalue / rvalue} + case "%": + return &constLValueExpr{Value: luaModulo(lvalue, rvalue)} + case "^": + return &constLValueExpr{Value: LNumber(math.Pow(float64(lvalue), float64(rvalue)))} + default: + panic(fmt.Sprintf("unknown binop: %v", expr.Operator)) + } + } else { + return expr + } + case *ast.UnaryMinusOpExpr: + expr.Expr = constFold(expr.Expr) + if value, ok := lnumberValue(expr.Expr); ok { + return &constLValueExpr{Value: LNumber(-value)} + } + return expr + default: + + return exp + } +} // }}} + +func compileFunctionExpr(context *funcContext, funcexpr *ast.FunctionExpr, ec *expcontext) { // {{{ + context.Proto.LineDefined = sline(funcexpr) + context.Proto.LastLineDefined = eline(funcexpr) + if len(funcexpr.ParList.Names) > maxRegisters { + raiseCompileError(context, context.Proto.LineDefined, "register overflow") + } + context.Proto.NumParameters = uint8(len(funcexpr.ParList.Names)) + if ec.ctype == ecMethod { + context.Proto.NumParameters += 1 + context.RegisterLocalVar("self") + } + for _, name := range funcexpr.ParList.Names { + context.RegisterLocalVar(name) + } + if funcexpr.ParList.HasVargs { + if CompatVarArg { + context.Proto.IsVarArg = VarArgHasArg | VarArgNeedsArg + if context.Parent != nil { + context.RegisterLocalVar("arg") + } + } + context.Proto.IsVarArg |= VarArgIsVarArg + } + + compileChunk(context, funcexpr.Stmts) + + context.Code.AddABC(OP_RETURN, 0, 1, 0, eline(funcexpr)) + context.EndScope() + context.Proto.Code = context.Code.List() + context.Proto.DbgSourcePositions = context.Code.PosList() + context.Proto.DbgUpvalues = context.Upvalues.Names() + context.Proto.NumUpvalues = uint8(len(context.Proto.DbgUpvalues)) + for _, clv := range context.Proto.Constants { + sv := "" + if slv, ok := clv.(LString); ok { + sv = string(slv) + } + context.Proto.stringConstants = append(context.Proto.stringConstants, sv) + } + patchCode(context) +} // }}} + +func compileTableExpr(context *funcContext, reg int, ex *ast.TableExpr, ec *expcontext) { // {{{ + code := context.Code + /* + tablereg := savereg(ec, reg) + if tablereg == reg { + reg += 1 + } + */ + tablereg := reg + reg++ + code.AddABC(OP_NEWTABLE, tablereg, 0, 0, sline(ex)) + tablepc := code.LastPC() + regbase := reg + + arraycount := 0 + lastvararg := false + for i, field := range ex.Fields { + islast := i == len(ex.Fields)-1 + if field.Key == nil { + if islast && isVarArgReturnExpr(field.Value) { + reg += compileExpr(context, reg, field.Value, ecnone(-2)) + lastvararg = true + } else { + reg += compileExpr(context, reg, field.Value, ecnone(0)) + arraycount += 1 + } + } else { + regorg := reg + b := reg + compileExprWithKMVPropagation(context, field.Key, ®, &b) + c := reg + compileExprWithKMVPropagation(context, field.Value, ®, &c) + opcode := OP_SETTABLE + if _, ok := field.Key.(*ast.StringExpr); ok { + opcode = OP_SETTABLEKS + } + code.AddABC(opcode, tablereg, b, c, sline(ex)) + reg = regorg + } + flush := arraycount % FieldsPerFlush + if (arraycount != 0 && (flush == 0 || islast)) || lastvararg { + reg = regbase + num := flush + if num == 0 { + num = FieldsPerFlush + } + c := (arraycount-1)/FieldsPerFlush + 1 + b := num + if islast && isVarArgReturnExpr(field.Value) { + b = 0 + } + line := field.Value + if field.Key != nil { + line = field.Key + } + if c > 511 { + c = 0 + } + code.AddABC(OP_SETLIST, tablereg, b, c, sline(line)) + if c == 0 { + code.Add(uint32(c), sline(line)) + } + } + } + code.SetB(tablepc, int2Fb(arraycount)) + code.SetC(tablepc, int2Fb(len(ex.Fields)-arraycount)) + if shouldmove(ec, tablereg) { + code.AddABC(OP_MOVE, ec.reg, tablereg, 0, sline(ex)) + } +} // }}} + +func compileArithmeticOpExpr(context *funcContext, reg int, expr *ast.ArithmeticOpExpr, ec *expcontext) { // {{{ + exp := constFold(expr) + if ex, ok := exp.(*constLValueExpr); ok { + exp.SetLine(sline(expr)) + compileExpr(context, reg, ex, ec) + return + } + expr, _ = exp.(*ast.ArithmeticOpExpr) + a := savereg(ec, reg) + b := reg + compileExprWithKMVPropagation(context, expr.Lhs, ®, &b) + c := reg + compileExprWithKMVPropagation(context, expr.Rhs, ®, &c) + + op := 0 + switch expr.Operator { + case "+": + op = OP_ADD + case "-": + op = OP_SUB + case "*": + op = OP_MUL + case "/": + op = OP_DIV + case "%": + op = OP_MOD + case "^": + op = OP_POW + } + context.Code.AddABC(op, a, b, c, sline(expr)) +} // }}} + +func compileStringConcatOpExpr(context *funcContext, reg int, expr *ast.StringConcatOpExpr, ec *expcontext) { // {{{ + code := context.Code + crange := 1 + for current := expr.Rhs; current != nil; { + if ex, ok := current.(*ast.StringConcatOpExpr); ok { + crange += 1 + current = ex.Rhs + } else { + current = nil + } + } + a := savereg(ec, reg) + basereg := reg + reg += compileExpr(context, reg, expr.Lhs, ecnone(0)) + reg += compileExpr(context, reg, expr.Rhs, ecnone(0)) + for pc := code.LastPC(); pc != 0 && opGetOpCode(code.At(pc)) == OP_CONCAT; pc-- { + code.Pop() + } + code.AddABC(OP_CONCAT, a, basereg, basereg+crange, sline(expr)) +} // }}} + +func compileUnaryOpExpr(context *funcContext, reg int, expr ast.Expr, ec *expcontext) { // {{{ + opcode := 0 + code := context.Code + var operandexpr ast.Expr + switch ex := expr.(type) { + case *ast.UnaryMinusOpExpr: + exp := constFold(ex) + if lvexpr, ok := exp.(*constLValueExpr); ok { + exp.SetLine(sline(expr)) + compileExpr(context, reg, lvexpr, ec) + return + } + ex, _ = exp.(*ast.UnaryMinusOpExpr) + operandexpr = ex.Expr + opcode = OP_UNM + case *ast.UnaryNotOpExpr: + switch ex.Expr.(type) { + case *ast.TrueExpr: + code.AddABC(OP_LOADBOOL, savereg(ec, reg), 0, 0, sline(expr)) + return + case *ast.FalseExpr, *ast.NilExpr: + code.AddABC(OP_LOADBOOL, savereg(ec, reg), 1, 0, sline(expr)) + return + default: + opcode = OP_NOT + operandexpr = ex.Expr + } + case *ast.UnaryLenOpExpr: + opcode = OP_LEN + operandexpr = ex.Expr + } + + a := savereg(ec, reg) + b := reg + compileExprWithMVPropagation(context, operandexpr, ®, &b) + code.AddABC(opcode, a, b, 0, sline(expr)) +} // }}} + +func compileRelationalOpExprAux(context *funcContext, reg int, expr *ast.RelationalOpExpr, flip int, label int) { // {{{ + code := context.Code + b := reg + compileExprWithKMVPropagation(context, expr.Lhs, ®, &b) + c := reg + compileExprWithKMVPropagation(context, expr.Rhs, ®, &c) + switch expr.Operator { + case "<": + code.AddABC(OP_LT, 0^flip, b, c, sline(expr)) + case ">": + code.AddABC(OP_LT, 0^flip, c, b, sline(expr)) + case "<=": + code.AddABC(OP_LE, 0^flip, b, c, sline(expr)) + case ">=": + code.AddABC(OP_LE, 0^flip, c, b, sline(expr)) + case "==": + code.AddABC(OP_EQ, 0^flip, b, c, sline(expr)) + case "~=": + code.AddABC(OP_EQ, 1^flip, b, c, sline(expr)) + } + code.AddASbx(OP_JMP, 0, label, sline(expr)) +} // }}} + +func compileRelationalOpExpr(context *funcContext, reg int, expr *ast.RelationalOpExpr, ec *expcontext) { // {{{ + a := savereg(ec, reg) + code := context.Code + jumplabel := context.NewLabel() + compileRelationalOpExprAux(context, reg, expr, 1, jumplabel) + code.AddABC(OP_LOADBOOL, a, 0, 1, sline(expr)) + context.SetLabelPc(jumplabel, code.LastPC()) + code.AddABC(OP_LOADBOOL, a, 1, 0, sline(expr)) +} // }}} + +func compileLogicalOpExpr(context *funcContext, reg int, expr *ast.LogicalOpExpr, ec *expcontext) { // {{{ + a := savereg(ec, reg) + code := context.Code + endlabel := context.NewLabel() + lb := &lblabels{context.NewLabel(), context.NewLabel(), endlabel, false} + nextcondlabel := context.NewLabel() + if expr.Operator == "and" { + compileLogicalOpExprAux(context, reg, expr.Lhs, ec, nextcondlabel, endlabel, false, lb) + context.SetLabelPc(nextcondlabel, code.LastPC()) + compileLogicalOpExprAux(context, reg, expr.Rhs, ec, endlabel, endlabel, false, lb) + } else { + compileLogicalOpExprAux(context, reg, expr.Lhs, ec, endlabel, nextcondlabel, true, lb) + context.SetLabelPc(nextcondlabel, code.LastPC()) + compileLogicalOpExprAux(context, reg, expr.Rhs, ec, endlabel, endlabel, false, lb) + } + + if lb.b { + context.SetLabelPc(lb.f, code.LastPC()) + code.AddABC(OP_LOADBOOL, a, 0, 1, sline(expr)) + context.SetLabelPc(lb.t, code.LastPC()) + code.AddABC(OP_LOADBOOL, a, 1, 0, sline(expr)) + } + + lastinst := code.Last() + if opGetOpCode(lastinst) == OP_JMP && opGetArgSbx(lastinst) == endlabel { + code.Pop() + } + + context.SetLabelPc(endlabel, code.LastPC()) +} // }}} + +func compileLogicalOpExprAux(context *funcContext, reg int, expr ast.Expr, ec *expcontext, thenlabel, elselabel int, hasnextcond bool, lb *lblabels) { // {{{ + // TODO folding constants? + code := context.Code + flip := 0 + jumplabel := elselabel + if hasnextcond { + flip = 1 + jumplabel = thenlabel + } + + switch ex := expr.(type) { + case *ast.FalseExpr: + if elselabel == lb.e { + code.AddASbx(OP_JMP, 0, lb.f, sline(expr)) + lb.b = true + } else { + code.AddASbx(OP_JMP, 0, elselabel, sline(expr)) + } + return + case *ast.NilExpr: + if elselabel == lb.e { + compileExpr(context, reg, expr, ec) + code.AddASbx(OP_JMP, 0, lb.e, sline(expr)) + } else { + code.AddASbx(OP_JMP, 0, elselabel, sline(expr)) + } + return + case *ast.TrueExpr: + if thenlabel == lb.e { + code.AddASbx(OP_JMP, 0, lb.t, sline(expr)) + lb.b = true + } else { + code.AddASbx(OP_JMP, 0, thenlabel, sline(expr)) + } + return + case *ast.NumberExpr, *ast.StringExpr: + if thenlabel == lb.e { + compileExpr(context, reg, expr, ec) + code.AddASbx(OP_JMP, 0, lb.e, sline(expr)) + } else { + code.AddASbx(OP_JMP, 0, thenlabel, sline(expr)) + } + return + case *ast.LogicalOpExpr: + switch ex.Operator { + case "and": + nextcondlabel := context.NewLabel() + compileLogicalOpExprAux(context, reg, ex.Lhs, ec, nextcondlabel, elselabel, false, lb) + context.SetLabelPc(nextcondlabel, context.Code.LastPC()) + compileLogicalOpExprAux(context, reg, ex.Rhs, ec, thenlabel, elselabel, hasnextcond, lb) + case "or": + nextcondlabel := context.NewLabel() + compileLogicalOpExprAux(context, reg, ex.Lhs, ec, thenlabel, nextcondlabel, true, lb) + context.SetLabelPc(nextcondlabel, context.Code.LastPC()) + compileLogicalOpExprAux(context, reg, ex.Rhs, ec, thenlabel, elselabel, hasnextcond, lb) + } + return + case *ast.RelationalOpExpr: + if thenlabel == elselabel { + flip ^= 1 + jumplabel = lb.t + lb.b = true + } else if thenlabel == lb.e { + jumplabel = lb.t + lb.b = true + } else if elselabel == lb.e { + jumplabel = lb.f + lb.b = true + } + compileRelationalOpExprAux(context, reg, ex, flip, jumplabel) + return + } + + a := reg + sreg := savereg(ec, a) + 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 { + context.Code.SetA(context.Code.LastPC(), sreg) + } else { + context.Code.AddABC(OP_MOVE, sreg, a, 0, sline(expr)) + } + } else { + reg += compileExpr(context, reg, expr, ecnone(0)) + if sreg == a { + code.AddABC(OP_TEST, a, 0, 0^flip, sline(expr)) + } else { + code.AddABC(OP_TESTSET, sreg, a, 0^flip, sline(expr)) + } + } + code.AddASbx(OP_JMP, 0, jumplabel, sline(expr)) +} // }}} + +func compileFuncCallExpr(context *funcContext, reg int, expr *ast.FuncCallExpr, ec *expcontext) int { // {{{ + funcreg := reg + if ec.ctype == ecLocal && ec.reg == (int(context.Proto.NumParameters)-1) { + funcreg = ec.reg + reg = ec.reg + } + argc := len(expr.Args) + islastvararg := false + name := "(anonymous)" + + if expr.Func != nil { // hoge.func() + reg += compileExpr(context, reg, expr.Func, ecnone(0)) + name = getExprName(context, expr.Func) + } else { // hoge:method() + b := reg + compileExprWithMVPropagation(context, expr.Receiver, ®, &b) + c := loadRk(context, ®, expr, LString(expr.Method)) + context.Code.AddABC(OP_SELF, funcreg, b, c, sline(expr)) + // increments a register for an implicit "self" + reg = b + 1 + reg2 := funcreg + 2 + if reg2 > reg { + reg = reg2 + } + argc += 1 + name = string(expr.Method) + } + + for i, ar := range expr.Args { + islastvararg = (i == len(expr.Args)-1) && isVarArgReturnExpr(ar) + if islastvararg { + compileExpr(context, reg, ar, ecnone(-2)) + } else { + reg += compileExpr(context, reg, ar, ecnone(0)) + } + } + b := argc + 1 + if islastvararg { + b = 0 + } + context.Code.AddABC(OP_CALL, funcreg, b, ec.varargopt+2, sline(expr)) + context.Proto.DbgCalls = append(context.Proto.DbgCalls, DbgCall{Pc: context.Code.LastPC(), Name: name}) + + if ec.varargopt == 0 && shouldmove(ec, funcreg) { + context.Code.AddABC(OP_MOVE, ec.reg, funcreg, 0, sline(expr)) + return 1 + } + if context.RegTop() > (funcreg+2+ec.varargopt) || ec.varargopt < -1 { + return 0 + } + return ec.varargopt + 1 +} // }}} + +func loadRk(context *funcContext, reg *int, expr ast.Expr, cnst LValue) int { // {{{ + cindex := context.ConstIndex(cnst) + if cindex <= opMaxIndexRk { + return opRkAsk(cindex) + } else { + ret := *reg + *reg++ + context.Code.AddABx(OP_LOADK, ret, cindex, sline(expr)) + return ret + } +} // }}} + +func getIdentRefType(context *funcContext, current *funcContext, expr *ast.IdentExpr) expContextType { // {{{ + if current == nil { + return ecGlobal + } else if current.FindLocalVar(expr.Value) > -1 { + if current == context { + return ecLocal + } + return ecUpvalue + } + return getIdentRefType(context, current.Parent, expr) +} // }}} + +func getExprName(context *funcContext, expr ast.Expr) string { // {{{ + switch ex := expr.(type) { + case *ast.IdentExpr: + return ex.Value + case *ast.AttrGetExpr: + switch kex := ex.Key.(type) { + case *ast.StringExpr: + return kex.Value + } + return "?" + } + return "?" +} // }}} + +func patchCode(context *funcContext) { // {{{ + maxreg := 1 + if np := int(context.Proto.NumParameters); np > 1 { + maxreg = np + } + moven := 0 + code := context.Code.List() + for pc := 0; pc < len(code); pc++ { + inst := code[pc] + curop := opGetOpCode(inst) + switch curop { + case OP_CLOSURE: + pc += int(context.Proto.FunctionPrototypes[opGetArgBx(inst)].NumUpvalues) + moven = 0 + continue + case OP_SETGLOBAL, OP_SETUPVAL, OP_EQ, OP_LT, OP_LE, OP_TEST, + OP_TAILCALL, OP_RETURN, OP_FORPREP, OP_FORLOOP, OP_TFORLOOP, + OP_SETLIST, OP_CLOSE: + /* nothing to do */ + case OP_CALL: + if reg := opGetArgA(inst) + opGetArgC(inst) - 2; reg > maxreg { + maxreg = reg + } + case OP_VARARG: + if reg := opGetArgA(inst) + opGetArgB(inst) - 1; reg > maxreg { + maxreg = reg + } + case OP_SELF: + if reg := opGetArgA(inst) + 1; reg > maxreg { + maxreg = reg + } + case OP_LOADNIL: + if reg := opGetArgB(inst); reg > maxreg { + maxreg = reg + } + case OP_JMP: // jump to jump optimization + distance := 0 + count := 0 // avoiding infinite loops + for jmp := inst; opGetOpCode(jmp) == OP_JMP && count < 5; jmp = context.Code.At(pc + distance + 1) { + d := context.GetLabelPc(opGetArgSbx(jmp)) - pc + if d > opMaxArgSbx { + if distance == 0 { + raiseCompileError(context, context.Proto.LineDefined, "too long to jump.") + } + break + } + distance = d + count++ + } + if distance == 0 { + context.Code.SetOpCode(pc, OP_NOP) + } else { + context.Code.SetSbx(pc, distance) + } + default: + if reg := opGetArgA(inst); reg > maxreg { + maxreg = reg + } + } + + // bulk move optimization(reducing op dipatch costs) + if curop == OP_MOVE { + moven++ + } else { + if moven > 1 { + context.Code.SetOpCode(pc-moven, OP_MOVEN) + context.Code.SetC(pc-moven, intMin(moven-1, opMaxArgsC)) + } + moven = 0 + } + } + maxreg++ + if maxreg > maxRegisters { + raiseCompileError(context, context.Proto.LineDefined, "register overflow(too many local variables)") + } + context.Proto.NumUsedRegisters = uint8(maxreg) +} // }}} + +func Compile(chunk []ast.Stmt, name string) (proto *FunctionProto, err error) { // {{{ + defer func() { + if rcv := recover(); rcv != nil { + if _, ok := rcv.(*CompileError); ok { + err = rcv.(error) + } else { + panic(rcv) + } + } + }() + err = nil + parlist := &ast.ParList{HasVargs: true, Names: []string{}} + funcexpr := &ast.FunctionExpr{ParList: parlist, Stmts: chunk} + context := newFuncContext(name, nil) + compileFunctionExpr(context, funcexpr, ecnone(0)) + proto = context.Proto + return +} // }}} diff --git a/vendor/github.com/yuin/gopher-lua/config.go b/vendor/github.com/yuin/gopher-lua/config.go new file mode 100644 index 00000000000..f58b59393a5 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/config.go @@ -0,0 +1,36 @@ +package lua + +import ( + "os" +) + +var CompatVarArg = true +var FieldsPerFlush = 50 +var RegistrySize = 256 * 20 +var RegistryGrowStep = 32 +var CallStackSize = 256 +var MaxTableGetLoop = 100 +var MaxArrayIndex = 67108864 + +type LNumber float64 + +const LNumberBit = 64 +const LNumberScanFormat = "%f" +const LuaVersion = "Lua 5.1" + +var LuaPath = "LUA_PATH" +var LuaLDir string +var LuaPathDefault string +var LuaOS string + +func init() { + if os.PathSeparator == '/' { // unix-like + LuaOS = "unix" + LuaLDir = "/usr/local/share/lua/5.1" + LuaPathDefault = "./?.lua;" + LuaLDir + "/?.lua;" + LuaLDir + "/?/init.lua" + } else { // windows + LuaOS = "windows" + LuaLDir = "!\\lua" + LuaPathDefault = ".\\?.lua;" + LuaLDir + "\\?.lua;" + LuaLDir + "\\?\\init.lua" + } +} diff --git a/vendor/github.com/yuin/gopher-lua/coroutinelib.go b/vendor/github.com/yuin/gopher-lua/coroutinelib.go new file mode 100644 index 00000000000..d42c41a1dfc --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/coroutinelib.go @@ -0,0 +1,112 @@ +package lua + +func OpenCoroutine(L *LState) int { + // TODO: Tie module name to contents of linit.go? + mod := L.RegisterModule(CoroutineLibName, coFuncs) + L.Push(mod) + return 1 +} + +var coFuncs = map[string]LGFunction{ + "create": coCreate, + "yield": coYield, + "resume": coResume, + "running": coRunning, + "status": coStatus, + "wrap": coWrap, +} + +func coCreate(L *LState) int { + fn := L.CheckFunction(1) + newthread, _ := L.NewThread() + base := 0 + newthread.stack.Push(callFrame{ + Fn: fn, + Pc: 0, + Base: base, + LocalBase: base + 1, + ReturnBase: base, + NArgs: 0, + NRet: MultRet, + Parent: nil, + TailCall: 0, + }) + L.Push(newthread) + return 1 +} + +func coYield(L *LState) int { + return -1 +} + +func coResume(L *LState) int { + th := L.CheckThread(1) + if L.G.CurrentThread == th { + msg := "can not resume a running thread" + if th.wrapped { + L.RaiseError(msg) + return 0 + } + L.Push(LFalse) + L.Push(LString(msg)) + return 2 + } + if th.Dead { + msg := "can not resume a dead thread" + if th.wrapped { + L.RaiseError(msg) + return 0 + } + L.Push(LFalse) + L.Push(LString(msg)) + return 2 + } + th.Parent = L + L.G.CurrentThread = th + if !th.isStarted() { + cf := th.stack.Last() + th.currentFrame = cf + th.SetTop(0) + nargs := L.GetTop() - 1 + L.XMoveTo(th, nargs) + cf.NArgs = nargs + th.initCallFrame(cf) + th.Panic = panicWithoutTraceback + } else { + nargs := L.GetTop() - 1 + L.XMoveTo(th, nargs) + } + top := L.GetTop() + threadRun(th) + return L.GetTop() - top +} + +func coRunning(L *LState) int { + if L.G.MainThread == L { + L.Push(LNil) + return 1 + } + L.Push(L.G.CurrentThread) + return 1 +} + +func coStatus(L *LState) int { + L.Push(LString(L.Status(L.CheckThread(1)))) + return 1 +} + +func wrapaux(L *LState) int { + L.Insert(L.ToThread(UpvalueIndex(1)), 1) + return coResume(L) +} + +func coWrap(L *LState) int { + coCreate(L) + L.CheckThread(L.GetTop()).wrapped = true + v := L.Get(L.GetTop()) + L.Pop(1) + L.Push(L.NewClosure(wrapaux, v)) + return 1 +} + +// diff --git a/vendor/github.com/yuin/gopher-lua/debuglib.go b/vendor/github.com/yuin/gopher-lua/debuglib.go new file mode 100644 index 00000000000..41f883f1d06 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/debuglib.go @@ -0,0 +1,173 @@ +package lua + +import ( + "fmt" + "strings" +) + +func OpenDebug(L *LState) int { + dbgmod := L.RegisterModule(DebugLibName, debugFuncs) + L.Push(dbgmod) + return 1 +} + +var debugFuncs = map[string]LGFunction{ + "getfenv": debugGetFEnv, + "getinfo": debugGetInfo, + "getlocal": debugGetLocal, + "getmetatable": debugGetMetatable, + "getupvalue": debugGetUpvalue, + "setfenv": debugSetFEnv, + "setlocal": debugSetLocal, + "setmetatable": debugSetMetatable, + "setupvalue": debugSetUpvalue, + "traceback": debugTraceback, +} + +func debugGetFEnv(L *LState) int { + L.Push(L.GetFEnv(L.CheckAny(1))) + return 1 +} + +func debugGetInfo(L *LState) int { + L.CheckTypes(1, LTFunction, LTNumber) + arg1 := L.Get(1) + what := L.OptString(2, "Slunf") + var dbg *Debug + var fn LValue + var err error + var ok bool + switch lv := arg1.(type) { + case *LFunction: + dbg = &Debug{} + fn, err = L.GetInfo(">"+what, dbg, lv) + case LNumber: + dbg, ok = L.GetStack(int(lv)) + if !ok { + L.Push(LNil) + return 1 + } + fn, err = L.GetInfo(what, dbg, LNil) + } + + if err != nil { + L.Push(LNil) + return 1 + } + tbl := L.NewTable() + if len(dbg.Name) > 0 { + tbl.RawSetString("name", LString(dbg.Name)) + } else { + tbl.RawSetString("name", LNil) + } + tbl.RawSetString("what", LString(dbg.What)) + tbl.RawSetString("source", LString(dbg.Source)) + tbl.RawSetString("currentline", LNumber(dbg.CurrentLine)) + tbl.RawSetString("nups", LNumber(dbg.NUpvalues)) + tbl.RawSetString("linedefined", LNumber(dbg.LineDefined)) + tbl.RawSetString("lastlinedefined", LNumber(dbg.LastLineDefined)) + tbl.RawSetString("func", fn) + L.Push(tbl) + return 1 +} + +func debugGetLocal(L *LState) int { + level := L.CheckInt(1) + idx := L.CheckInt(2) + dbg, ok := L.GetStack(level) + if !ok { + L.ArgError(1, "level out of range") + } + name, value := L.GetLocal(dbg, idx) + if len(name) > 0 { + L.Push(LString(name)) + L.Push(value) + return 2 + } + L.Push(LNil) + return 1 +} + +func debugGetMetatable(L *LState) int { + L.Push(L.GetMetatable(L.CheckAny(1))) + return 1 +} + +func debugGetUpvalue(L *LState) int { + fn := L.CheckFunction(1) + idx := L.CheckInt(2) + name, value := L.GetUpvalue(fn, idx) + if len(name) > 0 { + L.Push(LString(name)) + L.Push(value) + return 2 + } + L.Push(LNil) + return 1 +} + +func debugSetFEnv(L *LState) int { + L.SetFEnv(L.CheckAny(1), L.CheckAny(2)) + return 0 +} + +func debugSetLocal(L *LState) int { + level := L.CheckInt(1) + idx := L.CheckInt(2) + value := L.CheckAny(3) + dbg, ok := L.GetStack(level) + if !ok { + L.ArgError(1, "level out of range") + } + name := L.SetLocal(dbg, idx, value) + if len(name) > 0 { + L.Push(LString(name)) + } else { + L.Push(LNil) + } + return 1 +} + +func debugSetMetatable(L *LState) int { + L.CheckTypes(2, LTNil, LTTable) + obj := L.Get(1) + mt := L.Get(2) + L.SetMetatable(obj, mt) + L.SetTop(1) + return 1 +} + +func debugSetUpvalue(L *LState) int { + fn := L.CheckFunction(1) + idx := L.CheckInt(2) + value := L.CheckAny(3) + name := L.SetUpvalue(fn, idx, value) + if len(name) > 0 { + L.Push(LString(name)) + } else { + L.Push(LNil) + } + return 1 +} + +func debugTraceback(L *LState) int { + msg := "" + level := L.OptInt(2, 1) + ls := L + if L.GetTop() > 0 { + if s, ok := L.Get(1).assertString(); ok { + msg = s + } + if l, ok := L.Get(1).(*LState); ok { + ls = l + msg = "" + } + } + + traceback := strings.TrimSpace(ls.stackTrace(level)) + if len(msg) > 0 { + traceback = fmt.Sprintf("%s\n%s", msg, traceback) + } + L.Push(LString(traceback)) + return 1 +} diff --git a/vendor/github.com/yuin/gopher-lua/function.go b/vendor/github.com/yuin/gopher-lua/function.go new file mode 100644 index 00000000000..169e5407cac --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/function.go @@ -0,0 +1,193 @@ +package lua + +import ( + "fmt" + "strings" +) + +const ( + VarArgHasArg uint8 = 1 + VarArgIsVarArg uint8 = 2 + VarArgNeedsArg uint8 = 4 +) + +type DbgLocalInfo struct { + Name string + StartPc int + EndPc int +} + +type DbgCall struct { + Name string + Pc int +} + +type FunctionProto struct { + SourceName string + LineDefined int + LastLineDefined int + NumUpvalues uint8 + NumParameters uint8 + IsVarArg uint8 + NumUsedRegisters uint8 + Code []uint32 + Constants []LValue + FunctionPrototypes []*FunctionProto + + DbgSourcePositions []int + DbgLocals []*DbgLocalInfo + DbgCalls []DbgCall + DbgUpvalues []string + + stringConstants []string +} + +/* Upvalue {{{ */ + +type Upvalue struct { + next *Upvalue + reg *registry + index int + value LValue + closed bool +} + +func (uv *Upvalue) Value() LValue { + //if uv.IsClosed() { + if uv.closed || uv.reg == nil { + return uv.value + } + //return uv.reg.Get(uv.index) + return uv.reg.array[uv.index] +} + +func (uv *Upvalue) SetValue(value LValue) { + if uv.IsClosed() { + uv.value = value + } else { + uv.reg.Set(uv.index, value) + } +} + +func (uv *Upvalue) Close() { + value := uv.Value() + uv.closed = true + uv.value = value +} + +func (uv *Upvalue) IsClosed() bool { + return uv.closed || uv.reg == nil +} + +func UpvalueIndex(i int) int { + return GlobalsIndex - i +} + +/* }}} */ + +/* FunctionProto {{{ */ + +func newFunctionProto(name string) *FunctionProto { + return &FunctionProto{ + SourceName: name, + LineDefined: 0, + LastLineDefined: 0, + NumUpvalues: 0, + NumParameters: 0, + IsVarArg: 0, + NumUsedRegisters: 2, + Code: make([]uint32, 0, 128), + Constants: make([]LValue, 0, 32), + FunctionPrototypes: make([]*FunctionProto, 0, 16), + + DbgSourcePositions: make([]int, 0, 128), + DbgLocals: make([]*DbgLocalInfo, 0, 16), + DbgCalls: make([]DbgCall, 0, 128), + DbgUpvalues: make([]string, 0, 16), + + stringConstants: make([]string, 0, 32), + } +} + +func (fp *FunctionProto) String() string { + return fp.str(1, 0) +} + +func (fp *FunctionProto) str(level int, count int) string { + indent := strings.Repeat(" ", level-1) + buf := []string{} + buf = append(buf, fmt.Sprintf("%v; function [%v] definition (level %v)\n", + indent, count, level)) + buf = append(buf, fmt.Sprintf("%v; %v upvalues, %v params, %v stacks\n", + indent, fp.NumUpvalues, fp.NumParameters, fp.NumUsedRegisters)) + for reg, linfo := range fp.DbgLocals { + buf = append(buf, fmt.Sprintf("%v.local %v ; %v\n", indent, linfo.Name, reg)) + } + for reg, upvalue := range fp.DbgUpvalues { + buf = append(buf, fmt.Sprintf("%v.upvalue %v ; %v\n", indent, upvalue, reg)) + } + for reg, conzt := range fp.Constants { + buf = append(buf, fmt.Sprintf("%v.const %v ; %v\n", indent, conzt.String(), reg)) + } + buf = append(buf, "\n") + + protono := 0 + for no, code := range fp.Code { + inst := opGetOpCode(code) + if inst == OP_CLOSURE { + buf = append(buf, "\n") + buf = append(buf, fp.FunctionPrototypes[protono].str(level+1, protono)) + buf = append(buf, "\n") + protono++ + } + buf = append(buf, fmt.Sprintf("%v[%03d] %v (line:%v)\n", + indent, no+1, opToString(code), fp.DbgSourcePositions[no])) + + } + buf = append(buf, fmt.Sprintf("%v; end of function\n", indent)) + return strings.Join(buf, "") +} + +/* }}} */ + +/* LFunction {{{ */ + +func newLFunctionL(proto *FunctionProto, env *LTable, nupvalue int) *LFunction { + return &LFunction{ + IsG: false, + Env: env, + + Proto: proto, + GFunction: nil, + Upvalues: make([]*Upvalue, nupvalue), + } +} + +func newLFunctionG(gfunc LGFunction, env *LTable, nupvalue int) *LFunction { + return &LFunction{ + IsG: true, + Env: env, + + Proto: nil, + GFunction: gfunc, + Upvalues: make([]*Upvalue, nupvalue), + } +} + +func (fn *LFunction) LocalName(regno, pc int) (string, bool) { + if fn.IsG { + return "", false + } + p := fn.Proto + for i := 0; i < len(p.DbgLocals) && p.DbgLocals[i].StartPc < pc; i++ { + if pc < p.DbgLocals[i].EndPc { + regno-- + if regno == 0 { + return p.DbgLocals[i].Name, true + } + } + } + return "", false +} + +/* }}} */ diff --git a/vendor/github.com/yuin/gopher-lua/go.mod b/vendor/github.com/yuin/gopher-lua/go.mod new file mode 100644 index 00000000000..a94de6e2036 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/go.mod @@ -0,0 +1,10 @@ +module github.com/yuin/gopher-lua + +go 1.14 + +require ( + github.com/chzyer/logex v1.1.10 // indirect + github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e + github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 // indirect + golang.org/x/sys v0.0.0-20190204203706-41f3e6584952 // indirect +) diff --git a/vendor/github.com/yuin/gopher-lua/go.sum b/vendor/github.com/yuin/gopher-lua/go.sum new file mode 100644 index 00000000000..ca60bd9c3df --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/go.sum @@ -0,0 +1,8 @@ +github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +golang.org/x/sys v0.0.0-20190204203706-41f3e6584952 h1:FDfvYgoVsA7TTZSbgiqjAbfPbK47CNHdWl3h/PJtii0= +golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= diff --git a/vendor/github.com/yuin/gopher-lua/iolib.go b/vendor/github.com/yuin/gopher-lua/iolib.go new file mode 100644 index 00000000000..4a86f89362d --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/iolib.go @@ -0,0 +1,746 @@ +package lua + +import ( + "bufio" + "errors" + "fmt" + "io" + "io/ioutil" + "os" + "os/exec" + "syscall" +) + +var ioFuncs = map[string]LGFunction{ + "close": ioClose, + "flush": ioFlush, + "lines": ioLines, + "input": ioInput, + "output": ioOutput, + "open": ioOpenFile, + "popen": ioPopen, + "read": ioRead, + "type": ioType, + "tmpfile": ioTmpFile, + "write": ioWrite, +} + +const lFileClass = "FILE*" + +type lFile struct { + fp *os.File + pp *exec.Cmd + writer io.Writer + reader *bufio.Reader + stdout io.ReadCloser + closed bool +} + +type lFileType int + +const ( + lFileFile lFileType = iota + lFileProcess +) + +const fileDefOutIndex = 1 +const fileDefInIndex = 2 +const fileDefaultWriteBuffer = 4096 +const fileDefaultReadBuffer = 4096 + +func checkFile(L *LState) *lFile { + ud := L.CheckUserData(1) + if file, ok := ud.Value.(*lFile); ok { + return file + } + L.ArgError(1, "file expected") + return nil +} + +func errorIfFileIsClosed(L *LState, file *lFile) { + if file.closed { + L.ArgError(1, "file is closed") + } +} + +func newFile(L *LState, file *os.File, path string, flag int, perm os.FileMode, writable, readable bool) (*LUserData, error) { + ud := L.NewUserData() + var err error + if file == nil { + file, err = os.OpenFile(path, flag, perm) + if err != nil { + return nil, err + } + } + lfile := &lFile{fp: file, pp: nil, writer: nil, reader: nil, stdout: nil, closed: false} + ud.Value = lfile + if writable { + lfile.writer = file + } + if readable { + lfile.reader = bufio.NewReaderSize(file, fileDefaultReadBuffer) + } + L.SetMetatable(ud, L.GetTypeMetatable(lFileClass)) + return ud, nil +} + +func newProcess(L *LState, cmd string, writable, readable bool) (*LUserData, error) { + ud := L.NewUserData() + c, args := popenArgs(cmd) + pp := exec.Command(c, args...) + lfile := &lFile{fp: nil, pp: pp, writer: nil, reader: nil, stdout: nil, closed: false} + ud.Value = lfile + + var err error + if writable { + lfile.writer, err = pp.StdinPipe() + } + if readable { + lfile.stdout, err = pp.StdoutPipe() + lfile.reader = bufio.NewReaderSize(lfile.stdout, fileDefaultReadBuffer) + } + if err != nil { + return nil, err + } + err = pp.Start() + if err != nil { + return nil, err + } + + L.SetMetatable(ud, L.GetTypeMetatable(lFileClass)) + return ud, nil +} + +func (file *lFile) Type() lFileType { + if file.fp == nil { + return lFileProcess + } + return lFileFile +} + +func (file *lFile) Name() string { + switch file.Type() { + case lFileFile: + return fmt.Sprintf("file %s", file.fp.Name()) + case lFileProcess: + return fmt.Sprintf("process %s", file.pp.Path) + } + return "" +} + +func (file *lFile) AbandonReadBuffer() error { + if file.Type() == lFileFile && file.reader != nil { + _, err := file.fp.Seek(-int64(file.reader.Buffered()), 1) + if err != nil { + return err + } + file.reader = bufio.NewReaderSize(file.fp, fileDefaultReadBuffer) + } + return nil +} + +func fileDefOut(L *LState) *LUserData { + return L.Get(UpvalueIndex(1)).(*LTable).RawGetInt(fileDefOutIndex).(*LUserData) +} + +func fileDefIn(L *LState) *LUserData { + return L.Get(UpvalueIndex(1)).(*LTable).RawGetInt(fileDefInIndex).(*LUserData) +} + +func fileIsWritable(L *LState, file *lFile) int { + if file.writer == nil { + L.Push(LNil) + L.Push(LString(fmt.Sprintf("%s is opened for only reading.", file.Name()))) + L.Push(LNumber(1)) // C-Lua compatibility: Original Lua pushes errno to the stack + return 3 + } + return 0 +} + +func fileIsReadable(L *LState, file *lFile) int { + if file.reader == nil { + L.Push(LNil) + L.Push(LString(fmt.Sprintf("%s is opened for only writing.", file.Name()))) + L.Push(LNumber(1)) // C-Lua compatibility: Original Lua pushes errno to the stack + return 3 + } + return 0 +} + +var stdFiles = []struct { + name string + file *os.File + writable bool + readable bool +}{ + {"stdout", os.Stdout, true, false}, + {"stdin", os.Stdin, false, true}, + {"stderr", os.Stderr, true, false}, +} + +func OpenIo(L *LState) int { + mod := L.RegisterModule(IoLibName, map[string]LGFunction{}).(*LTable) + mt := L.NewTypeMetatable(lFileClass) + mt.RawSetString("__index", mt) + L.SetFuncs(mt, fileMethods) + mt.RawSetString("lines", L.NewClosure(fileLines, L.NewFunction(fileLinesIter))) + + for _, finfo := range stdFiles { + file, _ := newFile(L, finfo.file, "", 0, os.FileMode(0), finfo.writable, finfo.readable) + mod.RawSetString(finfo.name, file) + } + uv := L.CreateTable(2, 0) + uv.RawSetInt(fileDefOutIndex, mod.RawGetString("stdout")) + uv.RawSetInt(fileDefInIndex, mod.RawGetString("stdin")) + for name, fn := range ioFuncs { + mod.RawSetString(name, L.NewClosure(fn, uv)) + } + mod.RawSetString("lines", L.NewClosure(ioLines, uv, L.NewClosure(ioLinesIter, uv))) + // Modifications are being made in-place rather than returned? + L.Push(mod) + return 1 +} + +var fileMethods = map[string]LGFunction{ + "__tostring": fileToString, + "write": fileWrite, + "close": fileClose, + "flush": fileFlush, + "lines": fileLines, + "read": fileRead, + "seek": fileSeek, + "setvbuf": fileSetVBuf, +} + +func fileToString(L *LState) int { + file := checkFile(L) + if file.Type() == lFileFile { + if file.closed { + L.Push(LString("file (closed)")) + } else { + L.Push(LString("file")) + } + } else { + if file.closed { + L.Push(LString("process (closed)")) + } else { + L.Push(LString("process")) + } + } + return 1 +} + +func fileWriteAux(L *LState, file *lFile, idx int) int { + if n := fileIsWritable(L, file); n != 0 { + return n + } + errorIfFileIsClosed(L, file) + top := L.GetTop() + out := file.writer + var err error + for i := idx; i <= top; i++ { + L.CheckTypes(i, LTNumber, LTString) + s := LVAsString(L.Get(i)) + if _, err = out.Write(unsafeFastStringToReadOnlyBytes(s)); err != nil { + goto errreturn + } + } + + file.AbandonReadBuffer() + L.Push(LTrue) + return 1 +errreturn: + + file.AbandonReadBuffer() + L.Push(LNil) + L.Push(LString(err.Error())) + L.Push(LNumber(1)) // C-Lua compatibility: Original Lua pushes errno to the stack + return 3 +} + +func fileCloseAux(L *LState, file *lFile) int { + file.closed = true + var err error + if file.writer != nil { + if bwriter, ok := file.writer.(*bufio.Writer); ok { + if err = bwriter.Flush(); err != nil { + goto errreturn + } + } + } + file.AbandonReadBuffer() + + switch file.Type() { + case lFileFile: + if err = file.fp.Close(); err != nil { + goto errreturn + } + L.Push(LTrue) + return 1 + case lFileProcess: + if file.stdout != nil { + file.stdout.Close() // ignore errors + } + err = file.pp.Wait() + var exitStatus int // Initialised to zero value = 0 + if err != nil { + if e2, ok := err.(*exec.ExitError); ok { + if s, ok := e2.Sys().(syscall.WaitStatus); ok { + exitStatus = s.ExitStatus() + } else { + err = errors.New("Unimplemented for system where exec.ExitError.Sys() is not syscall.WaitStatus.") + } + } + } else { + exitStatus = 0 + } + L.Push(LNumber(exitStatus)) + return 1 + } + +errreturn: + L.RaiseError(err.Error()) + return 0 +} + +func fileFlushAux(L *LState, file *lFile) int { + if n := fileIsWritable(L, file); n != 0 { + return n + } + errorIfFileIsClosed(L, file) + + if bwriter, ok := file.writer.(*bufio.Writer); ok { + if err := bwriter.Flush(); err != nil { + L.Push(LNil) + L.Push(LString(err.Error())) + return 2 + } + } + L.Push(LTrue) + return 1 +} + +func fileReadAux(L *LState, file *lFile, idx int) int { + if n := fileIsReadable(L, file); n != 0 { + return n + } + errorIfFileIsClosed(L, file) + if L.GetTop() == idx-1 { + L.Push(LString("*l")) + } + var err error + top := L.GetTop() + for i := idx; i <= top; i++ { + switch lv := L.Get(i).(type) { + case LNumber: + size := int64(lv) + if size == 0 { + _, err = file.reader.ReadByte() + if err == io.EOF { + L.Push(LNil) + goto normalreturn + } + file.reader.UnreadByte() + } + var buf []byte + var iseof bool + buf, err, iseof = readBufioSize(file.reader, size) + if iseof { + L.Push(LNil) + goto normalreturn + } + if err != nil { + goto errreturn + } + L.Push(LString(string(buf))) + case LString: + options := L.CheckString(i) + if len(options) > 0 && options[0] != '*' { + L.ArgError(2, "invalid options:"+options) + } + for _, opt := range options[1:] { + switch opt { + case 'n': + var v LNumber + _, err = fmt.Fscanf(file.reader, LNumberScanFormat, &v) + if err == io.EOF { + L.Push(LNil) + goto normalreturn + } + if err != nil { + goto errreturn + } + L.Push(v) + case 'a': + var buf []byte + buf, err = ioutil.ReadAll(file.reader) + if err == io.EOF { + L.Push(emptyLString) + goto normalreturn + } + if err != nil { + goto errreturn + } + L.Push(LString(string(buf))) + case 'l': + var buf []byte + var iseof bool + buf, err, iseof = readBufioLine(file.reader) + if iseof { + L.Push(LNil) + goto normalreturn + } + if err != nil { + goto errreturn + } + L.Push(LString(string(buf))) + default: + L.ArgError(2, "invalid options:"+string(opt)) + } + } + } + } +normalreturn: + return L.GetTop() - top + +errreturn: + L.RaiseError(err.Error()) + //L.Push(LNil) + //L.Push(LString(err.Error())) + return 2 +} + +var fileSeekOptions = []string{"set", "cur", "end"} + +func fileSeek(L *LState) int { + file := checkFile(L) + if file.Type() != lFileFile { + L.Push(LNil) + L.Push(LString("can not seek a process.")) + return 2 + } + + top := L.GetTop() + if top == 1 { + L.Push(LString("cur")) + L.Push(LNumber(0)) + } else if top == 2 { + L.Push(LNumber(0)) + } + + var pos int64 + var err error + + err = file.AbandonReadBuffer() + if err != nil { + goto errreturn + } + + pos, err = file.fp.Seek(L.CheckInt64(3), L.CheckOption(2, fileSeekOptions)) + if err != nil { + goto errreturn + } + + L.Push(LNumber(pos)) + return 1 + +errreturn: + L.Push(LNil) + L.Push(LString(err.Error())) + return 2 +} + +func fileWrite(L *LState) int { + return fileWriteAux(L, checkFile(L), 2) +} + +func fileClose(L *LState) int { + return fileCloseAux(L, checkFile(L)) +} + +func fileFlush(L *LState) int { + return fileFlushAux(L, checkFile(L)) +} + +func fileLinesIter(L *LState) int { + var file *lFile + if ud, ok := L.Get(1).(*LUserData); ok { + file = ud.Value.(*lFile) + } else { + file = L.Get(UpvalueIndex(2)).(*LUserData).Value.(*lFile) + } + buf, _, err := file.reader.ReadLine() + if err != nil { + if err == io.EOF { + L.Push(LNil) + return 1 + } + L.RaiseError(err.Error()) + } + L.Push(LString(string(buf))) + return 1 +} + +func fileLines(L *LState) int { + file := checkFile(L) + ud := L.CheckUserData(1) + if n := fileIsReadable(L, file); n != 0 { + return 0 + } + L.Push(L.NewClosure(fileLinesIter, L.Get(UpvalueIndex(1)), ud)) + return 1 +} + +func fileRead(L *LState) int { + return fileReadAux(L, checkFile(L), 2) +} + +var filebufOptions = []string{"no", "full"} + +func fileSetVBuf(L *LState) int { + var err error + var writer io.Writer + file := checkFile(L) + if n := fileIsWritable(L, file); n != 0 { + return n + } + switch filebufOptions[L.CheckOption(2, filebufOptions)] { + case "no": + switch file.Type() { + case lFileFile: + file.writer = file.fp + case lFileProcess: + file.writer, err = file.pp.StdinPipe() + if err != nil { + goto errreturn + } + } + case "full", "line": // TODO line buffer not supported + bufsize := L.OptInt(3, fileDefaultWriteBuffer) + switch file.Type() { + case lFileFile: + file.writer = bufio.NewWriterSize(file.fp, bufsize) + case lFileProcess: + writer, err = file.pp.StdinPipe() + if err != nil { + goto errreturn + } + file.writer = bufio.NewWriterSize(writer, bufsize) + } + } + L.Push(LTrue) + return 1 +errreturn: + L.Push(LNil) + L.Push(LString(err.Error())) + return 2 +} + +func ioInput(L *LState) int { + if L.GetTop() == 0 { + L.Push(fileDefIn(L)) + return 1 + } + switch lv := L.Get(1).(type) { + case LString: + file, err := newFile(L, nil, string(lv), os.O_RDONLY, 0600, false, true) + if err != nil { + L.RaiseError(err.Error()) + } + L.Get(UpvalueIndex(1)).(*LTable).RawSetInt(fileDefInIndex, file) + L.Push(file) + return 1 + case *LUserData: + if _, ok := lv.Value.(*lFile); ok { + L.Get(UpvalueIndex(1)).(*LTable).RawSetInt(fileDefInIndex, lv) + L.Push(lv) + return 1 + } + + } + L.ArgError(1, "string or file expedted, but got "+L.Get(1).Type().String()) + return 0 +} + +func ioClose(L *LState) int { + if L.GetTop() == 0 { + return fileCloseAux(L, fileDefOut(L).Value.(*lFile)) + } + return fileClose(L) +} + +func ioFlush(L *LState) int { + return fileFlushAux(L, fileDefOut(L).Value.(*lFile)) +} + +func ioLinesIter(L *LState) int { + var file *lFile + toclose := false + if ud, ok := L.Get(1).(*LUserData); ok { + file = ud.Value.(*lFile) + } else { + file = L.Get(UpvalueIndex(2)).(*LUserData).Value.(*lFile) + toclose = true + } + buf, _, err := file.reader.ReadLine() + if err != nil { + if err == io.EOF { + if toclose { + fileCloseAux(L, file) + } + L.Push(LNil) + return 1 + } + L.RaiseError(err.Error()) + } + L.Push(LString(string(buf))) + return 1 +} + +func ioLines(L *LState) int { + if L.GetTop() == 0 { + L.Push(L.Get(UpvalueIndex(2))) + L.Push(fileDefIn(L)) + return 2 + } + + path := L.CheckString(1) + ud, err := newFile(L, nil, path, os.O_RDONLY, os.FileMode(0600), false, true) + if err != nil { + return 0 + } + L.Push(L.NewClosure(ioLinesIter, L.Get(UpvalueIndex(1)), ud)) + return 1 +} + +var ioOpenOpions = []string{"r", "rb", "w", "wb", "a", "ab", "r+", "rb+", "w+", "wb+", "a+", "ab+"} + +func ioOpenFile(L *LState) int { + path := L.CheckString(1) + if L.GetTop() == 1 { + L.Push(LString("r")) + } + mode := os.O_RDONLY + perm := 0600 + writable := true + readable := true + switch ioOpenOpions[L.CheckOption(2, ioOpenOpions)] { + case "r", "rb": + mode = os.O_RDONLY + writable = false + case "w", "wb": + mode = os.O_WRONLY | os.O_CREATE + readable = false + case "a", "ab": + mode = os.O_WRONLY | os.O_APPEND | os.O_CREATE + case "r+", "rb+": + mode = os.O_RDWR + case "w+", "wb+": + mode = os.O_RDWR | os.O_TRUNC | os.O_CREATE + case "a+", "ab+": + mode = os.O_APPEND | os.O_RDWR | os.O_CREATE + } + file, err := newFile(L, nil, path, mode, os.FileMode(perm), writable, readable) + if err != nil { + L.Push(LNil) + L.Push(LString(err.Error())) + L.Push(LNumber(1)) // C-Lua compatibility: Original Lua pushes errno to the stack + return 3 + } + L.Push(file) + return 1 + +} + +var ioPopenOptions = []string{"r", "w"} + +func ioPopen(L *LState) int { + cmd := L.CheckString(1) + if L.GetTop() == 1 { + L.Push(LString("r")) + } + var file *LUserData + var err error + + switch ioPopenOptions[L.CheckOption(2, ioPopenOptions)] { + case "r": + file, err = newProcess(L, cmd, false, true) + case "w": + file, err = newProcess(L, cmd, true, false) + } + if err != nil { + L.Push(LNil) + L.Push(LString(err.Error())) + return 2 + } + L.Push(file) + return 1 +} + +func ioRead(L *LState) int { + return fileReadAux(L, fileDefIn(L).Value.(*lFile), 1) +} + +func ioType(L *LState) int { + ud, udok := L.Get(1).(*LUserData) + if !udok { + L.Push(LNil) + return 1 + } + file, ok := ud.Value.(*lFile) + if !ok { + L.Push(LNil) + return 1 + } + if file.closed { + L.Push(LString("closed file")) + return 1 + } + L.Push(LString("file")) + return 1 +} + +func ioTmpFile(L *LState) int { + file, err := ioutil.TempFile("", "") + if err != nil { + L.Push(LNil) + L.Push(LString(err.Error())) + return 2 + } + L.G.tempFiles = append(L.G.tempFiles, file) + ud, _ := newFile(L, file, "", 0, os.FileMode(0), true, true) + L.Push(ud) + return 1 +} + +func ioOutput(L *LState) int { + if L.GetTop() == 0 { + L.Push(fileDefOut(L)) + return 1 + } + switch lv := L.Get(1).(type) { + case LString: + file, err := newFile(L, nil, string(lv), os.O_WRONLY|os.O_CREATE, 0600, true, false) + if err != nil { + L.RaiseError(err.Error()) + } + L.Get(UpvalueIndex(1)).(*LTable).RawSetInt(fileDefOutIndex, file) + L.Push(file) + return 1 + case *LUserData: + if _, ok := lv.Value.(*lFile); ok { + L.Get(UpvalueIndex(1)).(*LTable).RawSetInt(fileDefOutIndex, lv) + L.Push(lv) + return 1 + } + + } + L.ArgError(1, "string or file expedted, but got "+L.Get(1).Type().String()) + return 0 +} + +func ioWrite(L *LState) int { + return fileWriteAux(L, fileDefOut(L).Value.(*lFile), 1) +} + +// diff --git a/vendor/github.com/yuin/gopher-lua/linit.go b/vendor/github.com/yuin/gopher-lua/linit.go new file mode 100644 index 00000000000..cd96d660151 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/linit.go @@ -0,0 +1,54 @@ +package lua + +const ( + // BaseLibName is here for consistency; the base functions have no namespace/library. + BaseLibName = "" + // LoadLibName is here for consistency; the loading system has no namespace/library. + LoadLibName = "package" + // TabLibName is the name of the table Library. + TabLibName = "table" + // IoLibName is the name of the io Library. + IoLibName = "io" + // OsLibName is the name of the os Library. + OsLibName = "os" + // StringLibName is the name of the string Library. + StringLibName = "string" + // MathLibName is the name of the math Library. + MathLibName = "math" + // DebugLibName is the name of the debug Library. + DebugLibName = "debug" + // ChannelLibName is the name of the channel Library. + ChannelLibName = "channel" + // CoroutineLibName is the name of the coroutine Library. + CoroutineLibName = "coroutine" +) + +type luaLib struct { + libName string + libFunc LGFunction +} + +var luaLibs = []luaLib{ + luaLib{LoadLibName, OpenPackage}, + luaLib{BaseLibName, OpenBase}, + luaLib{TabLibName, OpenTable}, + luaLib{IoLibName, OpenIo}, + luaLib{OsLibName, OpenOs}, + luaLib{StringLibName, OpenString}, + luaLib{MathLibName, OpenMath}, + luaLib{DebugLibName, OpenDebug}, + luaLib{ChannelLibName, OpenChannel}, + luaLib{CoroutineLibName, OpenCoroutine}, +} + +// OpenLibs loads the built-in libraries. It is equivalent to running OpenLoad, +// then OpenBase, then iterating over the other OpenXXX functions in any order. +func (ls *LState) OpenLibs() { + // NB: Map iteration order in Go is deliberately randomised, so must open Load/Base + // prior to iterating. + for _, lib := range luaLibs { + ls.Push(ls.NewFunction(lib.libFunc)) + ls.Push(LString(lib.libName)) + ls.Call(1, 0) + } +} diff --git a/vendor/github.com/yuin/gopher-lua/loadlib.go b/vendor/github.com/yuin/gopher-lua/loadlib.go new file mode 100644 index 00000000000..772bb04ad88 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/loadlib.go @@ -0,0 +1,125 @@ +package lua + +import ( + "fmt" + "os" + "path/filepath" + "strings" +) + +/* load lib {{{ */ + +var loLoaders = []LGFunction{loLoaderPreload, loLoaderLua} + +func loGetPath(env string, defpath string) string { + path := os.Getenv(env) + if len(path) == 0 { + path = defpath + } + path = strings.Replace(path, ";;", ";"+defpath+";", -1) + if os.PathSeparator != '/' { + dir, err := filepath.Abs(filepath.Dir(os.Args[0])) + if err != nil { + panic(err) + } + path = strings.Replace(path, "!", dir, -1) + } + return path +} + +func loFindFile(L *LState, name, pname string) (string, string) { + name = strings.Replace(name, ".", string(os.PathSeparator), -1) + lv := L.GetField(L.GetField(L.Get(EnvironIndex), "package"), pname) + path, ok := lv.(LString) + if !ok { + L.RaiseError("package.%s must be a string", pname) + } + messages := []string{} + for _, pattern := range strings.Split(string(path), ";") { + luapath := strings.Replace(pattern, "?", name, -1) + if _, err := os.Stat(luapath); err == nil { + return luapath, "" + } else { + messages = append(messages, err.Error()) + } + } + return "", strings.Join(messages, "\n\t") +} + +func OpenPackage(L *LState) int { + packagemod := L.RegisterModule(LoadLibName, loFuncs) + + L.SetField(packagemod, "preload", L.NewTable()) + + loaders := L.CreateTable(len(loLoaders), 0) + for i, loader := range loLoaders { + L.RawSetInt(loaders, i+1, L.NewFunction(loader)) + } + L.SetField(packagemod, "loaders", loaders) + L.SetField(L.Get(RegistryIndex), "_LOADERS", loaders) + + loaded := L.NewTable() + L.SetField(packagemod, "loaded", loaded) + L.SetField(L.Get(RegistryIndex), "_LOADED", loaded) + + L.SetField(packagemod, "path", LString(loGetPath(LuaPath, LuaPathDefault))) + L.SetField(packagemod, "cpath", emptyLString) + + L.Push(packagemod) + return 1 +} + +var loFuncs = map[string]LGFunction{ + "loadlib": loLoadLib, + "seeall": loSeeAll, +} + +func loLoaderPreload(L *LState) int { + name := L.CheckString(1) + preload := L.GetField(L.GetField(L.Get(EnvironIndex), "package"), "preload") + if _, ok := preload.(*LTable); !ok { + L.RaiseError("package.preload must be a table") + } + lv := L.GetField(preload, name) + if lv == LNil { + L.Push(LString(fmt.Sprintf("no field package.preload['%s']", name))) + return 1 + } + L.Push(lv) + return 1 +} + +func loLoaderLua(L *LState) int { + name := L.CheckString(1) + path, msg := loFindFile(L, name, "path") + if len(path) == 0 { + L.Push(LString(msg)) + return 1 + } + fn, err1 := L.LoadFile(path) + if err1 != nil { + L.RaiseError(err1.Error()) + } + L.Push(fn) + return 1 +} + +func loLoadLib(L *LState) int { + L.RaiseError("loadlib is not supported") + return 0 +} + +func loSeeAll(L *LState) int { + mod := L.CheckTable(1) + mt := L.GetMetatable(mod) + if mt == LNil { + mt = L.CreateTable(0, 1) + L.SetMetatable(mod, mt) + } + L.SetField(mt, "__index", L.Get(GlobalsIndex)) + return 0 +} + +/* }}} */ + +// diff --git a/vendor/github.com/yuin/gopher-lua/mathlib.go b/vendor/github.com/yuin/gopher-lua/mathlib.go new file mode 100644 index 00000000000..e612f2f0bf8 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/mathlib.go @@ -0,0 +1,231 @@ +package lua + +import ( + "math" + "math/rand" +) + +func OpenMath(L *LState) int { + mod := L.RegisterModule(MathLibName, mathFuncs).(*LTable) + mod.RawSetString("pi", LNumber(math.Pi)) + mod.RawSetString("huge", LNumber(math.MaxFloat64)) + L.Push(mod) + return 1 +} + +var mathFuncs = map[string]LGFunction{ + "abs": mathAbs, + "acos": mathAcos, + "asin": mathAsin, + "atan": mathAtan, + "atan2": mathAtan2, + "ceil": mathCeil, + "cos": mathCos, + "cosh": mathCosh, + "deg": mathDeg, + "exp": mathExp, + "floor": mathFloor, + "fmod": mathFmod, + "frexp": mathFrexp, + "ldexp": mathLdexp, + "log": mathLog, + "log10": mathLog10, + "max": mathMax, + "min": mathMin, + "mod": mathMod, + "modf": mathModf, + "pow": mathPow, + "rad": mathRad, + "random": mathRandom, + "randomseed": mathRandomseed, + "sin": mathSin, + "sinh": mathSinh, + "sqrt": mathSqrt, + "tan": mathTan, + "tanh": mathTanh, +} + +func mathAbs(L *LState) int { + L.Push(LNumber(math.Abs(float64(L.CheckNumber(1))))) + return 1 +} + +func mathAcos(L *LState) int { + L.Push(LNumber(math.Acos(float64(L.CheckNumber(1))))) + return 1 +} + +func mathAsin(L *LState) int { + L.Push(LNumber(math.Asin(float64(L.CheckNumber(1))))) + return 1 +} + +func mathAtan(L *LState) int { + L.Push(LNumber(math.Atan(float64(L.CheckNumber(1))))) + return 1 +} + +func mathAtan2(L *LState) int { + L.Push(LNumber(math.Atan2(float64(L.CheckNumber(1)), float64(L.CheckNumber(2))))) + return 1 +} + +func mathCeil(L *LState) int { + L.Push(LNumber(math.Ceil(float64(L.CheckNumber(1))))) + return 1 +} + +func mathCos(L *LState) int { + L.Push(LNumber(math.Cos(float64(L.CheckNumber(1))))) + return 1 +} + +func mathCosh(L *LState) int { + L.Push(LNumber(math.Cosh(float64(L.CheckNumber(1))))) + return 1 +} + +func mathDeg(L *LState) int { + L.Push(LNumber(float64(L.CheckNumber(1)) * 180 / math.Pi)) + return 1 +} + +func mathExp(L *LState) int { + L.Push(LNumber(math.Exp(float64(L.CheckNumber(1))))) + return 1 +} + +func mathFloor(L *LState) int { + L.Push(LNumber(math.Floor(float64(L.CheckNumber(1))))) + return 1 +} + +func mathFmod(L *LState) int { + L.Push(LNumber(math.Mod(float64(L.CheckNumber(1)), float64(L.CheckNumber(2))))) + return 1 +} + +func mathFrexp(L *LState) int { + v1, v2 := math.Frexp(float64(L.CheckNumber(1))) + L.Push(LNumber(v1)) + L.Push(LNumber(v2)) + return 2 +} + +func mathLdexp(L *LState) int { + L.Push(LNumber(math.Ldexp(float64(L.CheckNumber(1)), L.CheckInt(2)))) + return 1 +} + +func mathLog(L *LState) int { + L.Push(LNumber(math.Log(float64(L.CheckNumber(1))))) + return 1 +} + +func mathLog10(L *LState) int { + L.Push(LNumber(math.Log10(float64(L.CheckNumber(1))))) + return 1 +} + +func mathMax(L *LState) int { + if L.GetTop() == 0 { + L.RaiseError("wrong number of arguments") + } + max := L.CheckNumber(1) + top := L.GetTop() + for i := 2; i <= top; i++ { + v := L.CheckNumber(i) + if v > max { + max = v + } + } + L.Push(max) + return 1 +} + +func mathMin(L *LState) int { + if L.GetTop() == 0 { + L.RaiseError("wrong number of arguments") + } + min := L.CheckNumber(1) + top := L.GetTop() + for i := 2; i <= top; i++ { + v := L.CheckNumber(i) + if v < min { + min = v + } + } + L.Push(min) + return 1 +} + +func mathMod(L *LState) int { + lhs := L.CheckNumber(1) + rhs := L.CheckNumber(2) + L.Push(luaModulo(lhs, rhs)) + return 1 +} + +func mathModf(L *LState) int { + v1, v2 := math.Modf(float64(L.CheckNumber(1))) + L.Push(LNumber(v1)) + L.Push(LNumber(v2)) + return 2 +} + +func mathPow(L *LState) int { + L.Push(LNumber(math.Pow(float64(L.CheckNumber(1)), float64(L.CheckNumber(2))))) + return 1 +} + +func mathRad(L *LState) int { + L.Push(LNumber(float64(L.CheckNumber(1)) * math.Pi / 180)) + return 1 +} + +func mathRandom(L *LState) int { + switch L.GetTop() { + case 0: + L.Push(LNumber(rand.Float64())) + case 1: + n := L.CheckInt(1) + L.Push(LNumber(rand.Intn(n) + 1)) + default: + min := L.CheckInt(1) + max := L.CheckInt(2) + 1 + L.Push(LNumber(rand.Intn(max-min) + min)) + } + return 1 +} + +func mathRandomseed(L *LState) int { + rand.Seed(L.CheckInt64(1)) + return 0 +} + +func mathSin(L *LState) int { + L.Push(LNumber(math.Sin(float64(L.CheckNumber(1))))) + return 1 +} + +func mathSinh(L *LState) int { + L.Push(LNumber(math.Sinh(float64(L.CheckNumber(1))))) + return 1 +} + +func mathSqrt(L *LState) int { + L.Push(LNumber(math.Sqrt(float64(L.CheckNumber(1))))) + return 1 +} + +func mathTan(L *LState) int { + L.Push(LNumber(math.Tan(float64(L.CheckNumber(1))))) + return 1 +} + +func mathTanh(L *LState) int { + L.Push(LNumber(math.Tanh(float64(L.CheckNumber(1))))) + return 1 +} + +// diff --git a/vendor/github.com/yuin/gopher-lua/opcode.go b/vendor/github.com/yuin/gopher-lua/opcode.go new file mode 100644 index 00000000000..91fff1c9b41 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/opcode.go @@ -0,0 +1,371 @@ +package lua + +import ( + "fmt" +) + +/* + gopherlua uses Lua 5.1.4's opcodes. + Lua 5.1.4 opcodes layout: + + instruction = 32bit(fixed length) + + +---------------------------------------------+ + |0-5(6bits)|6-13(8bit)|14-22(9bit)|23-31(9bit)| + |==========+==========+===========+===========| + | opcode | A | C | B | + |----------+----------+-----------+-----------| + | opcode | A | Bx(unsigned) | + |----------+----------+-----------+-----------| + | opcode | A | sBx(signed) | + +---------------------------------------------+ +*/ + +const opInvalidInstruction = ^uint32(0) + +const opSizeCode = 6 +const opSizeA = 8 +const opSizeB = 9 +const opSizeC = 9 +const opSizeBx = 18 +const opSizesBx = 18 + +const opMaxArgsA = (1 << opSizeA) - 1 +const opMaxArgsB = (1 << opSizeB) - 1 +const opMaxArgsC = (1 << opSizeC) - 1 +const opMaxArgBx = (1 << opSizeBx) - 1 +const opMaxArgSbx = opMaxArgBx >> 1 + +const ( + OP_MOVE int = iota /* A B R(A) := R(B) */ + OP_MOVEN /* A B R(A) := R(B); followed by R(C) MOVE ops */ + OP_LOADK /* A Bx R(A) := Kst(Bx) */ + OP_LOADBOOL /* A B C R(A) := (Bool)B; if (C) pc++ */ + OP_LOADNIL /* A B R(A) := ... := R(B) := nil */ + OP_GETUPVAL /* A B R(A) := UpValue[B] */ + + OP_GETGLOBAL /* A Bx R(A) := Gbl[Kst(Bx)] */ + OP_GETTABLE /* A B C R(A) := R(B)[RK(C)] */ + OP_GETTABLEKS /* A B C R(A) := R(B)[RK(C)] ; RK(C) is constant string */ + + OP_SETGLOBAL /* A Bx Gbl[Kst(Bx)] := R(A) */ + OP_SETUPVAL /* A B UpValue[B] := R(A) */ + OP_SETTABLE /* A B C R(A)[RK(B)] := RK(C) */ + OP_SETTABLEKS /* A B C R(A)[RK(B)] := RK(C) ; RK(B) is constant string */ + + OP_NEWTABLE /* A B C R(A) := {} (size = BC) */ + + OP_SELF /* A B C R(A+1) := R(B); R(A) := R(B)[RK(C)] */ + + OP_ADD /* A B C R(A) := RK(B) + RK(C) */ + OP_SUB /* A B C R(A) := RK(B) - RK(C) */ + OP_MUL /* A B C R(A) := RK(B) * RK(C) */ + OP_DIV /* A B C R(A) := RK(B) / RK(C) */ + OP_MOD /* A B C R(A) := RK(B) % RK(C) */ + OP_POW /* A B C R(A) := RK(B) ^ RK(C) */ + OP_UNM /* A B R(A) := -R(B) */ + OP_NOT /* A B R(A) := not R(B) */ + OP_LEN /* A B R(A) := length of R(B) */ + + OP_CONCAT /* A B C R(A) := R(B).. ... ..R(C) */ + + OP_JMP /* sBx pc+=sBx */ + + OP_EQ /* A B C if ((RK(B) == RK(C)) ~= A) then pc++ */ + OP_LT /* A B C if ((RK(B) < RK(C)) ~= A) then pc++ */ + OP_LE /* A B C if ((RK(B) <= RK(C)) ~= A) then pc++ */ + + OP_TEST /* A C if not (R(A) <=> C) then pc++ */ + OP_TESTSET /* A B C if (R(B) <=> C) then R(A) := R(B) else pc++ */ + + OP_CALL /* A B C R(A) ... R(A+C-2) := R(A)(R(A+1) ... R(A+B-1)) */ + OP_TAILCALL /* A B C return R(A)(R(A+1) ... R(A+B-1)) */ + OP_RETURN /* A B return R(A) ... R(A+B-2) (see note) */ + + OP_FORLOOP /* A sBx R(A)+=R(A+2); + if R(A) =) R(A)*/ + OP_CLOSURE /* A Bx R(A) := closure(KPROTO[Bx] R(A) ... R(A+n)) */ + + OP_VARARG /* A B R(A) R(A+1) ... R(A+B-1) = vararg */ + + OP_NOP /* NOP */ +) +const opCodeMax = OP_NOP + +type opArgMode int + +const ( + opArgModeN opArgMode = iota + opArgModeU + opArgModeR + opArgModeK +) + +type opType int + +const ( + opTypeABC = iota + opTypeABx + opTypeASbx +) + +type opProp struct { + Name string + IsTest bool + SetRegA bool + ModeArgB opArgMode + ModeArgC opArgMode + Type opType +} + +var opProps = []opProp{ + opProp{"MOVE", false, true, opArgModeR, opArgModeN, opTypeABC}, + opProp{"MOVEN", false, true, opArgModeR, opArgModeN, opTypeABC}, + opProp{"LOADK", false, true, opArgModeK, opArgModeN, opTypeABx}, + opProp{"LOADBOOL", false, true, opArgModeU, opArgModeU, opTypeABC}, + opProp{"LOADNIL", false, true, opArgModeR, opArgModeN, opTypeABC}, + opProp{"GETUPVAL", false, true, opArgModeU, opArgModeN, opTypeABC}, + opProp{"GETGLOBAL", false, true, opArgModeK, opArgModeN, opTypeABx}, + opProp{"GETTABLE", false, true, opArgModeR, opArgModeK, opTypeABC}, + opProp{"GETTABLEKS", false, true, opArgModeR, opArgModeK, opTypeABC}, + opProp{"SETGLOBAL", false, false, opArgModeK, opArgModeN, opTypeABx}, + opProp{"SETUPVAL", false, false, opArgModeU, opArgModeN, opTypeABC}, + opProp{"SETTABLE", false, false, opArgModeK, opArgModeK, opTypeABC}, + opProp{"SETTABLEKS", false, false, opArgModeK, opArgModeK, opTypeABC}, + opProp{"NEWTABLE", false, true, opArgModeU, opArgModeU, opTypeABC}, + opProp{"SELF", false, true, opArgModeR, opArgModeK, opTypeABC}, + opProp{"ADD", false, true, opArgModeK, opArgModeK, opTypeABC}, + opProp{"SUB", false, true, opArgModeK, opArgModeK, opTypeABC}, + opProp{"MUL", false, true, opArgModeK, opArgModeK, opTypeABC}, + opProp{"DIV", false, true, opArgModeK, opArgModeK, opTypeABC}, + opProp{"MOD", false, true, opArgModeK, opArgModeK, opTypeABC}, + opProp{"POW", false, true, opArgModeK, opArgModeK, opTypeABC}, + opProp{"UNM", false, true, opArgModeR, opArgModeN, opTypeABC}, + opProp{"NOT", false, true, opArgModeR, opArgModeN, opTypeABC}, + opProp{"LEN", false, true, opArgModeR, opArgModeN, opTypeABC}, + opProp{"CONCAT", false, true, opArgModeR, opArgModeR, opTypeABC}, + opProp{"JMP", false, false, opArgModeR, opArgModeN, opTypeASbx}, + opProp{"EQ", true, false, opArgModeK, opArgModeK, opTypeABC}, + opProp{"LT", true, false, opArgModeK, opArgModeK, opTypeABC}, + opProp{"LE", true, false, opArgModeK, opArgModeK, opTypeABC}, + opProp{"TEST", true, true, opArgModeR, opArgModeU, opTypeABC}, + opProp{"TESTSET", true, true, opArgModeR, opArgModeU, opTypeABC}, + opProp{"CALL", false, true, opArgModeU, opArgModeU, opTypeABC}, + opProp{"TAILCALL", false, true, opArgModeU, opArgModeU, opTypeABC}, + opProp{"RETURN", false, false, opArgModeU, opArgModeN, opTypeABC}, + opProp{"FORLOOP", false, true, opArgModeR, opArgModeN, opTypeASbx}, + opProp{"FORPREP", false, true, opArgModeR, opArgModeN, opTypeASbx}, + opProp{"TFORLOOP", true, false, opArgModeN, opArgModeU, opTypeABC}, + opProp{"SETLIST", false, false, opArgModeU, opArgModeU, opTypeABC}, + opProp{"CLOSE", false, false, opArgModeN, opArgModeN, opTypeABC}, + opProp{"CLOSURE", false, true, opArgModeU, opArgModeN, opTypeABx}, + opProp{"VARARG", false, true, opArgModeU, opArgModeN, opTypeABC}, + opProp{"NOP", false, false, opArgModeR, opArgModeN, opTypeASbx}, +} + +func opGetOpCode(inst uint32) int { + return int(inst >> 26) +} + +func opSetOpCode(inst *uint32, opcode int) { + *inst = (*inst & 0x3ffffff) | uint32(opcode<<26) +} + +func opGetArgA(inst uint32) int { + return int(inst>>18) & 0xff +} + +func opSetArgA(inst *uint32, arg int) { + *inst = (*inst & 0xfc03ffff) | uint32((arg&0xff)<<18) +} + +func opGetArgB(inst uint32) int { + return int(inst & 0x1ff) +} + +func opSetArgB(inst *uint32, arg int) { + *inst = (*inst & 0xfffffe00) | uint32(arg&0x1ff) +} + +func opGetArgC(inst uint32) int { + return int(inst>>9) & 0x1ff +} + +func opSetArgC(inst *uint32, arg int) { + *inst = (*inst & 0xfffc01ff) | uint32((arg&0x1ff)<<9) +} + +func opGetArgBx(inst uint32) int { + return int(inst & 0x3ffff) +} + +func opSetArgBx(inst *uint32, arg int) { + *inst = (*inst & 0xfffc0000) | uint32(arg&0x3ffff) +} + +func opGetArgSbx(inst uint32) int { + return opGetArgBx(inst) - opMaxArgSbx +} + +func opSetArgSbx(inst *uint32, arg int) { + opSetArgBx(inst, arg+opMaxArgSbx) +} + +func opCreateABC(op int, a int, b int, c int) uint32 { + var inst uint32 = 0 + opSetOpCode(&inst, op) + opSetArgA(&inst, a) + opSetArgB(&inst, b) + opSetArgC(&inst, c) + return inst +} + +func opCreateABx(op int, a int, bx int) uint32 { + var inst uint32 = 0 + opSetOpCode(&inst, op) + opSetArgA(&inst, a) + opSetArgBx(&inst, bx) + return inst +} + +func opCreateASbx(op int, a int, sbx int) uint32 { + var inst uint32 = 0 + opSetOpCode(&inst, op) + opSetArgA(&inst, a) + opSetArgSbx(&inst, sbx) + return inst +} + +const opBitRk = 1 << (opSizeB - 1) +const opMaxIndexRk = opBitRk - 1 + +func opIsK(value int) bool { + return bool((value & opBitRk) != 0) +} + +func opIndexK(value int) int { + return value & ^opBitRk +} + +func opRkAsk(value int) int { + return value | opBitRk +} + +func opToString(inst uint32) string { + op := opGetOpCode(inst) + if op > opCodeMax { + return "" + } + prop := &(opProps[op]) + + arga := opGetArgA(inst) + argb := opGetArgB(inst) + argc := opGetArgC(inst) + argbx := opGetArgBx(inst) + argsbx := opGetArgSbx(inst) + + buf := "" + switch prop.Type { + case opTypeABC: + buf = fmt.Sprintf("%s | %d, %d, %d", prop.Name, arga, argb, argc) + case opTypeABx: + buf = fmt.Sprintf("%s | %d, %d", prop.Name, arga, argbx) + case opTypeASbx: + buf = fmt.Sprintf("%s | %d, %d", prop.Name, arga, argsbx) + } + + switch op { + case OP_MOVE: + buf += fmt.Sprintf("; R(%v) := R(%v)", arga, argb) + case OP_MOVEN: + buf += fmt.Sprintf("; R(%v) := R(%v); followed by %v MOVE ops", arga, argb, argc) + case OP_LOADK: + buf += fmt.Sprintf("; R(%v) := Kst(%v)", arga, argbx) + case OP_LOADBOOL: + buf += fmt.Sprintf("; R(%v) := (Bool)%v; if (%v) pc++", arga, argb, argc) + case OP_LOADNIL: + buf += fmt.Sprintf("; R(%v) := ... := R(%v) := nil", arga, argb) + case OP_GETUPVAL: + buf += fmt.Sprintf("; R(%v) := UpValue[%v]", arga, argb) + case OP_GETGLOBAL: + buf += fmt.Sprintf("; R(%v) := Gbl[Kst(%v)]", arga, argbx) + case OP_GETTABLE: + buf += fmt.Sprintf("; R(%v) := R(%v)[RK(%v)]", arga, argb, argc) + case OP_GETTABLEKS: + buf += fmt.Sprintf("; R(%v) := R(%v)[RK(%v)] ; RK(%v) is constant string", arga, argb, argc, argc) + case OP_SETGLOBAL: + buf += fmt.Sprintf("; Gbl[Kst(%v)] := R(%v)", argbx, arga) + case OP_SETUPVAL: + buf += fmt.Sprintf("; UpValue[%v] := R(%v)", argb, arga) + case OP_SETTABLE: + buf += fmt.Sprintf("; R(%v)[RK(%v)] := RK(%v)", arga, argb, argc) + case OP_SETTABLEKS: + buf += fmt.Sprintf("; R(%v)[RK(%v)] := RK(%v) ; RK(%v) is constant string", arga, argb, argc, argb) + case OP_NEWTABLE: + buf += fmt.Sprintf("; R(%v) := {} (size = BC)", arga) + case OP_SELF: + buf += fmt.Sprintf("; R(%v+1) := R(%v); R(%v) := R(%v)[RK(%v)]", arga, argb, arga, argb, argc) + case OP_ADD: + buf += fmt.Sprintf("; R(%v) := RK(%v) + RK(%v)", arga, argb, argc) + case OP_SUB: + buf += fmt.Sprintf("; R(%v) := RK(%v) - RK(%v)", arga, argb, argc) + case OP_MUL: + buf += fmt.Sprintf("; R(%v) := RK(%v) * RK(%v)", arga, argb, argc) + case OP_DIV: + buf += fmt.Sprintf("; R(%v) := RK(%v) / RK(%v)", arga, argb, argc) + case OP_MOD: + buf += fmt.Sprintf("; R(%v) := RK(%v) %% RK(%v)", arga, argb, argc) + case OP_POW: + buf += fmt.Sprintf("; R(%v) := RK(%v) ^ RK(%v)", arga, argb, argc) + case OP_UNM: + buf += fmt.Sprintf("; R(%v) := -R(%v)", arga, argb) + case OP_NOT: + buf += fmt.Sprintf("; R(%v) := not R(%v)", arga, argb) + case OP_LEN: + buf += fmt.Sprintf("; R(%v) := length of R(%v)", arga, argb) + case OP_CONCAT: + buf += fmt.Sprintf("; R(%v) := R(%v).. ... ..R(%v)", arga, argb, argc) + case OP_JMP: + buf += fmt.Sprintf("; pc+=%v", argsbx) + case OP_EQ: + buf += fmt.Sprintf("; if ((RK(%v) == RK(%v)) ~= %v) then pc++", argb, argc, arga) + case OP_LT: + buf += fmt.Sprintf("; if ((RK(%v) < RK(%v)) ~= %v) then pc++", argb, argc, arga) + case OP_LE: + buf += fmt.Sprintf("; if ((RK(%v) <= RK(%v)) ~= %v) then pc++", argb, argc, arga) + case OP_TEST: + buf += fmt.Sprintf("; if not (R(%v) <=> %v) then pc++", arga, argc) + case OP_TESTSET: + buf += fmt.Sprintf("; if (R(%v) <=> %v) then R(%v) := R(%v) else pc++", argb, argc, arga, argb) + case OP_CALL: + buf += fmt.Sprintf("; R(%v) ... R(%v+%v-2) := R(%v)(R(%v+1) ... R(%v+%v-1))", arga, arga, argc, arga, arga, arga, argb) + case OP_TAILCALL: + buf += fmt.Sprintf("; return R(%v)(R(%v+1) ... R(%v+%v-1))", arga, arga, arga, argb) + case OP_RETURN: + buf += fmt.Sprintf("; return R(%v) ... R(%v+%v-2)", arga, arga, argb) + case OP_FORLOOP: + buf += fmt.Sprintf("; R(%v)+=R(%v+2); if R(%v) =) R(%v)", arga) + case OP_CLOSURE: + buf += fmt.Sprintf("; R(%v) := closure(KPROTO[%v] R(%v) ... R(%v+n))", arga, argbx, arga, arga) + case OP_VARARG: + buf += fmt.Sprintf("; R(%v) R(%v+1) ... R(%v+%v-1) = vararg", arga, arga, arga, argb) + case OP_NOP: + /* nothing to do */ + } + return buf +} diff --git a/vendor/github.com/yuin/gopher-lua/oslib.go b/vendor/github.com/yuin/gopher-lua/oslib.go new file mode 100644 index 00000000000..c70a99bf136 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/oslib.go @@ -0,0 +1,221 @@ +package lua + +import ( + "io/ioutil" + "os" + "strings" + "time" +) + +var startedAt time.Time + +func init() { + startedAt = time.Now() +} + +func getIntField(L *LState, tb *LTable, key string, v int) int { + ret := tb.RawGetString(key) + + switch lv := ret.(type) { + case LNumber: + return int(lv) + case LString: + 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 + slv = strings.TrimLeft(slv, "0") + } + if num, err := parseNumber(slv); err == nil { + return int(num) + } + default: + return v + } + + return v +} + +func getBoolField(L *LState, tb *LTable, key string, v bool) bool { + ret := tb.RawGetString(key) + if lb, ok := ret.(LBool); ok { + return bool(lb) + } + return v +} + +func OpenOs(L *LState) int { + osmod := L.RegisterModule(OsLibName, osFuncs) + L.Push(osmod) + return 1 +} + +var osFuncs = map[string]LGFunction{ + "clock": osClock, + "difftime": osDiffTime, + "execute": osExecute, + "exit": osExit, + "date": osDate, + "getenv": osGetEnv, + "remove": osRemove, + "rename": osRename, + "setenv": osSetEnv, + "setlocale": osSetLocale, + "time": osTime, + "tmpname": osTmpname, +} + +func osClock(L *LState) int { + L.Push(LNumber(float64(time.Now().Sub(startedAt)) / float64(time.Second))) + return 1 +} + +func osDiffTime(L *LState) int { + L.Push(LNumber(L.CheckInt64(1) - L.CheckInt64(2))) + return 1 +} + +func osExecute(L *LState) int { + var procAttr os.ProcAttr + procAttr.Files = []*os.File{os.Stdin, os.Stdout, os.Stderr} + cmd, args := popenArgs(L.CheckString(1)) + args = append([]string{cmd}, args...) + process, err := os.StartProcess(cmd, args, &procAttr) + if err != nil { + L.Push(LNumber(1)) + return 1 + } + + ps, err := process.Wait() + if err != nil || !ps.Success() { + L.Push(LNumber(1)) + return 1 + } + L.Push(LNumber(0)) + return 1 +} + +func osExit(L *LState) int { + L.Close() + os.Exit(L.OptInt(1, 0)) + return 1 +} + +func osDate(L *LState) int { + t := time.Now() + cfmt := "%c" + if L.GetTop() >= 1 { + cfmt = L.CheckString(1) + if strings.HasPrefix(cfmt, "!") { + t = time.Now().UTC() + cfmt = strings.TrimLeft(cfmt, "!") + } + if L.GetTop() >= 2 { + t = time.Unix(L.CheckInt64(2), 0) + } + if strings.HasPrefix(cfmt, "*t") { + ret := L.NewTable() + ret.RawSetString("year", LNumber(t.Year())) + ret.RawSetString("month", LNumber(t.Month())) + ret.RawSetString("day", LNumber(t.Day())) + ret.RawSetString("hour", LNumber(t.Hour())) + ret.RawSetString("min", LNumber(t.Minute())) + ret.RawSetString("sec", LNumber(t.Second())) + ret.RawSetString("wday", LNumber(t.Weekday()+1)) + // TODO yday & dst + ret.RawSetString("yday", LNumber(0)) + ret.RawSetString("isdst", LFalse) + L.Push(ret) + return 1 + } + } + L.Push(LString(strftime(t, cfmt))) + return 1 +} + +func osGetEnv(L *LState) int { + v := os.Getenv(L.CheckString(1)) + if len(v) == 0 { + L.Push(LNil) + } else { + L.Push(LString(v)) + } + return 1 +} + +func osRemove(L *LState) int { + err := os.Remove(L.CheckString(1)) + if err != nil { + L.Push(LNil) + L.Push(LString(err.Error())) + return 2 + } else { + L.Push(LTrue) + return 1 + } +} + +func osRename(L *LState) int { + err := os.Rename(L.CheckString(1), L.CheckString(2)) + if err != nil { + L.Push(LNil) + L.Push(LString(err.Error())) + return 2 + } else { + L.Push(LTrue) + return 1 + } +} + +func osSetLocale(L *LState) int { + // setlocale is not supported + L.Push(LFalse) + return 1 +} + +func osSetEnv(L *LState) int { + err := os.Setenv(L.CheckString(1), L.CheckString(2)) + if err != nil { + L.Push(LNil) + L.Push(LString(err.Error())) + return 2 + } else { + L.Push(LTrue) + return 1 + } +} + +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) + } + L.Push(LNumber(t.Unix())) + } + return 1 +} + +func osTmpname(L *LState) int { + file, err := ioutil.TempFile("", "") + if err != nil { + L.RaiseError("unable to generate a unique filename") + } + file.Close() + os.Remove(file.Name()) // ignore errors + L.Push(LString(file.Name())) + return 1 +} + +// diff --git a/vendor/github.com/yuin/gopher-lua/package.go b/vendor/github.com/yuin/gopher-lua/package.go new file mode 100644 index 00000000000..9fde3f0c215 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/package.go @@ -0,0 +1,7 @@ +// GopherLua: VM and compiler for Lua in Go +package lua + +const PackageName = "GopherLua" +const PackageVersion = "0.1" +const PackageAuthors = "Yusuke Inuzuka" +const PackageCopyRight = PackageName + " " + PackageVersion + " Copyright (C) 2015 -2017 " + PackageAuthors diff --git a/vendor/github.com/yuin/gopher-lua/parse/Makefile b/vendor/github.com/yuin/gopher-lua/parse/Makefile new file mode 100644 index 00000000000..6dd048c165f --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/parse/Makefile @@ -0,0 +1,4 @@ +all : parser.go + +parser.go : parser.go.y + goyacc -o $@ parser.go.y; [ -f y.output ] && ( rm -f y.output ) diff --git a/vendor/github.com/yuin/gopher-lua/parse/lexer.go b/vendor/github.com/yuin/gopher-lua/parse/lexer.go new file mode 100644 index 00000000000..d711e78bc19 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/parse/lexer.go @@ -0,0 +1,539 @@ +package parse + +import ( + "bufio" + "bytes" + "fmt" + "github.com/yuin/gopher-lua/ast" + "io" + "reflect" + "strconv" + "strings" +) + +const EOF = -1 +const whitespace1 = 1<<'\t' | 1<<' ' +const whitespace2 = 1<<'\t' | 1<<'\n' | 1<<'\r' | 1<<' ' + +type Error struct { + Pos ast.Position + Message string + Token string +} + +func (e *Error) Error() string { + pos := e.Pos + if pos.Line == EOF { + return fmt.Sprintf("%v at EOF: %s\n", pos.Source, e.Message) + } else { + return fmt.Sprintf("%v line:%d(column:%d) near '%v': %s\n", pos.Source, pos.Line, pos.Column, e.Token, e.Message) + } +} + +func writeChar(buf *bytes.Buffer, c int) { buf.WriteByte(byte(c)) } + +func isDecimal(ch int) bool { return '0' <= ch && ch <= '9' } + +func isIdent(ch int, pos int) bool { + return ch == '_' || 'A' <= ch && ch <= 'Z' || 'a' <= ch && ch <= 'z' || isDecimal(ch) && pos > 0 +} + +func isDigit(ch int) bool { + return '0' <= ch && ch <= '9' || 'a' <= ch && ch <= 'f' || 'A' <= ch && ch <= 'F' +} + +type Scanner struct { + Pos ast.Position + reader *bufio.Reader +} + +func NewScanner(reader io.Reader, source string) *Scanner { + return &Scanner{ + Pos: ast.Position{ + Source: source, + Line: 1, + Column: 0, + }, + reader: bufio.NewReaderSize(reader, 4096), + } +} + +func (sc *Scanner) Error(tok string, msg string) *Error { return &Error{sc.Pos, msg, tok} } + +func (sc *Scanner) TokenError(tok ast.Token, msg string) *Error { return &Error{tok.Pos, msg, tok.Str} } + +func (sc *Scanner) readNext() int { + ch, err := sc.reader.ReadByte() + if err == io.EOF { + return EOF + } + return int(ch) +} + +func (sc *Scanner) Newline(ch int) { + if ch < 0 { + return + } + sc.Pos.Line += 1 + sc.Pos.Column = 0 + next := sc.Peek() + if ch == '\n' && next == '\r' || ch == '\r' && next == '\n' { + sc.reader.ReadByte() + } +} + +func (sc *Scanner) Next() int { + ch := sc.readNext() + switch ch { + case '\n', '\r': + sc.Newline(ch) + ch = int('\n') + case EOF: + sc.Pos.Line = EOF + sc.Pos.Column = 0 + default: + sc.Pos.Column++ + } + return ch +} + +func (sc *Scanner) Peek() int { + ch := sc.readNext() + if ch != EOF { + sc.reader.UnreadByte() + } + return ch +} + +func (sc *Scanner) skipWhiteSpace(whitespace int64) int { + ch := sc.Next() + for ; whitespace&(1<': + if sc.Peek() == '=' { + tok.Type = TGte + tok.Str = ">=" + sc.Next() + } else { + tok.Type = ch + tok.Str = string(ch) + } + case '.': + ch2 := sc.Peek() + switch { + case isDecimal(ch2): + tok.Type = TNumber + err = sc.scanNumber(ch, buf) + tok.Str = buf.String() + case ch2 == '.': + writeChar(buf, ch) + writeChar(buf, sc.Next()) + if sc.Peek() == '.' { + writeChar(buf, sc.Next()) + tok.Type = T3Comma + } else { + tok.Type = T2Comma + } + default: + tok.Type = '.' + } + tok.Str = buf.String() + case '+', '*', '/', '%', '^', '#', '(', ')', '{', '}', ']', ';', ':', ',': + tok.Type = ch + tok.Str = string(ch) + default: + writeChar(buf, ch) + err = sc.Error(buf.String(), "Invalid token") + goto finally + } + } + +finally: + tok.Name = TokenName(int(tok.Type)) + return tok, err +} + +// yacc interface {{{ + +type Lexer struct { + scanner *Scanner + Stmts []ast.Stmt + PNewLine bool + Token ast.Token + PrevTokenType int +} + +func (lx *Lexer) Lex(lval *yySymType) int { + lx.PrevTokenType = lx.Token.Type + tok, err := lx.scanner.Scan(lx) + if err != nil { + panic(err) + } + if tok.Type < 0 { + return 0 + } + lval.token = tok + lx.Token = tok + return int(tok.Type) +} + +func (lx *Lexer) Error(message string) { + panic(lx.scanner.Error(lx.Token.Str, message)) +} + +func (lx *Lexer) TokenError(tok ast.Token, message string) { + panic(lx.scanner.TokenError(tok, message)) +} + +func Parse(reader io.Reader, name string) (chunk []ast.Stmt, err error) { + lexer := &Lexer{NewScanner(reader, name), nil, false, ast.Token{Str: ""}, TNil} + chunk = nil + defer func() { + if e := recover(); e != nil { + err, _ = e.(error) + } + }() + yyParse(lexer) + chunk = lexer.Stmts + return +} + +// }}} + +// Dump {{{ + +func isInlineDumpNode(rv reflect.Value) bool { + switch rv.Kind() { + case reflect.Struct, reflect.Slice, reflect.Interface, reflect.Ptr: + return false + default: + return true + } +} + +func dump(node interface{}, level int, s string) string { + rt := reflect.TypeOf(node) + if fmt.Sprint(rt) == "" { + return strings.Repeat(s, level) + "" + } + + rv := reflect.ValueOf(node) + buf := []string{} + switch rt.Kind() { + case reflect.Slice: + if rv.Len() == 0 { + return strings.Repeat(s, level) + "" + } + for i := 0; i < rv.Len(); i++ { + buf = append(buf, dump(rv.Index(i).Interface(), level, s)) + } + case reflect.Ptr: + vt := rv.Elem() + tt := rt.Elem() + indicies := []int{} + for i := 0; i < tt.NumField(); i++ { + if strings.Index(tt.Field(i).Name, "Base") > -1 { + continue + } + indicies = append(indicies, i) + } + switch { + case len(indicies) == 0: + return strings.Repeat(s, level) + "" + case len(indicies) == 1 && isInlineDumpNode(vt.Field(indicies[0])): + for _, i := range indicies { + buf = append(buf, strings.Repeat(s, level)+"- Node$"+tt.Name()+": "+dump(vt.Field(i).Interface(), 0, s)) + } + default: + buf = append(buf, strings.Repeat(s, level)+"- Node$"+tt.Name()) + for _, i := range indicies { + if isInlineDumpNode(vt.Field(i)) { + inf := dump(vt.Field(i).Interface(), 0, s) + buf = append(buf, strings.Repeat(s, level+1)+tt.Field(i).Name+": "+inf) + } else { + buf = append(buf, strings.Repeat(s, level+1)+tt.Field(i).Name+": ") + buf = append(buf, dump(vt.Field(i).Interface(), level+2, s)) + } + } + } + default: + buf = append(buf, strings.Repeat(s, level)+fmt.Sprint(node)) + } + return strings.Join(buf, "\n") +} + +func Dump(chunk []ast.Stmt) string { + return dump(chunk, 0, " ") +} + +// }} diff --git a/vendor/github.com/yuin/gopher-lua/parse/parser.go b/vendor/github.com/yuin/gopher-lua/parse/parser.go new file mode 100644 index 00000000000..f8f59b36154 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/parse/parser.go @@ -0,0 +1,1137 @@ +//line parser.go.y:2 +package parse + +import __yyfmt__ "fmt" + +//line parser.go.y:2 +import ( + "github.com/yuin/gopher-lua/ast" +) + +//line parser.go.y:34 +type yySymType struct { + yys int + token ast.Token + + stmts []ast.Stmt + stmt ast.Stmt + + funcname *ast.FuncName + funcexpr *ast.FunctionExpr + + exprlist []ast.Expr + expr ast.Expr + + fieldlist []*ast.Field + field *ast.Field + fieldsep string + + namelist []string + parlist *ast.ParList +} + +const TAnd = 57346 +const TBreak = 57347 +const TDo = 57348 +const TElse = 57349 +const TElseIf = 57350 +const TEnd = 57351 +const TFalse = 57352 +const TFor = 57353 +const TFunction = 57354 +const TIf = 57355 +const TIn = 57356 +const TLocal = 57357 +const TNil = 57358 +const TNot = 57359 +const TOr = 57360 +const TReturn = 57361 +const TRepeat = 57362 +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{ + "TAnd", + "TBreak", + "TDo", + "TElse", + "TElseIf", + "TEnd", + "TFalse", + "TFor", + "TFunction", + "TIf", + "TIn", + "TLocal", + "TNil", + "TNot", + "TOr", + "TReturn", + "TRepeat", + "TThen", + "TTrue", + "TUntil", + "TWhile", + "TEqeq", + "TNeq", + "TLte", + "TGte", + "T2Comma", + "T3Comma", + "TIdent", + "TNumber", + "TString", + " {", + " (", + " >", + " <", + " +", + " -", + " *", + " /", + " %", + "UNARY", + " ^", +} +var yyStatenames = []string{} + +const yyEofCode = 1 +const yyErrCode = 2 +const yyMaxDepth = 200 + +//line parser.go.y:514 +func TokenName(c int) string { + if c >= TAnd && c-TAnd < len(yyToknames) { + if yyToknames[c-TAnd] != "" { + return yyToknames[c-TAnd] + } + } + return string([]byte{byte(c)}) +} + +//line yacctab:1 +var yyExca = []int{ + -1, 1, + 1, -1, + -2, 0, + -1, 17, + 46, 31, + 47, 31, + -2, 68, + -1, 93, + 46, 32, + 47, 32, + -2, 68, +} + +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, +} +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 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 yyR1 = []int{ + + 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, + 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, +} +var yyR2 = []int{ + + 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, +} +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, + -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, +} +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, + 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, +} +var yyTok1 = []int{ + + 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, 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, 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, +} +var yyTok2 = []int{ + + 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, +} +var yyTok3 = []int{ + 0, +} + +//line yaccpar:1 + +/* parser for yacc output */ + +var yyDebug = 0 + +type yyLexer interface { + Lex(lval *yySymType) int + Error(s string) +} + +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] + } + } + return __yyfmt__.Sprintf("tok-%v", c) +} + +func yyStatname(s int) string { + if s >= 0 && s < len(yyStatenames) { + if yyStatenames[s] != "" { + return yyStatenames[s] + } + } + return __yyfmt__.Sprintf("state-%v", s) +} + +func yylex1(lex yyLexer, lval *yySymType) int { + c := 0 + char := lex.Lex(lval) + if char <= 0 { + c = yyTok1[0] + goto out + } + if char < len(yyTok1) { + c = yyTok1[char] + goto out + } + if char >= yyPrivate { + if char < yyPrivate+len(yyTok2) { + c = yyTok2[char-yyPrivate] + goto out + } + } + for i := 0; i < len(yyTok3); i += 2 { + c = yyTok3[i+0] + if c == char { + c = yyTok3[i+1] + goto out + } + } + +out: + if c == 0 { + c = yyTok2[1] /* unknown char */ + } + if yyDebug >= 3 { + __yyfmt__.Printf("lex %s(%d)\n", yyTokname(c), uint(char)) + } + return c +} + +func yyParse(yylex yyLexer) int { + var yyn int + var yylval yySymType + var yyVAL yySymType + yyS := make([]yySymType, yyMaxDepth) + + Nerrs := 0 /* number of errors */ + Errflag := 0 /* error recovery flag */ + yystate := 0 + yychar := -1 + yyp := -1 + goto yystack + +ret0: + return 0 + +ret1: + return 1 + +yystack: + /* put a state and value onto the stack */ + if yyDebug >= 4 { + __yyfmt__.Printf("char %v in %v\n", yyTokname(yychar), yyStatname(yystate)) + } + + yyp++ + if yyp >= len(yyS) { + nyys := make([]yySymType, len(yyS)*2) + copy(nyys, yyS) + yyS = nyys + } + yyS[yyp] = yyVAL + yyS[yyp].yys = yystate + +yynewstate: + yyn = yyPact[yystate] + if yyn <= yyFlag { + goto yydefault /* simple state */ + } + if yychar < 0 { + yychar = yylex1(yylex, &yylval) + } + yyn += yychar + if yyn < 0 || yyn >= yyLast { + goto yydefault + } + yyn = yyAct[yyn] + if yyChk[yyn] == yychar { /* valid shift */ + yychar = -1 + yyVAL = yylval + yystate = yyn + if Errflag > 0 { + Errflag-- + } + goto yystack + } + +yydefault: + /* default state action */ + yyn = yyDef[yystate] + if yyn == -2 { + if yychar < 0 { + yychar = yylex1(yylex, &yylval) + } + + /* look through exception table */ + xi := 0 + for { + if yyExca[xi+0] == -1 && yyExca[xi+1] == yystate { + break + } + xi += 2 + } + for xi += 2; ; xi += 2 { + yyn = yyExca[xi+0] + if yyn < 0 || yyn == yychar { + break + } + } + yyn = yyExca[xi+1] + if yyn < 0 { + goto ret0 + } + } + if yyn == 0 { + /* error ... attempt to resume parsing */ + switch Errflag { + case 0: /* brand new error */ + yylex.Error("syntax error") + Nerrs++ + if yyDebug >= 1 { + __yyfmt__.Printf("%s", yyStatname(yystate)) + __yyfmt__.Printf(" saw %s\n", yyTokname(yychar)) + } + fallthrough + + case 1, 2: /* incompletely recovered error ... try again */ + Errflag = 3 + + /* find a state where "error" is a legal shift action */ + for yyp >= 0 { + yyn = yyPact[yyS[yyp].yys] + yyErrCode + if yyn >= 0 && yyn < yyLast { + yystate = yyAct[yyn] /* simulate a shift of "error" */ + if yyChk[yystate] == yyErrCode { + goto yystack + } + } + + /* the current p has no shift on "error", pop stack */ + if yyDebug >= 2 { + __yyfmt__.Printf("error recovery pops state %d\n", yyS[yyp].yys) + } + yyp-- + } + /* there is no state on the stack with an error shift ... abort */ + goto ret1 + + case 3: /* no shift yet; clobber input char */ + if yyDebug >= 2 { + __yyfmt__.Printf("error recovery discards %s\n", yyTokname(yychar)) + } + if yychar == yyEofCode { + goto ret1 + } + yychar = -1 + goto yynewstate /* try again in the same state */ + } + } + + /* reduction by production yyn */ + if yyDebug >= 2 { + __yyfmt__.Printf("reduce %v in:\n\t%v\n", yyn, yyStatname(yystate)) + } + + yynt := yyn + yypt := yyp + _ = yypt // guard against "declared and not used" + + yyp -= yyR2[yyn] + yyVAL = yyS[yyp+1] + + /* consult goto table to find next state */ + yyn = yyR1[yyn] + yyg := yyPgo[yyn] + yyj := yyg + yyS[yyp].yys + 1 + + if yyj >= yyLast { + yystate = yyAct[yyg] + } else { + yystate = yyAct[yyj] + if yyChk[yystate] != -yyn { + yystate = yyAct[yyg] + } + } + // dummy call; replaced with literal code + switch yynt { + + case 1: + //line parser.go.y:73 + { + yyVAL.stmts = yyS[yypt-0].stmts + if l, ok := yylex.(*Lexer); ok { + l.Stmts = yyVAL.stmts + } + } + case 2: + //line parser.go.y:79 + { + yyVAL.stmts = append(yyS[yypt-1].stmts, yyS[yypt-0].stmt) + if l, ok := yylex.(*Lexer); ok { + l.Stmts = yyVAL.stmts + } + } + case 3: + //line parser.go.y:85 + { + yyVAL.stmts = append(yyS[yypt-2].stmts, yyS[yypt-1].stmt) + if l, ok := yylex.(*Lexer); ok { + l.Stmts = yyVAL.stmts + } + } + case 4: + //line parser.go.y:93 + { + yyVAL.stmts = []ast.Stmt{} + } + case 5: + //line parser.go.y:96 + { + yyVAL.stmts = append(yyS[yypt-1].stmts, yyS[yypt-0].stmt) + } + case 6: + //line parser.go.y:99 + { + yyVAL.stmts = yyS[yypt-1].stmts + } + case 7: + //line parser.go.y:104 + { + yyVAL.stmts = yyS[yypt-0].stmts + } + case 8: + //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()) + } + case 9: + //line parser.go.y:114 + { + if _, ok := yyS[yypt-0].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()) + } + } + case 10: + //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) + } + case 11: + //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) + } + case 12: + //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()) + } + case 13: + //line parser.go.y:137 + { + yyVAL.stmt = &ast.IfStmt{Condition: yyS[yypt-4].expr, Then: yyS[yypt-2].stmts} + cur := yyVAL.stmt + for _, elseif := range yyS[yypt-1].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) + } + case 14: + //line parser.go.y:147 + { + yyVAL.stmt = &ast.IfStmt{Condition: yyS[yypt-6].expr, Then: yyS[yypt-4].stmts} + cur := yyVAL.stmt + for _, elseif := range yyS[yypt-3].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) + } + case 15: + //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) + } + case 16: + //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) + } + case 17: + //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) + } + case 18: + //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()) + } + case 19: + //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()) + } + case 20: + //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) + } + case 21: + //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) + } + case 22: + //line parser.go.y:193 + { + yyVAL.stmts = []ast.Stmt{} + } + case 23: + //line parser.go.y:196 + { + 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) + } + case 24: + //line parser.go.y:202 + { + yyVAL.stmt = &ast.ReturnStmt{Exprs: nil} + yyVAL.stmt.SetLine(yyS[yypt-0].token.Pos.Line) + } + case 25: + //line parser.go.y:206 + { + yyVAL.stmt = &ast.ReturnStmt{Exprs: yyS[yypt-0].exprlist} + yyVAL.stmt.SetLine(yyS[yypt-1].token.Pos.Line) + } + case 26: + //line parser.go.y:210 + { + yyVAL.stmt = &ast.BreakStmt{} + yyVAL.stmt.SetLine(yyS[yypt-0].token.Pos.Line) + } + case 27: + //line parser.go.y:216 + { + yyVAL.funcname = yyS[yypt-0].funcname + } + case 28: + //line parser.go.y:219 + { + yyVAL.funcname = &ast.FuncName{Func: nil, Receiver: yyS[yypt-2].funcname.Func, Method: yyS[yypt-0].token.Str} + } + case 29: + //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) + } + case 30: + //line parser.go.y:228 + { + 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} + } + case 31: + //line parser.go.y:237 + { + yyVAL.exprlist = []ast.Expr{yyS[yypt-0].expr} + } + case 32: + //line parser.go.y:240 + { + yyVAL.exprlist = append(yyS[yypt-2].exprlist, yyS[yypt-0].expr) + } + case 33: + //line parser.go.y:245 + { + yyVAL.expr = &ast.IdentExpr{Value: yyS[yypt-0].token.Str} + yyVAL.expr.SetLine(yyS[yypt-0].token.Pos.Line) + } + case 34: + //line parser.go.y:249 + { + yyVAL.expr = &ast.AttrGetExpr{Object: yyS[yypt-3].expr, Key: yyS[yypt-1].expr} + yyVAL.expr.SetLine(yyS[yypt-3].expr.Line()) + } + case 35: + //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()) + } + case 36: + //line parser.go.y:261 + { + yyVAL.namelist = []string{yyS[yypt-0].token.Str} + } + case 37: + //line parser.go.y:264 + { + yyVAL.namelist = append(yyS[yypt-2].namelist, yyS[yypt-0].token.Str) + } + case 38: + //line parser.go.y:269 + { + yyVAL.exprlist = []ast.Expr{yyS[yypt-0].expr} + } + case 39: + //line parser.go.y:272 + { + yyVAL.exprlist = append(yyS[yypt-2].exprlist, yyS[yypt-0].expr) + } + case 40: + //line parser.go.y:277 + { + yyVAL.expr = &ast.NilExpr{} + yyVAL.expr.SetLine(yyS[yypt-0].token.Pos.Line) + } + case 41: + //line parser.go.y:281 + { + yyVAL.expr = &ast.FalseExpr{} + yyVAL.expr.SetLine(yyS[yypt-0].token.Pos.Line) + } + case 42: + //line parser.go.y:285 + { + yyVAL.expr = &ast.TrueExpr{} + yyVAL.expr.SetLine(yyS[yypt-0].token.Pos.Line) + } + case 43: + //line parser.go.y:289 + { + yyVAL.expr = &ast.NumberExpr{Value: yyS[yypt-0].token.Str} + yyVAL.expr.SetLine(yyS[yypt-0].token.Pos.Line) + } + case 44: + //line parser.go.y:293 + { + yyVAL.expr = &ast.Comma3Expr{} + yyVAL.expr.SetLine(yyS[yypt-0].token.Pos.Line) + } + case 45: + //line parser.go.y:297 + { + yyVAL.expr = yyS[yypt-0].expr + } + case 46: + //line parser.go.y:300 + { + yyVAL.expr = yyS[yypt-0].expr + } + case 47: + //line parser.go.y:303 + { + yyVAL.expr = yyS[yypt-0].expr + } + case 48: + //line parser.go.y:306 + { + yyVAL.expr = yyS[yypt-0].expr + } + case 49: + //line parser.go.y:309 + { + yyVAL.expr = &ast.LogicalOpExpr{Lhs: yyS[yypt-2].expr, Operator: "or", Rhs: yyS[yypt-0].expr} + yyVAL.expr.SetLine(yyS[yypt-2].expr.Line()) + } + case 50: + //line parser.go.y:313 + { + yyVAL.expr = &ast.LogicalOpExpr{Lhs: yyS[yypt-2].expr, Operator: "and", Rhs: yyS[yypt-0].expr} + yyVAL.expr.SetLine(yyS[yypt-2].expr.Line()) + } + case 51: + //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()) + } + case 52: + //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()) + } + case 53: + //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()) + } + case 54: + //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()) + } + case 55: + //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()) + } + case 56: + //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()) + } + case 57: + //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()) + } + case 58: + //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()) + } + case 59: + //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()) + } + case 60: + //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()) + } + case 61: + //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()) + } + case 62: + //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()) + } + case 63: + //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()) + } + case 64: + //line parser.go.y:369 + { + yyVAL.expr = &ast.UnaryMinusOpExpr{Expr: yyS[yypt-0].expr} + yyVAL.expr.SetLine(yyS[yypt-0].expr.Line()) + } + case 65: + //line parser.go.y:373 + { + yyVAL.expr = &ast.UnaryNotOpExpr{Expr: yyS[yypt-0].expr} + yyVAL.expr.SetLine(yyS[yypt-0].expr.Line()) + } + case 66: + //line parser.go.y:377 + { + yyVAL.expr = &ast.UnaryLenOpExpr{Expr: yyS[yypt-0].expr} + yyVAL.expr.SetLine(yyS[yypt-0].expr.Line()) + } + case 67: + //line parser.go.y:383 + { + yyVAL.expr = &ast.StringExpr{Value: yyS[yypt-0].token.Str} + yyVAL.expr.SetLine(yyS[yypt-0].token.Pos.Line) + } + case 68: + //line parser.go.y:389 + { + yyVAL.expr = yyS[yypt-0].expr + } + case 69: + //line parser.go.y:392 + { + yyVAL.expr = yyS[yypt-0].expr + } + case 70: + //line parser.go.y:395 + { + yyVAL.expr = yyS[yypt-0].expr + } + case 71: + //line parser.go.y:398 + { + yyVAL.expr = yyS[yypt-1].expr + yyVAL.expr.SetLine(yyS[yypt-2].token.Pos.Line) + } + case 72: + //line parser.go.y:404 + { + yyS[yypt-1].expr.(*ast.FuncCallExpr).AdjustRet = true + yyVAL.expr = yyS[yypt-1].expr + } + case 73: + //line parser.go.y:410 + { + yyVAL.expr = &ast.FuncCallExpr{Func: yyS[yypt-1].expr, Args: yyS[yypt-0].exprlist} + yyVAL.expr.SetLine(yyS[yypt-1].expr.Line()) + } + case 74: + //line parser.go.y:414 + { + 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()) + } + case 75: + //line parser.go.y:420 + { + if yylex.(*Lexer).PNewLine { + yylex.(*Lexer).TokenError(yyS[yypt-1].token, "ambiguous syntax (function call x new statement)") + } + yyVAL.exprlist = []ast.Expr{} + } + case 76: + //line parser.go.y:426 + { + if yylex.(*Lexer).PNewLine { + yylex.(*Lexer).TokenError(yyS[yypt-2].token, "ambiguous syntax (function call x new statement)") + } + yyVAL.exprlist = yyS[yypt-1].exprlist + } + case 77: + //line parser.go.y:432 + { + yyVAL.exprlist = []ast.Expr{yyS[yypt-0].expr} + } + case 78: + //line parser.go.y:435 + { + yyVAL.exprlist = []ast.Expr{yyS[yypt-0].expr} + } + case 79: + //line parser.go.y:440 + { + 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()) + } + case 80: + //line parser.go.y:447 + { + 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) + } + case 81: + //line parser.go.y:452 + { + 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) + } + case 82: + //line parser.go.y:459 + { + yyVAL.parlist = &ast.ParList{HasVargs: true, Names: []string{}} + } + case 83: + //line parser.go.y:462 + { + yyVAL.parlist = &ast.ParList{HasVargs: false, Names: []string{}} + yyVAL.parlist.Names = append(yyVAL.parlist.Names, yyS[yypt-0].namelist...) + } + case 84: + //line parser.go.y:466 + { + 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 + { + yyVAL.expr = &ast.TableExpr{Fields: []*ast.Field{}} + yyVAL.expr.SetLine(yyS[yypt-1].token.Pos.Line) + } + case 86: + //line parser.go.y:477 + { + yyVAL.expr = &ast.TableExpr{Fields: yyS[yypt-1].fieldlist} + yyVAL.expr.SetLine(yyS[yypt-2].token.Pos.Line) + } + case 87: + //line parser.go.y:484 + { + yyVAL.fieldlist = []*ast.Field{yyS[yypt-0].field} + } + case 88: + //line parser.go.y:487 + { + yyVAL.fieldlist = append(yyS[yypt-2].fieldlist, yyS[yypt-0].field) + } + case 89: + //line parser.go.y:490 + { + yyVAL.fieldlist = yyS[yypt-1].fieldlist + } + case 90: + //line parser.go.y:495 + { + 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) + } + case 91: + //line parser.go.y:499 + { + yyVAL.field = &ast.Field{Key: yyS[yypt-3].expr, Value: yyS[yypt-0].expr} + } + case 92: + //line parser.go.y:502 + { + yyVAL.field = &ast.Field{Value: yyS[yypt-0].expr} + } + case 93: + //line parser.go.y:507 + { + yyVAL.fieldsep = "," + } + case 94: + //line parser.go.y:510 + { + yyVAL.fieldsep = ";" + } + } + goto yystack /* stack new state and value */ +} diff --git a/vendor/github.com/yuin/gopher-lua/parse/parser.go.y b/vendor/github.com/yuin/gopher-lua/parse/parser.go.y new file mode 100644 index 00000000000..956133db292 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/parse/parser.go.y @@ -0,0 +1,524 @@ +%{ +package parse + +import ( + "github.com/yuin/gopher-lua/ast" +) +%} +%type chunk +%type chunk1 +%type block +%type stat +%type elseifs +%type laststat +%type funcname +%type funcname1 +%type varlist +%type var +%type namelist +%type exprlist +%type expr +%type string +%type prefixexp +%type functioncall +%type afunctioncall +%type args +%type function +%type funcbody +%type parlist +%type tableconstructor +%type fieldlist +%type field +%type fieldsep + +%union { + token ast.Token + + stmts []ast.Stmt + stmt ast.Stmt + + funcname *ast.FuncName + funcexpr *ast.FunctionExpr + + exprlist []ast.Expr + expr ast.Expr + + fieldlist []*ast.Field + field *ast.Field + fieldsep string + + namelist []string + parlist *ast.ParList +} + +/* Reserved words */ +%token TAnd TBreak TDo TElse TElseIf TEnd TFalse TFor TFunction TIf TIn TLocal TNil TNot TOr TReturn TRepeat TThen TTrue TUntil TWhile + +/* Literals */ +%token TEqeq TNeq TLte TGte T2Comma T3Comma TIdent TNumber TString '{' '(' + +/* Operators */ +%left TOr +%left TAnd +%left '>' '<' TGte TLte TEqeq TNeq +%right T2Comma +%left '+' '-' +%left '*' '/' '%' +%right UNARY /* not # -(unary) */ +%right '^' + +%% + +chunk: + chunk1 { + $$ = $1 + if l, ok := yylex.(*Lexer); ok { + l.Stmts = $$ + } + } | + chunk1 laststat { + $$ = append($1, $2) + if l, ok := yylex.(*Lexer); ok { + l.Stmts = $$ + } + } | + chunk1 laststat ';' { + $$ = append($1, $2) + if l, ok := yylex.(*Lexer); ok { + l.Stmts = $$ + } + } + +chunk1: + { + $$ = []ast.Stmt{} + } | + chunk1 stat { + $$ = append($1, $2) + } | + chunk1 ';' { + $$ = $1 + } + +block: + chunk { + $$ = $1 + } + +stat: + varlist '=' exprlist { + $$ = &ast.AssignStmt{Lhs: $1, Rhs: $3} + $$.SetLine($1[0].Line()) + } | + /* 'stat = functioncal' causes a reduce/reduce conflict */ + prefixexp { + if _, ok := $1.(*ast.FuncCallExpr); !ok { + yylex.(*Lexer).Error("parse error") + } else { + $$ = &ast.FuncCallStmt{Expr: $1} + $$.SetLine($1.Line()) + } + } | + TDo block TEnd { + $$ = &ast.DoBlockStmt{Stmts: $2} + $$.SetLine($1.Pos.Line) + $$.SetLastLine($3.Pos.Line) + } | + TWhile expr TDo block TEnd { + $$ = &ast.WhileStmt{Condition: $2, Stmts: $4} + $$.SetLine($1.Pos.Line) + $$.SetLastLine($5.Pos.Line) + } | + TRepeat block TUntil expr { + $$ = &ast.RepeatStmt{Condition: $4, Stmts: $2} + $$.SetLine($1.Pos.Line) + $$.SetLastLine($4.Line()) + } | + TIf expr TThen block elseifs TEnd { + $$ = &ast.IfStmt{Condition: $2, Then: $4} + cur := $$ + for _, elseif := range $5 { + cur.(*ast.IfStmt).Else = []ast.Stmt{elseif} + cur = elseif + } + $$.SetLine($1.Pos.Line) + $$.SetLastLine($6.Pos.Line) + } | + TIf expr TThen block elseifs TElse block TEnd { + $$ = &ast.IfStmt{Condition: $2, Then: $4} + cur := $$ + for _, elseif := range $5 { + cur.(*ast.IfStmt).Else = []ast.Stmt{elseif} + cur = elseif + } + cur.(*ast.IfStmt).Else = $7 + $$.SetLine($1.Pos.Line) + $$.SetLastLine($8.Pos.Line) + } | + TFor TIdent '=' expr ',' expr TDo block TEnd { + $$ = &ast.NumberForStmt{Name: $2.Str, Init: $4, Limit: $6, Stmts: $8} + $$.SetLine($1.Pos.Line) + $$.SetLastLine($9.Pos.Line) + } | + TFor TIdent '=' expr ',' expr ',' expr TDo block TEnd { + $$ = &ast.NumberForStmt{Name: $2.Str, Init: $4, Limit: $6, Step:$8, Stmts: $10} + $$.SetLine($1.Pos.Line) + $$.SetLastLine($11.Pos.Line) + } | + TFor namelist TIn exprlist TDo block TEnd { + $$ = &ast.GenericForStmt{Names:$2, Exprs:$4, Stmts: $6} + $$.SetLine($1.Pos.Line) + $$.SetLastLine($7.Pos.Line) + } | + TFunction funcname funcbody { + $$ = &ast.FuncDefStmt{Name: $2, Func: $3} + $$.SetLine($1.Pos.Line) + $$.SetLastLine($3.LastLine()) + } | + TLocal TFunction TIdent funcbody { + $$ = &ast.LocalAssignStmt{Names:[]string{$3.Str}, Exprs: []ast.Expr{$4}} + $$.SetLine($1.Pos.Line) + $$.SetLastLine($4.LastLine()) + } | + TLocal namelist '=' exprlist { + $$ = &ast.LocalAssignStmt{Names: $2, Exprs:$4} + $$.SetLine($1.Pos.Line) + } | + TLocal namelist { + $$ = &ast.LocalAssignStmt{Names: $2, Exprs:[]ast.Expr{}} + $$.SetLine($1.Pos.Line) + } + +elseifs: + { + $$ = []ast.Stmt{} + } | + elseifs TElseIf expr TThen block { + $$ = append($1, &ast.IfStmt{Condition: $3, Then: $5}) + $$[len($$)-1].SetLine($2.Pos.Line) + } + +laststat: + TReturn { + $$ = &ast.ReturnStmt{Exprs:nil} + $$.SetLine($1.Pos.Line) + } | + TReturn exprlist { + $$ = &ast.ReturnStmt{Exprs:$2} + $$.SetLine($1.Pos.Line) + } | + TBreak { + $$ = &ast.BreakStmt{} + $$.SetLine($1.Pos.Line) + } + +funcname: + funcname1 { + $$ = $1 + } | + funcname1 ':' TIdent { + $$ = &ast.FuncName{Func:nil, Receiver:$1.Func, Method: $3.Str} + } + +funcname1: + TIdent { + $$ = &ast.FuncName{Func: &ast.IdentExpr{Value:$1.Str}} + $$.Func.SetLine($1.Pos.Line) + } | + funcname1 '.' TIdent { + key:= &ast.StringExpr{Value:$3.Str} + key.SetLine($3.Pos.Line) + fn := &ast.AttrGetExpr{Object: $1.Func, Key: key} + fn.SetLine($3.Pos.Line) + $$ = &ast.FuncName{Func: fn} + } + +varlist: + var { + $$ = []ast.Expr{$1} + } | + varlist ',' var { + $$ = append($1, $3) + } + +var: + TIdent { + $$ = &ast.IdentExpr{Value:$1.Str} + $$.SetLine($1.Pos.Line) + } | + prefixexp '[' expr ']' { + $$ = &ast.AttrGetExpr{Object: $1, Key: $3} + $$.SetLine($1.Line()) + } | + prefixexp '.' TIdent { + key := &ast.StringExpr{Value:$3.Str} + key.SetLine($3.Pos.Line) + $$ = &ast.AttrGetExpr{Object: $1, Key: key} + $$.SetLine($1.Line()) + } + +namelist: + TIdent { + $$ = []string{$1.Str} + } | + namelist ',' TIdent { + $$ = append($1, $3.Str) + } + +exprlist: + expr { + $$ = []ast.Expr{$1} + } | + exprlist ',' expr { + $$ = append($1, $3) + } + +expr: + TNil { + $$ = &ast.NilExpr{} + $$.SetLine($1.Pos.Line) + } | + TFalse { + $$ = &ast.FalseExpr{} + $$.SetLine($1.Pos.Line) + } | + TTrue { + $$ = &ast.TrueExpr{} + $$.SetLine($1.Pos.Line) + } | + TNumber { + $$ = &ast.NumberExpr{Value: $1.Str} + $$.SetLine($1.Pos.Line) + } | + T3Comma { + $$ = &ast.Comma3Expr{} + $$.SetLine($1.Pos.Line) + } | + function { + $$ = $1 + } | + prefixexp { + $$ = $1 + } | + string { + $$ = $1 + } | + tableconstructor { + $$ = $1 + } | + expr TOr expr { + $$ = &ast.LogicalOpExpr{Lhs: $1, Operator: "or", Rhs: $3} + $$.SetLine($1.Line()) + } | + expr TAnd expr { + $$ = &ast.LogicalOpExpr{Lhs: $1, Operator: "and", Rhs: $3} + $$.SetLine($1.Line()) + } | + expr '>' expr { + $$ = &ast.RelationalOpExpr{Lhs: $1, Operator: ">", Rhs: $3} + $$.SetLine($1.Line()) + } | + expr '<' expr { + $$ = &ast.RelationalOpExpr{Lhs: $1, Operator: "<", Rhs: $3} + $$.SetLine($1.Line()) + } | + expr TGte expr { + $$ = &ast.RelationalOpExpr{Lhs: $1, Operator: ">=", Rhs: $3} + $$.SetLine($1.Line()) + } | + expr TLte expr { + $$ = &ast.RelationalOpExpr{Lhs: $1, Operator: "<=", Rhs: $3} + $$.SetLine($1.Line()) + } | + expr TEqeq expr { + $$ = &ast.RelationalOpExpr{Lhs: $1, Operator: "==", Rhs: $3} + $$.SetLine($1.Line()) + } | + expr TNeq expr { + $$ = &ast.RelationalOpExpr{Lhs: $1, Operator: "~=", Rhs: $3} + $$.SetLine($1.Line()) + } | + expr T2Comma expr { + $$ = &ast.StringConcatOpExpr{Lhs: $1, Rhs: $3} + $$.SetLine($1.Line()) + } | + expr '+' expr { + $$ = &ast.ArithmeticOpExpr{Lhs: $1, Operator: "+", Rhs: $3} + $$.SetLine($1.Line()) + } | + expr '-' expr { + $$ = &ast.ArithmeticOpExpr{Lhs: $1, Operator: "-", Rhs: $3} + $$.SetLine($1.Line()) + } | + expr '*' expr { + $$ = &ast.ArithmeticOpExpr{Lhs: $1, Operator: "*", Rhs: $3} + $$.SetLine($1.Line()) + } | + expr '/' expr { + $$ = &ast.ArithmeticOpExpr{Lhs: $1, Operator: "/", Rhs: $3} + $$.SetLine($1.Line()) + } | + expr '%' expr { + $$ = &ast.ArithmeticOpExpr{Lhs: $1, Operator: "%", Rhs: $3} + $$.SetLine($1.Line()) + } | + expr '^' expr { + $$ = &ast.ArithmeticOpExpr{Lhs: $1, Operator: "^", Rhs: $3} + $$.SetLine($1.Line()) + } | + '-' expr %prec UNARY { + $$ = &ast.UnaryMinusOpExpr{Expr: $2} + $$.SetLine($2.Line()) + } | + TNot expr %prec UNARY { + $$ = &ast.UnaryNotOpExpr{Expr: $2} + $$.SetLine($2.Line()) + } | + '#' expr %prec UNARY { + $$ = &ast.UnaryLenOpExpr{Expr: $2} + $$.SetLine($2.Line()) + } + +string: + TString { + $$ = &ast.StringExpr{Value: $1.Str} + $$.SetLine($1.Pos.Line) + } + +prefixexp: + var { + $$ = $1 + } | + afunctioncall { + $$ = $1 + } | + functioncall { + $$ = $1 + } | + '(' expr ')' { + $$ = $2 + $$.SetLine($1.Pos.Line) + } + +afunctioncall: + '(' functioncall ')' { + $2.(*ast.FuncCallExpr).AdjustRet = true + $$ = $2 + } + +functioncall: + prefixexp args { + $$ = &ast.FuncCallExpr{Func: $1, Args: $2} + $$.SetLine($1.Line()) + } | + prefixexp ':' TIdent args { + $$ = &ast.FuncCallExpr{Method: $3.Str, Receiver: $1, Args: $4} + $$.SetLine($1.Line()) + } + +args: + '(' ')' { + if yylex.(*Lexer).PNewLine { + yylex.(*Lexer).TokenError($1, "ambiguous syntax (function call x new statement)") + } + $$ = []ast.Expr{} + } | + '(' exprlist ')' { + if yylex.(*Lexer).PNewLine { + yylex.(*Lexer).TokenError($1, "ambiguous syntax (function call x new statement)") + } + $$ = $2 + } | + tableconstructor { + $$ = []ast.Expr{$1} + } | + string { + $$ = []ast.Expr{$1} + } + +function: + TFunction funcbody { + $$ = &ast.FunctionExpr{ParList:$2.ParList, Stmts: $2.Stmts} + $$.SetLine($1.Pos.Line) + $$.SetLastLine($2.LastLine()) + } + +funcbody: + '(' parlist ')' block TEnd { + $$ = &ast.FunctionExpr{ParList: $2, Stmts: $4} + $$.SetLine($1.Pos.Line) + $$.SetLastLine($5.Pos.Line) + } | + '(' ')' block TEnd { + $$ = &ast.FunctionExpr{ParList: &ast.ParList{HasVargs: false, Names: []string{}}, Stmts: $3} + $$.SetLine($1.Pos.Line) + $$.SetLastLine($4.Pos.Line) + } + +parlist: + T3Comma { + $$ = &ast.ParList{HasVargs: true, Names: []string{}} + } | + namelist { + $$ = &ast.ParList{HasVargs: false, Names: []string{}} + $$.Names = append($$.Names, $1...) + } | + namelist ',' T3Comma { + $$ = &ast.ParList{HasVargs: true, Names: []string{}} + $$.Names = append($$.Names, $1...) + } + + +tableconstructor: + '{' '}' { + $$ = &ast.TableExpr{Fields: []*ast.Field{}} + $$.SetLine($1.Pos.Line) + } | + '{' fieldlist '}' { + $$ = &ast.TableExpr{Fields: $2} + $$.SetLine($1.Pos.Line) + } + + +fieldlist: + field { + $$ = []*ast.Field{$1} + } | + fieldlist fieldsep field { + $$ = append($1, $3) + } | + fieldlist fieldsep { + $$ = $1 + } + +field: + TIdent '=' expr { + $$ = &ast.Field{Key: &ast.StringExpr{Value:$1.Str}, Value: $3} + $$.Key.SetLine($1.Pos.Line) + } | + '[' expr ']' '=' expr { + $$ = &ast.Field{Key: $2, Value: $5} + } | + expr { + $$ = &ast.Field{Value: $1} + } + +fieldsep: + ',' { + $$ = "," + } | + ';' { + $$ = ";" + } + +%% + +func TokenName(c int) string { + if c >= TAnd && c-TAnd < len(yyToknames) { + if yyToknames[c-TAnd] != "" { + return yyToknames[c-TAnd] + } + } + return string([]byte{byte(c)}) +} + diff --git a/vendor/github.com/yuin/gopher-lua/pm/pm.go b/vendor/github.com/yuin/gopher-lua/pm/pm.go new file mode 100644 index 00000000000..e15bc21005d --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/pm/pm.go @@ -0,0 +1,638 @@ +// Lua pattern match functions for Go +package pm + +import ( + "fmt" +) + +const EOS = -1 +const _UNKNOWN = -2 + +/* Error {{{ */ + +type Error struct { + Pos int + Message string +} + +func newError(pos int, message string, args ...interface{}) *Error { + if len(args) == 0 { + return &Error{pos, message} + } + return &Error{pos, fmt.Sprintf(message, args...)} +} + +func (e *Error) Error() string { + switch e.Pos { + case EOS: + return fmt.Sprintf("%s at EOS", e.Message) + case _UNKNOWN: + return fmt.Sprintf("%s", e.Message) + default: + return fmt.Sprintf("%s at %d", e.Message, e.Pos) + } +} + +/* }}} */ + +/* MatchData {{{ */ + +type MatchData struct { + // captured positions + // layout + // xxxx xxxx xxxx xxx0 : caputured positions + // xxxx xxxx xxxx xxx1 : position captured positions + captures []uint32 +} + +func newMatchState() *MatchData { return &MatchData{[]uint32{}} } + +func (st *MatchData) addPosCapture(s, pos int) { + for s+1 >= len(st.captures) { + st.captures = append(st.captures, 0) + } + st.captures[s] = (uint32(pos) << 1) | 1 + st.captures[s+1] = (uint32(pos) << 1) | 1 +} + +func (st *MatchData) setCapture(s, pos int) uint32 { + for s >= len(st.captures) { + st.captures = append(st.captures, 0) + } + v := st.captures[s] + st.captures[s] = (uint32(pos) << 1) + return v +} + +func (st *MatchData) restoreCapture(s int, pos uint32) { st.captures[s] = pos } + +func (st *MatchData) CaptureLength() int { return len(st.captures) } + +func (st *MatchData) IsPosCapture(idx int) bool { return (st.captures[idx] & 1) == 1 } + +func (st *MatchData) Capture(idx int) int { return int(st.captures[idx] >> 1) } + +/* }}} */ + +/* scanner {{{ */ + +type scannerState struct { + Pos int + started bool +} + +type scanner struct { + src []byte + State scannerState + saved scannerState +} + +func newScanner(src []byte) *scanner { + return &scanner{ + src: src, + State: scannerState{ + Pos: 0, + started: false, + }, + saved: scannerState{}, + } +} + +func (sc *scanner) Length() int { return len(sc.src) } + +func (sc *scanner) Next() int { + if !sc.State.started { + sc.State.started = true + if len(sc.src) == 0 { + sc.State.Pos = EOS + } + } else { + sc.State.Pos = sc.NextPos() + } + if sc.State.Pos == EOS { + return EOS + } + return int(sc.src[sc.State.Pos]) +} + +func (sc *scanner) CurrentPos() int { + return sc.State.Pos +} + +func (sc *scanner) NextPos() int { + if sc.State.Pos == EOS || sc.State.Pos >= len(sc.src)-1 { + return EOS + } + if !sc.State.started { + return 0 + } else { + return sc.State.Pos + 1 + } +} + +func (sc *scanner) Peek() int { + cureof := sc.State.Pos == EOS + ch := sc.Next() + if !cureof { + if sc.State.Pos == EOS { + sc.State.Pos = len(sc.src) - 1 + } else { + sc.State.Pos-- + if sc.State.Pos < 0 { + sc.State.Pos = 0 + sc.State.started = false + } + } + } + return ch +} + +func (sc *scanner) Save() { sc.saved = sc.State } + +func (sc *scanner) Restore() { sc.State = sc.saved } + +/* }}} */ + +/* bytecode {{{ */ + +type opCode int + +const ( + opChar opCode = iota + opMatch + opTailMatch + opJmp + opSplit + opSave + opPSave + opBrace + opNumber +) + +type inst struct { + OpCode opCode + Class class + Operand1 int + Operand2 int +} + +/* }}} */ + +/* classes {{{ */ + +type class interface { + Matches(ch int) bool +} + +type dotClass struct{} + +func (pn *dotClass) Matches(ch int) bool { return true } + +type charClass struct { + Ch int +} + +func (pn *charClass) Matches(ch int) bool { return pn.Ch == ch } + +type singleClass struct { + Class int +} + +func (pn *singleClass) Matches(ch int) bool { + ret := false + switch pn.Class { + case 'a', 'A': + ret = 'A' <= ch && ch <= 'Z' || 'a' <= ch && ch <= 'z' + case 'c', 'C': + ret = (0x00 <= ch && ch <= 0x1F) || ch == 0x7F + case 'd', 'D': + ret = '0' <= ch && ch <= '9' + 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) + case 's', 'S': + switch ch { + case ' ', '\f', '\n', '\r', '\t', '\v': + ret = true + } + case 'u', 'U': + ret = 'A' <= ch && ch <= 'Z' + case 'w', 'W': + ret = '0' <= ch && ch <= '9' || 'A' <= ch && ch <= 'Z' || 'a' <= ch && ch <= 'z' + case 'x', 'X': + ret = '0' <= ch && ch <= '9' || 'a' <= ch && ch <= 'f' || 'A' <= ch && ch <= 'F' + case 'z', 'Z': + ret = ch == 0 + default: + return ch == pn.Class + } + if 'A' <= pn.Class && pn.Class <= 'Z' { + return !ret + } + return ret +} + +type setClass struct { + IsNot bool + Classes []class +} + +func (pn *setClass) Matches(ch int) bool { + for _, class := range pn.Classes { + if class.Matches(ch) { + return !pn.IsNot + } + } + return pn.IsNot +} + +type rangeClass struct { + Begin class + End class +} + +func (pn *rangeClass) Matches(ch int) bool { + switch begin := pn.Begin.(type) { + case *charClass: + end, ok := pn.End.(*charClass) + if !ok { + return false + } + return begin.Ch <= ch && ch <= end.Ch + } + return false +} + +// }}} + +// patterns {{{ + +type pattern interface{} + +type singlePattern struct { + Class class +} + +type seqPattern struct { + MustHead bool + MustTail bool + Patterns []pattern +} + +type repeatPattern struct { + Type int + Class class +} + +type posCapPattern struct{} + +type capPattern struct { + Pattern pattern +} + +type numberPattern struct { + N int +} + +type bracePattern struct { + Begin int + End int +} + +// }}} + +/* parse {{{ */ + +func parseClass(sc *scanner, allowset bool) class { + ch := sc.Next() + switch ch { + case '%': + return &singleClass{sc.Next()} + case '.': + if allowset { + return &dotClass{} + } + return &charClass{ch} + case '[': + if allowset { + return parseClassSet(sc) + } + return &charClass{ch} + //case '^' '$', '(', ')', ']', '*', '+', '-', '?': + // panic(newError(sc.CurrentPos(), "invalid %c", ch)) + case EOS: + panic(newError(sc.CurrentPos(), "unexpected EOS")) + default: + return &charClass{ch} + } +} + +func parseClassSet(sc *scanner) class { + set := &setClass{false, []class{}} + if sc.Peek() == '^' { + set.IsNot = true + sc.Next() + } + isrange := false + for { + ch := sc.Peek() + switch ch { + // case '[': + // panic(newError(sc.CurrentPos(), "'[' can not be nested")) + case EOS: + panic(newError(sc.CurrentPos(), "unexpected EOS")) + case ']': + if len(set.Classes) > 0 { + sc.Next() + goto exit + } + fallthrough + case '-': + if len(set.Classes) > 0 { + sc.Next() + isrange = true + continue + } + fallthrough + default: + set.Classes = append(set.Classes, parseClass(sc, false)) + } + if isrange { + begin := set.Classes[len(set.Classes)-2] + end := set.Classes[len(set.Classes)-1] + set.Classes = set.Classes[0 : len(set.Classes)-2] + set.Classes = append(set.Classes, &rangeClass{begin, end}) + isrange = false + } + } +exit: + if isrange { + set.Classes = append(set.Classes, &charClass{'-'}) + } + + return set +} + +func parsePattern(sc *scanner, toplevel bool) *seqPattern { + pat := &seqPattern{} + if toplevel { + if sc.Peek() == '^' { + sc.Next() + pat.MustHead = true + } + } + for { + ch := sc.Peek() + switch ch { + case '%': + sc.Save() + sc.Next() + switch sc.Peek() { + case '0': + panic(newError(sc.CurrentPos(), "invalid capture index")) + case '1', '2', '3', '4', '5', '6', '7', '8', '9': + pat.Patterns = append(pat.Patterns, &numberPattern{sc.Next() - 48}) + case 'b': + sc.Next() + pat.Patterns = append(pat.Patterns, &bracePattern{sc.Next(), sc.Next()}) + default: + sc.Restore() + pat.Patterns = append(pat.Patterns, &singlePattern{parseClass(sc, true)}) + } + case '.', '[', ']': + pat.Patterns = append(pat.Patterns, &singlePattern{parseClass(sc, true)}) + //case ']': + // panic(newError(sc.CurrentPos(), "invalid ']'")) + case ')': + if toplevel { + panic(newError(sc.CurrentPos(), "invalid ')'")) + } + return pat + case '(': + sc.Next() + if sc.Peek() == ')' { + sc.Next() + pat.Patterns = append(pat.Patterns, &posCapPattern{}) + } else { + ret := &capPattern{parsePattern(sc, false)} + if sc.Peek() != ')' { + panic(newError(sc.CurrentPos(), "unfinished capture")) + } + sc.Next() + pat.Patterns = append(pat.Patterns, ret) + } + case '*', '+', '-', '?': + sc.Next() + if len(pat.Patterns) > 0 { + spat, ok := pat.Patterns[len(pat.Patterns)-1].(*singlePattern) + if ok { + pat.Patterns = pat.Patterns[0 : len(pat.Patterns)-1] + pat.Patterns = append(pat.Patterns, &repeatPattern{ch, spat.Class}) + continue + } + } + pat.Patterns = append(pat.Patterns, &singlePattern{&charClass{ch}}) + case '$': + if toplevel && (sc.NextPos() == sc.Length()-1 || sc.NextPos() == EOS) { + pat.MustTail = true + } else { + pat.Patterns = append(pat.Patterns, &singlePattern{&charClass{ch}}) + } + sc.Next() + case EOS: + sc.Next() + goto exit + default: + sc.Next() + pat.Patterns = append(pat.Patterns, &singlePattern{&charClass{ch}}) + } + } +exit: + return pat +} + +type iptr struct { + insts []inst + capture int +} + +func compilePattern(p pattern, ps ...*iptr) []inst { + var ptr *iptr + toplevel := false + if len(ps) == 0 { + toplevel = true + ptr = &iptr{[]inst{inst{opSave, nil, 0, -1}}, 2} + } else { + ptr = ps[0] + } + switch pat := p.(type) { + case *singlePattern: + ptr.insts = append(ptr.insts, inst{opChar, pat.Class, -1, -1}) + case *seqPattern: + for _, cp := range pat.Patterns { + compilePattern(cp, ptr) + } + case *repeatPattern: + idx := len(ptr.insts) + switch pat.Type { + case '*': + ptr.insts = append(ptr.insts, + inst{opSplit, nil, idx + 1, idx + 3}, + inst{opChar, pat.Class, -1, -1}, + inst{opJmp, nil, idx, -1}) + case '+': + ptr.insts = append(ptr.insts, + inst{opChar, pat.Class, -1, -1}, + inst{opSplit, nil, idx, idx + 2}) + case '-': + ptr.insts = append(ptr.insts, + inst{opSplit, nil, idx + 3, idx + 1}, + inst{opChar, pat.Class, -1, -1}, + inst{opJmp, nil, idx, -1}) + case '?': + ptr.insts = append(ptr.insts, + inst{opSplit, nil, idx + 1, idx + 2}, + inst{opChar, pat.Class, -1, -1}) + } + case *posCapPattern: + ptr.insts = append(ptr.insts, inst{opPSave, nil, ptr.capture, -1}) + ptr.capture += 2 + case *capPattern: + c0, c1 := ptr.capture, ptr.capture+1 + ptr.capture += 2 + ptr.insts = append(ptr.insts, inst{opSave, nil, c0, -1}) + compilePattern(pat.Pattern, ptr) + ptr.insts = append(ptr.insts, inst{opSave, nil, c1, -1}) + case *bracePattern: + ptr.insts = append(ptr.insts, inst{opBrace, nil, pat.Begin, pat.End}) + case *numberPattern: + ptr.insts = append(ptr.insts, inst{opNumber, nil, pat.N, -1}) + } + if toplevel { + if p.(*seqPattern).MustTail { + ptr.insts = append(ptr.insts, inst{opSave, nil, 1, -1}, inst{opTailMatch, nil, -1, -1}) + } + ptr.insts = append(ptr.insts, inst{opSave, nil, 1, -1}, inst{opMatch, nil, -1, -1}) + } + return ptr.insts +} + +/* }}} parse */ + +/* VM {{{ */ + +// Simple recursive virtual machine based on the +// "Regular Expression Matching: the Virtual Machine Approach" (https://swtch.com/~rsc/regexp/regexp2.html) +func recursiveVM(src []byte, insts []inst, pc, sp int, ms ...*MatchData) (bool, int, *MatchData) { + var m *MatchData + if len(ms) == 0 { + m = newMatchState() + } else { + m = ms[0] + } +redo: + inst := insts[pc] + switch inst.OpCode { + case opChar: + if sp >= len(src) || !inst.Class.Matches(int(src[sp])) { + return false, sp, m + } + pc++ + sp++ + goto redo + case opMatch: + return true, sp, m + case opTailMatch: + return sp >= len(src), sp, m + case opJmp: + pc = inst.Operand1 + goto redo + case opSplit: + if ok, nsp, _ := recursiveVM(src, insts, inst.Operand1, sp, m); ok { + return true, nsp, m + } + pc = inst.Operand2 + goto redo + case opSave: + s := m.setCapture(inst.Operand1, sp) + if ok, nsp, _ := recursiveVM(src, insts, pc+1, sp, m); ok { + return true, nsp, m + } + m.restoreCapture(inst.Operand1, s) + return false, sp, m + case opPSave: + m.addPosCapture(inst.Operand1, sp+1) + pc++ + goto redo + case opBrace: + if sp >= len(src) || int(src[sp]) != inst.Operand1 { + return false, sp, m + } + count := 1 + for sp = sp + 1; sp < len(src); sp++ { + if int(src[sp]) == inst.Operand2 { + count-- + } + if count == 0 { + pc++ + sp++ + goto redo + } + if int(src[sp]) == inst.Operand1 { + count++ + } + } + return false, sp, m + case opNumber: + idx := inst.Operand1 * 2 + if idx >= m.CaptureLength()-1 { + panic(newError(_UNKNOWN, "invalid capture index")) + } + capture := src[m.Capture(idx):m.Capture(idx+1)] + for i := 0; i < len(capture); i++ { + if i+sp >= len(src) || capture[i] != src[i+sp] { + return false, sp, m + } + } + pc++ + sp += len(capture) + goto redo + } + panic("should not reach here") +} + +/* }}} */ + +/* API {{{ */ + +func Find(p string, src []byte, offset, limit int) (matches []*MatchData, err error) { + defer func() { + if v := recover(); v != nil { + if perr, ok := v.(*Error); ok { + err = perr + } else { + panic(v) + } + } + }() + pat := parsePattern(newScanner([]byte(p)), true) + insts := compilePattern(pat) + matches = []*MatchData{} + for sp := offset; sp <= len(src); { + ok, nsp, ms := recursiveVM(src, insts, 0, sp) + sp++ + if ok { + if sp < nsp { + sp = nsp + } + matches = append(matches, ms) + } + if len(matches) == limit || pat.MustHead { + break + } + } + return +} + +/* }}} */ diff --git a/vendor/github.com/yuin/gopher-lua/state.go b/vendor/github.com/yuin/gopher-lua/state.go new file mode 100644 index 00000000000..b8d084a62e8 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/state.go @@ -0,0 +1,2236 @@ +package lua + +//////////////////////////////////////////////////////// +// This file was generated by go-inline. DO NOT EDIT. // +//////////////////////////////////////////////////////// + +import ( + "context" + "fmt" + "io" + "math" + "os" + "runtime" + "strings" + "sync" + "sync/atomic" + "time" + + "github.com/yuin/gopher-lua/parse" +) + +const MultRet = -1 +const RegistryIndex = -10000 +const EnvironIndex = -10001 +const GlobalsIndex = -10002 + +/* ApiError {{{ */ + +type ApiError struct { + Type ApiErrorType + Object LValue + StackTrace string + // Underlying error. This attribute is set only if the Type is ApiErrorFile or ApiErrorSyntax + Cause error +} + +func newApiError(code ApiErrorType, object LValue) *ApiError { + return &ApiError{code, object, "", nil} +} + +func newApiErrorS(code ApiErrorType, message string) *ApiError { + return newApiError(code, LString(message)) +} + +func newApiErrorE(code ApiErrorType, err error) *ApiError { + return &ApiError{code, LString(err.Error()), "", err} +} + +func (e *ApiError) Error() string { + if len(e.StackTrace) > 0 { + return fmt.Sprintf("%s\n%s", e.Object.String(), e.StackTrace) + } + return e.Object.String() +} + +type ApiErrorType int + +const ( + ApiErrorSyntax ApiErrorType = iota + ApiErrorFile + ApiErrorRun + ApiErrorError + ApiErrorPanic +) + +/* }}} */ + +/* ResumeState {{{ */ + +type ResumeState int + +const ( + ResumeOK ResumeState = iota + ResumeYield + ResumeError +) + +/* }}} */ + +/* P {{{ */ + +type P struct { + Fn LValue + NRet int + Protect bool + Handler *LFunction +} + +/* }}} */ + +/* Options {{{ */ + +// Options is a configuration that is used to create a new LState. +type Options struct { + // Call stack size. This defaults to `lua.CallStackSize`. + CallStackSize int + // Data stack size. This defaults to `lua.RegistrySize`. + RegistrySize int + // Allow the registry to grow from the registry size specified up to a value of RegistryMaxSize. A value of 0 + // indicates no growth is permitted. The registry will not shrink again after any growth. + RegistryMaxSize int + // If growth is enabled, step up by an additional `RegistryGrowStep` each time to avoid having to resize too often. + // This defaults to `lua.RegistryGrowStep` + RegistryGrowStep int + // Controls whether or not libraries are opened by default + SkipOpenLibs bool + // Tells whether a Go stacktrace should be included in a Lua stacktrace when panics occur. + IncludeGoStackTrace bool + // If `MinimizeStackMemory` is set, the call stack will be automatically grown or shrank up to a limit of + // `CallStackSize` in order to minimize memory usage. This does incur a slight performance penalty. + MinimizeStackMemory bool +} + +/* }}} */ + +/* Debug {{{ */ + +type Debug struct { + frame *callFrame + Name string + What string + Source string + CurrentLine int + NUpvalues int + LineDefined int + LastLineDefined int +} + +/* }}} */ + +/* callFrame {{{ */ + +type callFrame struct { + Idx int + Fn *LFunction + Parent *callFrame + Pc int + Base int + LocalBase int + ReturnBase int + NArgs int + NRet int + TailCall int +} + +type callFrameStack interface { + Push(v callFrame) + Pop() *callFrame + Last() *callFrame + + SetSp(sp int) + Sp() int + At(sp int) *callFrame + + IsFull() bool + IsEmpty() bool + + FreeAll() +} + +type fixedCallFrameStack struct { + array []callFrame + sp int +} + +func newFixedCallFrameStack(size int) callFrameStack { + return &fixedCallFrameStack{ + array: make([]callFrame, size), + sp: 0, + } +} + +func (cs *fixedCallFrameStack) IsEmpty() bool { return cs.sp == 0 } + +func (cs *fixedCallFrameStack) IsFull() bool { return cs.sp == len(cs.array) } + +func (cs *fixedCallFrameStack) Clear() { + cs.sp = 0 +} + +func (cs *fixedCallFrameStack) Push(v callFrame) { + cs.array[cs.sp] = v + cs.array[cs.sp].Idx = cs.sp + cs.sp++ +} + +func (cs *fixedCallFrameStack) Sp() int { + return cs.sp +} + +func (cs *fixedCallFrameStack) SetSp(sp int) { + cs.sp = sp +} + +func (cs *fixedCallFrameStack) Last() *callFrame { + if cs.sp == 0 { + return nil + } + return &cs.array[cs.sp-1] +} + +func (cs *fixedCallFrameStack) At(sp int) *callFrame { + return &cs.array[sp] +} + +func (cs *fixedCallFrameStack) Pop() *callFrame { + cs.sp-- + return &cs.array[cs.sp] +} + +func (cs *fixedCallFrameStack) FreeAll() { + // nothing to do for fixed callframestack +} + +// FramesPerSegment should be a power of 2 constant for performance reasons. It will allow the go compiler to change +// the divs and mods into bitshifts. Max is 256 due to current use of uint8 to count how many frames in a segment are +// used. +const FramesPerSegment = 8 + +type callFrameStackSegment struct { + array [FramesPerSegment]callFrame +} +type segIdx uint16 +type autoGrowingCallFrameStack struct { + segments []*callFrameStackSegment + segIdx segIdx + // segSp is the number of frames in the current segment which are used. Full 'sp' value is segIdx * FramesPerSegment + segSp. + // It points to the next stack slot to use, so 0 means to use the 0th element in the segment, and a value of + // FramesPerSegment indicates that the segment is full and cannot accommodate another frame. + segSp uint8 +} + +var segmentPool sync.Pool + +func newCallFrameStackSegment() *callFrameStackSegment { + seg := segmentPool.Get() + if seg == nil { + return &callFrameStackSegment{} + } + return seg.(*callFrameStackSegment) +} + +func freeCallFrameStackSegment(seg *callFrameStackSegment) { + segmentPool.Put(seg) +} + +// newCallFrameStack allocates a new stack for a lua state, which will auto grow up to a max size of at least maxSize. +// it will actually grow up to the next segment size multiple after maxSize, where the segment size is dictated by +// FramesPerSegment. +func newAutoGrowingCallFrameStack(maxSize int) callFrameStack { + cs := &autoGrowingCallFrameStack{ + segments: make([]*callFrameStackSegment, (maxSize+(FramesPerSegment-1))/FramesPerSegment), + segIdx: 0, + } + cs.segments[0] = newCallFrameStackSegment() + return cs +} + +func (cs *autoGrowingCallFrameStack) IsEmpty() bool { + return cs.segIdx == 0 && cs.segSp == 0 +} + +// IsFull returns true if the stack cannot receive any more stack pushes without overflowing +func (cs *autoGrowingCallFrameStack) IsFull() bool { + return int(cs.segIdx) == len(cs.segments) && cs.segSp >= FramesPerSegment +} + +func (cs *autoGrowingCallFrameStack) Clear() { + for i := segIdx(1); i <= cs.segIdx; i++ { + freeCallFrameStackSegment(cs.segments[i]) + cs.segments[i] = nil + } + cs.segIdx = 0 + cs.segSp = 0 +} + +func (cs *autoGrowingCallFrameStack) FreeAll() { + for i := segIdx(0); i <= cs.segIdx; i++ { + freeCallFrameStackSegment(cs.segments[i]) + cs.segments[i] = nil + } +} + +// Push pushes the passed callFrame onto the stack. it panics if the stack is full, caller should call IsFull() before +// invoking this to avoid this. +func (cs *autoGrowingCallFrameStack) Push(v callFrame) { + curSeg := cs.segments[cs.segIdx] + if cs.segSp >= FramesPerSegment { + // segment full, push new segment if allowed + if cs.segIdx < segIdx(len(cs.segments)-1) { + curSeg = newCallFrameStackSegment() + cs.segIdx++ + cs.segments[cs.segIdx] = curSeg + cs.segSp = 0 + } else { + panic("lua callstack overflow") + } + } + curSeg.array[cs.segSp] = v + curSeg.array[cs.segSp].Idx = int(cs.segSp) + FramesPerSegment*int(cs.segIdx) + cs.segSp++ +} + +// Sp retrieves the current stack depth, which is the number of frames currently pushed on the stack. +func (cs *autoGrowingCallFrameStack) Sp() int { + return int(cs.segSp) + int(cs.segIdx)*FramesPerSegment +} + +// SetSp can be used to rapidly unwind the stack, freeing all stack frames on the way. It should not be used to +// allocate new stack space, use Push() for that. +func (cs *autoGrowingCallFrameStack) SetSp(sp int) { + desiredSegIdx := segIdx(sp / FramesPerSegment) + desiredFramesInLastSeg := uint8(sp % FramesPerSegment) + for { + if cs.segIdx <= desiredSegIdx { + break + } + freeCallFrameStackSegment(cs.segments[cs.segIdx]) + cs.segments[cs.segIdx] = nil + cs.segIdx-- + } + cs.segSp = desiredFramesInLastSeg +} + +func (cs *autoGrowingCallFrameStack) Last() *callFrame { + curSeg := cs.segments[cs.segIdx] + segSp := cs.segSp + if segSp == 0 { + if cs.segIdx == 0 { + return nil + } + curSeg = cs.segments[cs.segIdx-1] + segSp = FramesPerSegment + } + return &curSeg.array[segSp-1] +} + +func (cs *autoGrowingCallFrameStack) At(sp int) *callFrame { + segIdx := segIdx(sp / FramesPerSegment) + frameIdx := uint8(sp % FramesPerSegment) + return &cs.segments[segIdx].array[frameIdx] +} + +// Pop pops off the most recent stack frame and returns it +func (cs *autoGrowingCallFrameStack) Pop() *callFrame { + curSeg := cs.segments[cs.segIdx] + if cs.segSp == 0 { + if cs.segIdx == 0 { + // stack empty + return nil + } + freeCallFrameStackSegment(curSeg) + cs.segments[cs.segIdx] = nil + cs.segIdx-- + cs.segSp = FramesPerSegment + curSeg = cs.segments[cs.segIdx] + } + cs.segSp-- + return &curSeg.array[cs.segSp] +} + +/* }}} */ + +/* registry {{{ */ + +type registryHandler interface { + registryOverflow() +} +type registry struct { + array []LValue + top int + growBy int + maxSize int + alloc *allocator + handler registryHandler +} + +func newRegistry(handler registryHandler, initialSize int, growBy int, maxSize int, alloc *allocator) *registry { + return ®istry{make([]LValue, initialSize), 0, growBy, maxSize, alloc, handler} +} + +func (rg *registry) checkSize(requiredSize int) { // +inline-start + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } +} // +inline-end + +func (rg *registry) resize(requiredSize int) { // +inline-start + newSize := requiredSize + rg.growBy // give some padding + if newSize > rg.maxSize { + newSize = rg.maxSize + } + if newSize < requiredSize { + rg.handler.registryOverflow() + return + } + rg.forceResize(newSize) +} // +inline-end + +func (rg *registry) forceResize(newSize int) { + newSlice := make([]LValue, newSize) + 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) { + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + requiredSize := top + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + oldtop := rg.top + rg.top = top + for i := oldtop; 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] + for i := range nilRange { + nilRange[i] = nil + } + } + //for i := rg.top; i < oldtop; i++ { + // rg.array[i] = LNil + //} +} + +func (rg *registry) Top() int { + return rg.top +} + +func (rg *registry) Push(v LValue) { + newSize := rg.top + 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[rg.top] = v + rg.top++ +} + +func (rg *registry) Pop() LValue { + v := rg.array[rg.top-1] + rg.array[rg.top-1] = LNil + rg.top-- + return v +} + +func (rg *registry) Get(reg int) LValue { + return rg.array[reg] +} + +// CopyRange will move a section of values from index `start` to index `regv` +// It will move `n` values. +// `limit` specifies the maximum end range that can be copied from. If it's set to -1, then it defaults to stopping at +// the top of the registry (values beyond the top are not initialized, so if specifying an alternative `limit` you should +// pass a value <= rg.top. +// If start+n is beyond the limit, then nil values will be copied to the destination slots. +// After the copy, the registry is truncated to be at the end of the copied range, ie the original of the copied values +// are nilled out. (So top will be regv+n) +// CopyRange should ideally be renamed to MoveRange. +func (rg *registry) CopyRange(regv, start, limit, n int) { // +inline-start + newSize := regv + n + // 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) + } + } + if limit == -1 || limit > rg.top { + limit = rg.top + } + for i := 0; i < n; i++ { + srcIdx := start + i + if srcIdx >= limit || srcIdx < 0 { + rg.array[regv+i] = LNil + } else { + rg.array[regv+i] = rg.array[srcIdx] + } + } + + // 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 + oldtop := rg.top + rg.top = regv + n + if rg.top < oldtop { + nilRange := rg.array[rg.top:oldtop] + for i := range nilRange { + nilRange[i] = nil + } + } +} // +inline-end + +// FillNil fills the registry with nil values from regm to regm+n and then sets the registry top to regm+n +func (rg *registry) FillNil(regm, n int) { // +inline-start + newSize := regm + n + // 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) + } + } + for i := 0; i < n; i++ { + rg.array[regm+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 + oldtop := rg.top + rg.top = regm + n + if rg.top < oldtop { + nilRange := rg.array[rg.top:oldtop] + for i := range nilRange { + nilRange[i] = nil + } + } +} // +inline-end + +func (rg *registry) Insert(value LValue, reg int) { + top := rg.Top() + if reg >= top { + 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)) + } + rg.Set(reg, value) +} + +func (rg *registry) Set(reg int, val LValue) { + newSize := reg + 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[reg] = val + if reg >= rg.top { + rg.top = reg + 1 + } +} + +func (rg *registry) SetNumber(reg int, val LNumber) { + newSize := reg + 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[reg] = rg.alloc.LNumber2I(val) + if reg >= rg.top { + rg.top = reg + 1 + } +} + +func (rg *registry) IsFull() bool { + return rg.top >= cap(rg.array) +} + +/* }}} */ + +/* Global {{{ */ + +func newGlobal() *Global { + return &Global{ + MainThread: nil, + Registry: newLTable(0, 32), + Global: newLTable(0, 64), + builtinMts: make(map[int]LValue), + tempFiles: make([]*os.File, 0, 10), + } +} + +/* }}} */ + +/* package local methods {{{ */ + +func panicWithTraceback(L *LState) { + err := newApiError(ApiErrorRun, L.Get(-1)) + err.StackTrace = L.stackTrace(0) + panic(err) +} + +func panicWithoutTraceback(L *LState) { + err := newApiError(ApiErrorRun, L.Get(-1)) + panic(err) +} + +func newLState(options Options) *LState { + al := newAllocator(32) + ls := &LState{ + G: newGlobal(), + Parent: nil, + Panic: panicWithTraceback, + Dead: false, + Options: options, + + stop: 0, + alloc: al, + currentFrame: nil, + wrapped: false, + uvcache: nil, + hasErrorFunc: false, + mainLoop: mainLoop, + ctx: nil, + } + if options.MinimizeStackMemory { + ls.stack = newAutoGrowingCallFrameStack(options.CallStackSize) + } else { + ls.stack = newFixedCallFrameStack(options.CallStackSize) + } + ls.reg = newRegistry(ls, options.RegistrySize, options.RegistryGrowStep, options.RegistryMaxSize, al) + ls.Env = ls.G.Global + return ls +} + +func (ls *LState) printReg() { + println("-------------------------") + println("thread:", ls) + println("top:", ls.reg.Top()) + if ls.currentFrame != nil { + println("function base:", ls.currentFrame.Base) + println("return base:", ls.currentFrame.ReturnBase) + } else { + println("(vm not started)") + } + println("local base:", ls.currentLocalBase()) + for i := 0; i < ls.reg.Top(); i++ { + println(i, ls.reg.Get(i).String()) + } + println("-------------------------") +} + +func (ls *LState) printCallStack() { + println("-------------------------") + for i := 0; i < ls.stack.Sp(); i++ { + print(i) + print(" ") + frame := ls.stack.At(i) + if frame == nil { + break + } + if frame.Fn.IsG { + println("IsG:", true, "Frame:", frame, "Fn:", frame.Fn) + } else { + println("IsG:", false, "Frame:", frame, "Fn:", frame.Fn, "pc:", frame.Pc) + } + } + println("-------------------------") +} + +func (ls *LState) closeAllUpvalues() { // +inline-start + for cf := ls.currentFrame; cf != nil; cf = cf.Parent { + if !cf.Fn.IsG { + ls.closeUpvalues(cf.LocalBase) + } + } +} // +inline-end + +func (ls *LState) raiseError(level int, format string, args ...interface{}) { + if !ls.hasErrorFunc { + ls.closeAllUpvalues() + } + message := format + if len(args) > 0 { + message = fmt.Sprintf(format, args...) + } + if level > 0 { + message = fmt.Sprintf("%v %v", ls.where(level-1, true), message) + } + if ls.reg.IsFull() { + // if the registry is full then it won't be possible to push a value, in this case, force a larger size + ls.reg.forceResize(ls.reg.Top() + 1) + } + ls.reg.Push(LString(message)) + ls.Panic(ls) +} + +func (ls *LState) findLocal(frame *callFrame, no int) string { + fn := frame.Fn + if !fn.IsG { + if name, ok := fn.LocalName(no, frame.Pc-1); ok { + return name + } + } + var top int + if ls.currentFrame == frame { + top = ls.reg.Top() + } else if frame.Idx+1 < ls.stack.Sp() { + top = ls.stack.At(frame.Idx + 1).Base + } else { + return "" + } + if top-frame.LocalBase >= no { + return "(*temporary)" + } + return "" +} + +func (ls *LState) where(level int, skipg bool) string { + dbg, ok := ls.GetStack(level) + if !ok { + return "" + } + cf := dbg.frame + proto := cf.Fn.Proto + sourcename := "[G]" + if proto != nil { + sourcename = proto.SourceName + } else if skipg { + return ls.where(level+1, skipg) + } + line := "" + if proto != nil { + line = fmt.Sprintf("%v:", proto.DbgSourcePositions[cf.Pc-1]) + } + return fmt.Sprintf("%v:%v", sourcename, line) +} + +func (ls *LState) stackTrace(level int) string { + buf := []string{} + header := "stack traceback:" + if ls.currentFrame != nil { + i := 0 + for dbg, ok := ls.GetStack(i); ok; dbg, ok = ls.GetStack(i) { + cf := dbg.frame + buf = append(buf, fmt.Sprintf("\t%v in %v", ls.Where(i), ls.formattedFrameFuncName(cf))) + if !cf.Fn.IsG && cf.TailCall > 0 { + for tc := cf.TailCall; tc > 0; tc-- { + buf = append(buf, "\t(tailcall): ?") + i++ + } + } + i++ + } + } + buf = append(buf, fmt.Sprintf("\t%v: %v", "[G]", "?")) + buf = buf[intMax(0, intMin(level, len(buf))):len(buf)] + if len(buf) > 20 { + newbuf := make([]string, 0, 20) + newbuf = append(newbuf, buf[0:7]...) + newbuf = append(newbuf, "\t...") + newbuf = append(newbuf, buf[len(buf)-7:len(buf)]...) + buf = newbuf + } + return fmt.Sprintf("%s\n%s", header, strings.Join(buf, "\n")) +} + +func (ls *LState) formattedFrameFuncName(fr *callFrame) string { + name, ischunk := ls.frameFuncName(fr) + if ischunk { + return name + } + if name[0] != '(' && name[0] != '<' { + return fmt.Sprintf("function '%s'", name) + } + return fmt.Sprintf("function %s", name) +} + +func (ls *LState) rawFrameFuncName(fr *callFrame) string { + name, _ := ls.frameFuncName(fr) + return name +} + +func (ls *LState) frameFuncName(fr *callFrame) (string, bool) { + frame := fr.Parent + if frame == nil { + if ls.Parent == nil { + return "main chunk", true + } else { + return "corountine", true + } + } + if !frame.Fn.IsG { + pc := frame.Pc - 1 + for _, call := range frame.Fn.Proto.DbgCalls { + if call.Pc == pc { + name := call.Name + if (name == "?" || fr.TailCall > 0) && !fr.Fn.IsG { + name = fmt.Sprintf("<%v:%v>", fr.Fn.Proto.SourceName, fr.Fn.Proto.LineDefined) + } + return name, false + } + } + } + if !fr.Fn.IsG { + return fmt.Sprintf("<%v:%v>", fr.Fn.Proto.SourceName, fr.Fn.Proto.LineDefined), false + } + return "(anonymous)", false +} + +func (ls *LState) isStarted() bool { + return ls.currentFrame != nil +} + +func (ls *LState) kill() { + ls.Dead = true +} + +func (ls *LState) indexToReg(idx int) int { + base := ls.currentLocalBase() + if idx > 0 { + return base + idx - 1 + } else if idx == 0 { + return -1 + } else { + tidx := ls.reg.Top() + idx + if tidx < base { + return -1 + } + return tidx + } +} + +func (ls *LState) currentLocalBase() int { + base := 0 + if ls.currentFrame != nil { + base = ls.currentFrame.LocalBase + } + return base +} + +func (ls *LState) currentEnv() *LTable { + return ls.Env + /* + if ls.currentFrame == nil { + return ls.Env + } + return ls.currentFrame.Fn.Env + */ +} + +func (ls *LState) rkValue(idx int) LValue { + /* + if OpIsK(idx) { + return ls.currentFrame.Fn.Proto.Constants[opIndexK(idx)] + } + return ls.reg.Get(ls.currentFrame.LocalBase + idx) + */ + if (idx & opBitRk) != 0 { + return ls.currentFrame.Fn.Proto.Constants[idx & ^opBitRk] + } + return ls.reg.array[ls.currentFrame.LocalBase+idx] +} + +func (ls *LState) rkString(idx int) string { + if (idx & opBitRk) != 0 { + return ls.currentFrame.Fn.Proto.stringConstants[idx & ^opBitRk] + } + return string(ls.reg.array[ls.currentFrame.LocalBase+idx].(LString)) +} + +func (ls *LState) closeUpvalues(idx int) { // +inline-start + if ls.uvcache != nil { + var prev *Upvalue + for uv := ls.uvcache; uv != nil; uv = uv.next { + if uv.index >= idx { + if prev != nil { + prev.next = nil + } else { + ls.uvcache = nil + } + uv.Close() + } + prev = uv + } + } +} // +inline-end + +func (ls *LState) findUpvalue(idx int) *Upvalue { + var prev *Upvalue + var next *Upvalue + if ls.uvcache != nil { + for uv := ls.uvcache; uv != nil; uv = uv.next { + if uv.index == idx { + return uv + } + if uv.index > idx { + next = uv + break + } + prev = uv + } + } + uv := &Upvalue{reg: ls.reg, index: idx, closed: false} + if prev != nil { + prev.next = uv + } else { + ls.uvcache = uv + } + if next != nil { + uv.next = next + } + return uv +} + +func (ls *LState) metatable(lvalue LValue, rawget bool) LValue { + var metatable LValue = LNil + switch obj := lvalue.(type) { + case *LTable: + metatable = obj.Metatable + case *LUserData: + metatable = obj.Metatable + default: + if table, ok := ls.G.builtinMts[int(obj.Type())]; ok { + metatable = table + } + } + + if !rawget && metatable != LNil { + oldmt := metatable + if tb, ok := metatable.(*LTable); ok { + metatable = tb.RawGetString("__metatable") + if metatable == LNil { + metatable = oldmt + } + } + } + + return metatable +} + +func (ls *LState) metaOp1(lvalue LValue, event string) LValue { + if mt := ls.metatable(lvalue, true); mt != LNil { + if tb, ok := mt.(*LTable); ok { + return tb.RawGetString(event) + } + } + return LNil +} + +func (ls *LState) metaOp2(value1, value2 LValue, event string) LValue { + if mt := ls.metatable(value1, true); mt != LNil { + if tb, ok := mt.(*LTable); ok { + if ret := tb.RawGetString(event); ret != LNil { + return ret + } + } + } + if mt := ls.metatable(value2, true); mt != LNil { + if tb, ok := mt.(*LTable); ok { + return tb.RawGetString(event) + } + } + return LNil +} + +func (ls *LState) metaCall(lvalue LValue) (*LFunction, bool) { + if fn, ok := lvalue.(*LFunction); ok { + return fn, false + } + if fn, ok := ls.metaOp1(lvalue, "__call").(*LFunction); ok { + return fn, true + } + return nil, false +} + +func (ls *LState) initCallFrame(cf *callFrame) { // +inline-start + if cf.Fn.IsG { + ls.reg.SetTop(cf.LocalBase + cf.NArgs) + } else { + proto := cf.Fn.Proto + nargs := cf.NArgs + np := int(proto.NumParameters) + newSize := cf.LocalBase + np + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + rg := ls.reg + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + for i := nargs; i < np; i++ { + ls.reg.array[cf.LocalBase+i] = LNil + nargs = np + } + + if (proto.IsVarArg & VarArgIsVarArg) == 0 { + if nargs < int(proto.NumUsedRegisters) { + nargs = int(proto.NumUsedRegisters) + } + newSize = cf.LocalBase + nargs + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + rg := ls.reg + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + for i := np; i < nargs; i++ { + ls.reg.array[cf.LocalBase+i] = LNil + } + ls.reg.top = cf.LocalBase + int(proto.NumUsedRegisters) + } else { + /* swap vararg positions: + closure + namedparam1 <- lbase + namedparam2 + vararg1 + vararg2 + + TO + + closure + nil + nil + vararg1 + vararg2 + namedparam1 <- lbase + namedparam2 + */ + nvarargs := nargs - np + if nvarargs < 0 { + nvarargs = 0 + } + + ls.reg.SetTop(cf.LocalBase + nargs + np) + for i := 0; i < np; i++ { + //ls.reg.Set(cf.LocalBase+nargs+i, ls.reg.Get(cf.LocalBase+i)) + ls.reg.array[cf.LocalBase+nargs+i] = ls.reg.array[cf.LocalBase+i] + //ls.reg.Set(cf.LocalBase+i, LNil) + ls.reg.array[cf.LocalBase+i] = LNil + } + + if CompatVarArg { + ls.reg.SetTop(cf.LocalBase + nargs + np + 1) + if (proto.IsVarArg & VarArgNeedsArg) != 0 { + argtb := newLTable(nvarargs, 0) + for i := 0; i < nvarargs; i++ { + argtb.RawSetInt(i+1, ls.reg.Get(cf.LocalBase+np+i)) + } + argtb.RawSetString("n", LNumber(nvarargs)) + //ls.reg.Set(cf.LocalBase+nargs+np, argtb) + ls.reg.array[cf.LocalBase+nargs+np] = argtb + } else { + ls.reg.array[cf.LocalBase+nargs+np] = LNil + } + } + cf.LocalBase += nargs + maxreg := cf.LocalBase + int(proto.NumUsedRegisters) + ls.reg.SetTop(maxreg) + } + } +} // +inline-end + +func (ls *LState) pushCallFrame(cf callFrame, fn LValue, meta bool) { // +inline-start + if meta { + cf.NArgs++ + ls.reg.Insert(fn, cf.LocalBase) + } + if cf.Fn == nil { + ls.RaiseError("attempt to call a non-function object") + } + if ls.stack.IsFull() { + ls.RaiseError("stack overflow") + } + ls.stack.Push(cf) + newcf := ls.stack.Last() + // this section is inlined by go-inline + // source function is 'func (ls *LState) initCallFrame(cf *callFrame) ' in '_state.go' + { + cf := newcf + if cf.Fn.IsG { + ls.reg.SetTop(cf.LocalBase + cf.NArgs) + } else { + proto := cf.Fn.Proto + nargs := cf.NArgs + np := int(proto.NumParameters) + newSize := cf.LocalBase + np + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + rg := ls.reg + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + for i := nargs; i < np; i++ { + ls.reg.array[cf.LocalBase+i] = LNil + nargs = np + } + + if (proto.IsVarArg & VarArgIsVarArg) == 0 { + if nargs < int(proto.NumUsedRegisters) { + nargs = int(proto.NumUsedRegisters) + } + newSize = cf.LocalBase + nargs + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + rg := ls.reg + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + for i := np; i < nargs; i++ { + ls.reg.array[cf.LocalBase+i] = LNil + } + ls.reg.top = cf.LocalBase + int(proto.NumUsedRegisters) + } else { + /* swap vararg positions: + closure + namedparam1 <- lbase + namedparam2 + vararg1 + vararg2 + + TO + + closure + nil + nil + vararg1 + vararg2 + namedparam1 <- lbase + namedparam2 + */ + nvarargs := nargs - np + if nvarargs < 0 { + nvarargs = 0 + } + + ls.reg.SetTop(cf.LocalBase + nargs + np) + for i := 0; i < np; i++ { + //ls.reg.Set(cf.LocalBase+nargs+i, ls.reg.Get(cf.LocalBase+i)) + ls.reg.array[cf.LocalBase+nargs+i] = ls.reg.array[cf.LocalBase+i] + //ls.reg.Set(cf.LocalBase+i, LNil) + ls.reg.array[cf.LocalBase+i] = LNil + } + + if CompatVarArg { + ls.reg.SetTop(cf.LocalBase + nargs + np + 1) + if (proto.IsVarArg & VarArgNeedsArg) != 0 { + argtb := newLTable(nvarargs, 0) + for i := 0; i < nvarargs; i++ { + argtb.RawSetInt(i+1, ls.reg.Get(cf.LocalBase+np+i)) + } + argtb.RawSetString("n", LNumber(nvarargs)) + //ls.reg.Set(cf.LocalBase+nargs+np, argtb) + ls.reg.array[cf.LocalBase+nargs+np] = argtb + } else { + ls.reg.array[cf.LocalBase+nargs+np] = LNil + } + } + cf.LocalBase += nargs + maxreg := cf.LocalBase + int(proto.NumUsedRegisters) + ls.reg.SetTop(maxreg) + } + } + } + ls.currentFrame = newcf +} // +inline-end + +func (ls *LState) callR(nargs, nret, rbase int) { + base := ls.reg.Top() - nargs - 1 + if rbase < 0 { + rbase = base + } + lv := ls.reg.Get(base) + fn, meta := ls.metaCall(lv) + ls.pushCallFrame(callFrame{ + Fn: fn, + Pc: 0, + Base: base, + LocalBase: base + 1, + ReturnBase: rbase, + NArgs: nargs, + NRet: nret, + Parent: ls.currentFrame, + TailCall: 0, + }, lv, meta) + if ls.G.MainThread == nil { + ls.G.MainThread = ls + ls.G.CurrentThread = ls + ls.mainLoop(ls, nil) + } else { + ls.mainLoop(ls, ls.currentFrame) + } + if nret != MultRet { + ls.reg.SetTop(rbase + nret) + } +} + +func (ls *LState) getField(obj LValue, key LValue) LValue { + curobj := obj + for i := 0; i < MaxTableGetLoop; i++ { + tb, istable := curobj.(*LTable) + if istable { + ret := tb.RawGet(key) + if ret != LNil { + return ret + } + } + metaindex := ls.metaOp1(curobj, "__index") + if metaindex == LNil { + if !istable { + ls.RaiseError("attempt to index a non-table object(%v) with key '%s'", curobj.Type().String(), key.String()) + } + return LNil + } + if metaindex.Type() == LTFunction { + ls.reg.Push(metaindex) + ls.reg.Push(curobj) + ls.reg.Push(key) + ls.Call(2, 1) + return ls.reg.Pop() + } else { + curobj = metaindex + } + } + ls.RaiseError("too many recursions in gettable") + return nil +} + +func (ls *LState) getFieldString(obj LValue, key string) LValue { + curobj := obj + for i := 0; i < MaxTableGetLoop; i++ { + tb, istable := curobj.(*LTable) + if istable { + ret := tb.RawGetString(key) + if ret != LNil { + return ret + } + } + metaindex := ls.metaOp1(curobj, "__index") + if metaindex == LNil { + if !istable { + ls.RaiseError("attempt to index a non-table object(%v) with key '%s'", curobj.Type().String(), key) + } + return LNil + } + if metaindex.Type() == LTFunction { + ls.reg.Push(metaindex) + ls.reg.Push(curobj) + ls.reg.Push(LString(key)) + ls.Call(2, 1) + return ls.reg.Pop() + } else { + curobj = metaindex + } + } + ls.RaiseError("too many recursions in gettable") + return nil +} + +func (ls *LState) setField(obj LValue, key LValue, value LValue) { + curobj := obj + for i := 0; i < MaxTableGetLoop; i++ { + tb, istable := curobj.(*LTable) + if istable { + if tb.RawGet(key) != LNil { + ls.RawSet(tb, key, value) + return + } + } + metaindex := ls.metaOp1(curobj, "__newindex") + if metaindex == LNil { + if !istable { + ls.RaiseError("attempt to index a non-table object(%v) with key '%s'", curobj.Type().String(), key.String()) + } + ls.RawSet(tb, key, value) + return + } + if metaindex.Type() == LTFunction { + ls.reg.Push(metaindex) + ls.reg.Push(curobj) + ls.reg.Push(key) + ls.reg.Push(value) + ls.Call(3, 0) + return + } else { + curobj = metaindex + } + } + ls.RaiseError("too many recursions in settable") +} + +func (ls *LState) setFieldString(obj LValue, key string, value LValue) { + curobj := obj + for i := 0; i < MaxTableGetLoop; i++ { + tb, istable := curobj.(*LTable) + if istable { + if tb.RawGetString(key) != LNil { + tb.RawSetString(key, value) + return + } + } + metaindex := ls.metaOp1(curobj, "__newindex") + if metaindex == LNil { + if !istable { + ls.RaiseError("attempt to index a non-table object(%v) with key '%s'", curobj.Type().String(), key) + } + tb.RawSetString(key, value) + return + } + if metaindex.Type() == LTFunction { + ls.reg.Push(metaindex) + ls.reg.Push(curobj) + ls.reg.Push(LString(key)) + ls.reg.Push(value) + ls.Call(3, 0) + return + } else { + curobj = metaindex + } + } + ls.RaiseError("too many recursions in settable") +} + +/* }}} */ + +/* api methods {{{ */ + +func NewState(opts ...Options) *LState { + var ls *LState + if len(opts) == 0 { + ls = newLState(Options{ + CallStackSize: CallStackSize, + RegistrySize: RegistrySize, + }) + ls.OpenLibs() + } else { + if opts[0].CallStackSize < 1 { + opts[0].CallStackSize = CallStackSize + } + if opts[0].RegistrySize < 128 { + opts[0].RegistrySize = RegistrySize + } + if opts[0].RegistryMaxSize < opts[0].RegistrySize { + opts[0].RegistryMaxSize = 0 // disable growth if max size is smaller than initial size + } else { + // if growth enabled, grow step is set + if opts[0].RegistryGrowStep < 1 { + opts[0].RegistryGrowStep = RegistryGrowStep + } + } + ls = newLState(opts[0]) + if !opts[0].SkipOpenLibs { + ls.OpenLibs() + } + } + return ls +} + +func (ls *LState) IsClosed() bool { + return ls.stack == nil +} + +func (ls *LState) Close() { + atomic.AddInt32(&ls.stop, 1) + for _, file := range ls.G.tempFiles { + // ignore errors in these operations + file.Close() + os.Remove(file.Name()) + } + ls.stack.FreeAll() + ls.stack = nil +} + +/* registry operations {{{ */ + +func (ls *LState) GetTop() int { + return ls.reg.Top() - ls.currentLocalBase() +} + +func (ls *LState) SetTop(idx int) { + base := ls.currentLocalBase() + newtop := ls.indexToReg(idx) + 1 + if newtop < base { + ls.reg.SetTop(base) + } else { + ls.reg.SetTop(newtop) + } +} + +func (ls *LState) Replace(idx int, value LValue) { + base := ls.currentLocalBase() + if idx > 0 { + reg := base + idx - 1 + if reg < ls.reg.Top() { + ls.reg.Set(reg, value) + } + } else if idx == 0 { + } else if idx > RegistryIndex { + if tidx := ls.reg.Top() + idx; tidx >= base { + ls.reg.Set(tidx, value) + } + } else { + switch idx { + case RegistryIndex: + if tb, ok := value.(*LTable); ok { + ls.G.Registry = tb + } else { + ls.RaiseError("registry must be a table(%v)", value.Type().String()) + } + case EnvironIndex: + if ls.currentFrame == nil { + ls.RaiseError("no calling environment") + } + if tb, ok := value.(*LTable); ok { + ls.currentFrame.Fn.Env = tb + } else { + ls.RaiseError("environment must be a table(%v)", value.Type().String()) + } + case GlobalsIndex: + if tb, ok := value.(*LTable); ok { + ls.G.Global = tb + } else { + ls.RaiseError("_G must be a table(%v)", value.Type().String()) + } + default: + fn := ls.currentFrame.Fn + index := GlobalsIndex - idx - 1 + if index < len(fn.Upvalues) { + fn.Upvalues[index].SetValue(value) + } + } + } +} + +func (ls *LState) Get(idx int) LValue { + base := ls.currentLocalBase() + if idx > 0 { + reg := base + idx - 1 + if reg < ls.reg.Top() { + return ls.reg.Get(reg) + } + return LNil + } else if idx == 0 { + return LNil + } else if idx > RegistryIndex { + tidx := ls.reg.Top() + idx + if tidx < base { + return LNil + } + return ls.reg.Get(tidx) + } else { + switch idx { + case RegistryIndex: + return ls.G.Registry + case EnvironIndex: + if ls.currentFrame == nil { + return ls.Env + } + return ls.currentFrame.Fn.Env + case GlobalsIndex: + return ls.G.Global + default: + fn := ls.currentFrame.Fn + index := GlobalsIndex - idx - 1 + if index < len(fn.Upvalues) { + return fn.Upvalues[index].Value() + } + return LNil + } + } + return LNil +} + +func (ls *LState) Push(value LValue) { + ls.reg.Push(value) +} + +func (ls *LState) Pop(n int) { + for i := 0; i < n; i++ { + if ls.GetTop() == 0 { + ls.RaiseError("register underflow") + } + ls.reg.Pop() + } +} + +func (ls *LState) Insert(value LValue, index int) { + reg := ls.indexToReg(index) + top := ls.reg.Top() + if reg >= top { + ls.reg.Set(reg, value) + return + } + if reg <= ls.currentLocalBase() { + reg = ls.currentLocalBase() + } + top-- + for ; top >= reg; top-- { + ls.reg.Set(top+1, ls.reg.Get(top)) + } + ls.reg.Set(reg, value) +} + +func (ls *LState) Remove(index int) { + reg := ls.indexToReg(index) + top := ls.reg.Top() + switch { + case reg >= top: + return + case reg < ls.currentLocalBase(): + return + case reg == top-1: + ls.Pop(1) + return + } + for i := reg; i < top-1; i++ { + ls.reg.Set(i, ls.reg.Get(i+1)) + } + ls.reg.SetTop(top - 1) +} + +/* }}} */ + +/* object allocation {{{ */ + +func (ls *LState) NewTable() *LTable { + return newLTable(defaultArrayCap, defaultHashCap) +} + +func (ls *LState) CreateTable(acap, hcap int) *LTable { + return newLTable(acap, hcap) +} + +// NewThread returns a new LState that shares with the original state all global objects. +// If the original state has context.Context, the new state has a new child context of the original state and this function returns its cancel function. +func (ls *LState) NewThread() (*LState, context.CancelFunc) { + thread := newLState(ls.Options) + thread.G = ls.G + thread.Env = ls.Env + var f context.CancelFunc = nil + if ls.ctx != nil { + thread.mainLoop = mainLoopWithContext + thread.ctx, f = context.WithCancel(ls.ctx) + } + return thread, f +} + +func (ls *LState) NewFunctionFromProto(proto *FunctionProto) *LFunction { + return newLFunctionL(proto, ls.Env, int(proto.NumUpvalues)) +} + +func (ls *LState) NewUserData() *LUserData { + return &LUserData{ + Env: ls.currentEnv(), + Metatable: LNil, + } +} + +func (ls *LState) NewFunction(fn LGFunction) *LFunction { + return newLFunctionG(fn, ls.currentEnv(), 0) +} + +func (ls *LState) NewClosure(fn LGFunction, upvalues ...LValue) *LFunction { + cl := newLFunctionG(fn, ls.currentEnv(), len(upvalues)) + for i, lv := range upvalues { + cl.Upvalues[i] = &Upvalue{} + cl.Upvalues[i].Close() + cl.Upvalues[i].SetValue(lv) + } + return cl +} + +/* }}} */ + +/* toType {{{ */ + +func (ls *LState) ToBool(n int) bool { + return LVAsBool(ls.Get(n)) +} + +func (ls *LState) ToInt(n int) int { + if lv, ok := ls.Get(n).(LNumber); ok { + return int(lv) + } + if lv, ok := ls.Get(n).(LString); ok { + if num, err := parseNumber(string(lv)); err == nil { + return int(num) + } + } + return 0 +} + +func (ls *LState) ToInt64(n int) int64 { + if lv, ok := ls.Get(n).(LNumber); ok { + return int64(lv) + } + if lv, ok := ls.Get(n).(LString); ok { + if num, err := parseNumber(string(lv)); err == nil { + return int64(num) + } + } + return 0 +} + +func (ls *LState) ToNumber(n int) LNumber { + return LVAsNumber(ls.Get(n)) +} + +func (ls *LState) ToString(n int) string { + return LVAsString(ls.Get(n)) +} + +func (ls *LState) ToTable(n int) *LTable { + if lv, ok := ls.Get(n).(*LTable); ok { + return lv + } + return nil +} + +func (ls *LState) ToFunction(n int) *LFunction { + if lv, ok := ls.Get(n).(*LFunction); ok { + return lv + } + return nil +} + +func (ls *LState) ToUserData(n int) *LUserData { + if lv, ok := ls.Get(n).(*LUserData); ok { + return lv + } + return nil +} + +func (ls *LState) ToThread(n int) *LState { + if lv, ok := ls.Get(n).(*LState); ok { + return lv + } + return nil +} + +/* }}} */ + +/* error & debug operations {{{ */ + +func (ls *LState) registryOverflow() { + ls.RaiseError("registry overflow") +} + +// This function is equivalent to luaL_error( http://www.lua.org/manual/5.1/manual.html#luaL_error ). +func (ls *LState) RaiseError(format string, args ...interface{}) { + ls.raiseError(1, format, args...) +} + +// This function is equivalent to lua_error( http://www.lua.org/manual/5.1/manual.html#lua_error ). +func (ls *LState) Error(lv LValue, level int) { + if str, ok := lv.(LString); ok { + ls.raiseError(level, string(str)) + } else { + if !ls.hasErrorFunc { + ls.closeAllUpvalues() + } + ls.Push(lv) + ls.Panic(ls) + } +} + +func (ls *LState) GetInfo(what string, dbg *Debug, fn LValue) (LValue, error) { + if !strings.HasPrefix(what, ">") { + fn = dbg.frame.Fn + } else { + what = what[1:] + } + f, ok := fn.(*LFunction) + if !ok { + return LNil, newApiErrorS(ApiErrorRun, "can not get debug info(an object in not a function)") + } + + retfn := false + for _, c := range what { + switch c { + case 'f': + retfn = true + case 'S': + if dbg.frame != nil && dbg.frame.Parent == nil { + dbg.What = "main" + } else if f.IsG { + dbg.What = "G" + } else if dbg.frame != nil && dbg.frame.TailCall > 0 { + dbg.What = "tail" + } else { + dbg.What = "Lua" + } + if !f.IsG { + dbg.Source = f.Proto.SourceName + dbg.LineDefined = f.Proto.LineDefined + dbg.LastLineDefined = f.Proto.LastLineDefined + } + case 'l': + if !f.IsG && dbg.frame != nil { + if dbg.frame.Pc > 0 { + dbg.CurrentLine = f.Proto.DbgSourcePositions[dbg.frame.Pc-1] + } + } else { + dbg.CurrentLine = -1 + } + case 'u': + dbg.NUpvalues = len(f.Upvalues) + case 'n': + if dbg.frame != nil { + dbg.Name = ls.rawFrameFuncName(dbg.frame) + } + default: + return LNil, newApiErrorS(ApiErrorRun, "invalid what: "+string(c)) + } + } + + if retfn { + return f, nil + } + return LNil, nil + +} + +func (ls *LState) GetStack(level int) (*Debug, bool) { + frame := ls.currentFrame + for ; level > 0 && frame != nil; frame = frame.Parent { + level-- + if !frame.Fn.IsG { + level -= frame.TailCall + } + } + + if level == 0 && frame != nil { + return &Debug{frame: frame}, true + } else if level < 0 && ls.stack.Sp() > 0 { + return &Debug{frame: ls.stack.At(0)}, true + } + return &Debug{}, false +} + +func (ls *LState) GetLocal(dbg *Debug, no int) (string, LValue) { + frame := dbg.frame + if name := ls.findLocal(frame, no); len(name) > 0 { + return name, ls.reg.Get(frame.LocalBase + no - 1) + } + return "", LNil +} + +func (ls *LState) SetLocal(dbg *Debug, no int, lv LValue) string { + frame := dbg.frame + if name := ls.findLocal(frame, no); len(name) > 0 { + ls.reg.Set(frame.LocalBase+no-1, lv) + return name + } + return "" +} + +func (ls *LState) GetUpvalue(fn *LFunction, no int) (string, LValue) { + if fn.IsG { + return "", LNil + } + + no-- + if no >= 0 && no < len(fn.Upvalues) { + return fn.Proto.DbgUpvalues[no], fn.Upvalues[no].Value() + } + return "", LNil +} + +func (ls *LState) SetUpvalue(fn *LFunction, no int, lv LValue) string { + if fn.IsG { + return "" + } + + no-- + if no >= 0 && no < len(fn.Upvalues) { + fn.Upvalues[no].SetValue(lv) + return fn.Proto.DbgUpvalues[no] + } + return "" +} + +/* }}} */ + +/* env operations {{{ */ + +func (ls *LState) GetFEnv(obj LValue) LValue { + switch lv := obj.(type) { + case *LFunction: + return lv.Env + case *LUserData: + return lv.Env + case *LState: + return lv.Env + } + return LNil +} + +func (ls *LState) SetFEnv(obj LValue, env LValue) { + tb, ok := env.(*LTable) + if !ok { + ls.RaiseError("cannot use %v as an environment", env.Type().String()) + } + + switch lv := obj.(type) { + case *LFunction: + lv.Env = tb + case *LUserData: + lv.Env = tb + case *LState: + lv.Env = tb + } + /* do nothing */ +} + +/* }}} */ + +/* table operations {{{ */ + +func (ls *LState) RawGet(tb *LTable, key LValue) LValue { + return tb.RawGet(key) +} + +func (ls *LState) RawGetInt(tb *LTable, key int) LValue { + return tb.RawGetInt(key) +} + +func (ls *LState) GetField(obj LValue, skey string) LValue { + return ls.getFieldString(obj, skey) +} + +func (ls *LState) GetTable(obj LValue, key LValue) LValue { + return ls.getField(obj, key) +} + +func (ls *LState) RawSet(tb *LTable, key LValue, value LValue) { + if n, ok := key.(LNumber); ok && math.IsNaN(float64(n)) { + ls.RaiseError("table index is NaN") + } else if key == LNil { + ls.RaiseError("table index is nil") + } + tb.RawSet(key, value) +} + +func (ls *LState) RawSetInt(tb *LTable, key int, value LValue) { + tb.RawSetInt(key, value) +} + +func (ls *LState) SetField(obj LValue, key string, value LValue) { + ls.setFieldString(obj, key, value) +} + +func (ls *LState) SetTable(obj LValue, key LValue, value LValue) { + ls.setField(obj, key, value) +} + +func (ls *LState) ForEach(tb *LTable, cb func(LValue, LValue)) { + tb.ForEach(cb) +} + +func (ls *LState) GetGlobal(name string) LValue { + return ls.GetField(ls.Get(GlobalsIndex), name) +} + +func (ls *LState) SetGlobal(name string, value LValue) { + ls.SetField(ls.Get(GlobalsIndex), name, value) +} + +func (ls *LState) Next(tb *LTable, key LValue) (LValue, LValue) { + return tb.Next(key) +} + +/* }}} */ + +/* unary operations {{{ */ + +func (ls *LState) ObjLen(v1 LValue) int { + if v1.Type() == LTString { + return len(string(v1.(LString))) + } + op := ls.metaOp1(v1, "__len") + if op.Type() == LTFunction { + ls.Push(op) + ls.Push(v1) + ls.Call(1, 1) + ret := ls.reg.Pop() + if ret.Type() == LTNumber { + return int(ret.(LNumber)) + } + } else if v1.Type() == LTTable { + return v1.(*LTable).Len() + } + return 0 +} + +/* }}} */ + +/* binary operations {{{ */ + +func (ls *LState) Concat(values ...LValue) string { + top := ls.reg.Top() + for _, value := range values { + ls.reg.Push(value) + } + ret := stringConcat(ls, len(values), ls.reg.Top()-1) + ls.reg.SetTop(top) + return LVAsString(ret) +} + +func (ls *LState) LessThan(lhs, rhs LValue) bool { + return lessThan(ls, lhs, rhs) +} + +func (ls *LState) Equal(lhs, rhs LValue) bool { + return equals(ls, lhs, rhs, false) +} + +func (ls *LState) RawEqual(lhs, rhs LValue) bool { + return equals(ls, lhs, rhs, true) +} + +/* }}} */ + +/* register operations {{{ */ + +func (ls *LState) Register(name string, fn LGFunction) { + ls.SetGlobal(name, ls.NewFunction(fn)) +} + +/* }}} */ + +/* load and function call operations {{{ */ + +func (ls *LState) Load(reader io.Reader, name string) (*LFunction, error) { + chunk, err := parse.Parse(reader, name) + if err != nil { + return nil, newApiErrorE(ApiErrorSyntax, err) + } + proto, err := Compile(chunk, name) + if err != nil { + return nil, newApiErrorE(ApiErrorSyntax, err) + } + return newLFunctionL(proto, ls.currentEnv(), 0), nil +} + +func (ls *LState) Call(nargs, nret int) { + ls.callR(nargs, nret, -1) +} + +func (ls *LState) PCall(nargs, nret int, errfunc *LFunction) (err error) { + err = nil + sp := ls.stack.Sp() + base := ls.reg.Top() - nargs - 1 + oldpanic := ls.Panic + ls.Panic = panicWithoutTraceback + if errfunc != nil { + ls.hasErrorFunc = true + } + defer func() { + ls.Panic = oldpanic + ls.hasErrorFunc = false + rcv := recover() + if rcv != nil { + if _, ok := rcv.(*ApiError); !ok { + err = newApiErrorS(ApiErrorPanic, fmt.Sprint(rcv)) + if ls.Options.IncludeGoStackTrace { + buf := make([]byte, 4096) + runtime.Stack(buf, false) + err.(*ApiError).StackTrace = strings.Trim(string(buf), "\000") + "\n" + ls.stackTrace(0) + } + } else { + err = rcv.(*ApiError) + } + if errfunc != nil { + ls.Push(errfunc) + ls.Push(err.(*ApiError).Object) + ls.Panic = panicWithoutTraceback + defer func() { + ls.Panic = oldpanic + rcv := recover() + if rcv != nil { + if _, ok := rcv.(*ApiError); !ok { + err = newApiErrorS(ApiErrorPanic, fmt.Sprint(rcv)) + if ls.Options.IncludeGoStackTrace { + buf := make([]byte, 4096) + runtime.Stack(buf, false) + err.(*ApiError).StackTrace = strings.Trim(string(buf), "\000") + ls.stackTrace(0) + } + } else { + err = rcv.(*ApiError) + err.(*ApiError).StackTrace = ls.stackTrace(0) + } + } + }() + ls.Call(1, 1) + err = newApiError(ApiErrorError, ls.Get(-1)) + } else if len(err.(*ApiError).StackTrace) == 0 { + err.(*ApiError).StackTrace = ls.stackTrace(0) + } + ls.stack.SetSp(sp) + ls.currentFrame = ls.stack.Last() + ls.reg.SetTop(base) + } + ls.stack.SetSp(sp) + if sp == 0 { + ls.currentFrame = nil + } + }() + + ls.Call(nargs, nret) + + return +} + +func (ls *LState) GPCall(fn LGFunction, data LValue) error { + ls.Push(newLFunctionG(fn, ls.currentEnv(), 0)) + ls.Push(data) + return ls.PCall(1, MultRet, nil) +} + +func (ls *LState) CallByParam(cp P, args ...LValue) error { + ls.Push(cp.Fn) + for _, arg := range args { + ls.Push(arg) + } + + if cp.Protect { + return ls.PCall(len(args), cp.NRet, cp.Handler) + } + ls.Call(len(args), cp.NRet) + return nil +} + +/* }}} */ + +/* metatable operations {{{ */ + +func (ls *LState) GetMetatable(obj LValue) LValue { + return ls.metatable(obj, false) +} + +func (ls *LState) SetMetatable(obj LValue, mt LValue) { + switch mt.(type) { + case *LNilType, *LTable: + default: + ls.RaiseError("metatable must be a table or nil, but got %v", mt.Type().String()) + } + + switch v := obj.(type) { + case *LTable: + v.Metatable = mt + case *LUserData: + v.Metatable = mt + default: + ls.G.builtinMts[int(obj.Type())] = mt + } +} + +/* }}} */ + +/* coroutine operations {{{ */ + +func (ls *LState) Status(th *LState) string { + status := "suspended" + if th.Dead { + status = "dead" + } else if ls.G.CurrentThread == th { + status = "running" + } else if ls.Parent == th { + status = "normal" + } + return status +} + +func (ls *LState) Resume(th *LState, fn *LFunction, args ...LValue) (ResumeState, error, []LValue) { + isstarted := th.isStarted() + if !isstarted { + base := 0 + th.stack.Push(callFrame{ + Fn: fn, + Pc: 0, + Base: base, + LocalBase: base + 1, + ReturnBase: base, + NArgs: 0, + NRet: MultRet, + Parent: nil, + TailCall: 0, + }) + } + + if ls.G.CurrentThread == th { + return ResumeError, newApiErrorS(ApiErrorRun, "can not resume a running thread"), nil + } + if th.Dead { + return ResumeError, newApiErrorS(ApiErrorRun, "can not resume a dead thread"), nil + } + th.Parent = ls + ls.G.CurrentThread = th + if !isstarted { + cf := th.stack.Last() + th.currentFrame = cf + th.SetTop(0) + for _, arg := range args { + th.Push(arg) + } + cf.NArgs = len(args) + th.initCallFrame(cf) + th.Panic = panicWithoutTraceback + } else { + for _, arg := range args { + th.Push(arg) + } + } + top := ls.GetTop() + threadRun(th) + haserror := LVIsFalse(ls.Get(top + 1)) + ret := make([]LValue, 0, ls.GetTop()) + for idx := top + 2; idx <= ls.GetTop(); idx++ { + ret = append(ret, ls.Get(idx)) + } + if len(ret) == 0 { + ret = append(ret, LNil) + } + ls.SetTop(top) + + if haserror { + return ResumeError, newApiError(ApiErrorRun, ret[0]), nil + } else if th.stack.IsEmpty() { + return ResumeOK, nil, ret + } + return ResumeYield, nil, ret +} + +func (ls *LState) Yield(values ...LValue) int { + ls.SetTop(0) + for _, lv := range values { + ls.Push(lv) + } + return -1 +} + +func (ls *LState) XMoveTo(other *LState, n int) { + if ls == other { + return + } + top := ls.GetTop() + n = intMin(n, top) + for i := n; i > 0; i-- { + other.Push(ls.Get(top - i + 1)) + } + ls.SetTop(top - n) +} + +/* }}} */ + +/* GopherLua original APIs {{{ */ + +// Set maximum memory size. This function can only be called from the main thread. +func (ls *LState) SetMx(mx int) { + if ls.Parent != nil { + ls.RaiseError("sub threads are not allowed to set a memory limit") + } + go func() { + limit := uint64(mx * 1024 * 1024) //MB + var s runtime.MemStats + for atomic.LoadInt32(&ls.stop) == 0 { + runtime.ReadMemStats(&s) + if s.Alloc >= limit { + fmt.Println("out of memory") + os.Exit(3) + } + time.Sleep(100 * time.Millisecond) + } + }() +} + +// SetContext set a context ctx to this LState. The provided ctx must be non-nil. +func (ls *LState) SetContext(ctx context.Context) { + ls.mainLoop = mainLoopWithContext + ls.ctx = ctx +} + +// Context returns the LState's context. To change the context, use WithContext. +func (ls *LState) Context() context.Context { + return ls.ctx +} + +// RemoveContext removes the context associated with this LState and returns this context. +func (ls *LState) RemoveContext() context.Context { + oldctx := ls.ctx + ls.mainLoop = mainLoop + ls.ctx = nil + return oldctx +} + +// Converts the Lua value at the given acceptable index to the chan LValue. +func (ls *LState) ToChannel(n int) chan LValue { + if lv, ok := ls.Get(n).(LChannel); ok { + return (chan LValue)(lv) + } + return nil +} + +// RemoveCallerFrame removes the stack frame above the current stack frame. This is useful in tail calls. It returns +// the new current frame. +func (ls *LState) RemoveCallerFrame() *callFrame { + cs := ls.stack + sp := cs.Sp() + parentFrame := cs.At(sp - 2) + currentFrame := cs.At(sp - 1) + parentsParentFrame := parentFrame.Parent + *parentFrame = *currentFrame + parentFrame.Parent = parentsParentFrame + parentFrame.Idx = sp - 2 + cs.Pop() + return parentFrame +} + +/* }}} */ + +/* }}} */ + +// diff --git a/vendor/github.com/yuin/gopher-lua/stringlib.go b/vendor/github.com/yuin/gopher-lua/stringlib.go new file mode 100644 index 00000000000..f484c2b33af --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/stringlib.go @@ -0,0 +1,448 @@ +package lua + +import ( + "fmt" + "strings" + + "github.com/yuin/gopher-lua/pm" +) + +const emptyLString LString = LString("") + +func OpenString(L *LState) int { + var mod *LTable + //_, ok := L.G.builtinMts[int(LTString)] + //if !ok { + mod = L.RegisterModule(StringLibName, strFuncs).(*LTable) + gmatch := L.NewClosure(strGmatch, L.NewFunction(strGmatchIter)) + mod.RawSetString("gmatch", gmatch) + mod.RawSetString("gfind", gmatch) + mod.RawSetString("__index", mod) + L.G.builtinMts[int(LTString)] = mod + //} + L.Push(mod) + return 1 +} + +var strFuncs = map[string]LGFunction{ + "byte": strByte, + "char": strChar, + "dump": strDump, + "find": strFind, + "format": strFormat, + "gsub": strGsub, + "len": strLen, + "lower": strLower, + "match": strMatch, + "rep": strRep, + "reverse": strReverse, + "sub": strSub, + "upper": strUpper, +} + +func strByte(L *LState) int { + str := L.CheckString(1) + start := L.OptInt(2, 1) - 1 + end := L.OptInt(3, -1) + l := len(str) + if start < 0 { + start = l + start + 1 + } + if end < 0 { + end = l + end + 1 + } + + if L.GetTop() == 2 { + if start < 0 || start >= l { + return 0 + } + L.Push(LNumber(str[start])) + return 1 + } + + start = intMax(start, 0) + end = intMin(end, l) + if end < 0 || end <= start || start >= l { + return 0 + } + + for i := start; i < end; i++ { + L.Push(LNumber(str[i])) + } + return end - start +} + +func strChar(L *LState) int { + top := L.GetTop() + bytes := make([]byte, L.GetTop()) + for i := 1; i <= top; i++ { + bytes[i-1] = uint8(L.CheckInt(i)) + } + L.Push(LString(string(bytes))) + return 1 +} + +func strDump(L *LState) int { + L.RaiseError("GopherLua does not support the string.dump") + return 0 +} + +func strFind(L *LState) int { + str := L.CheckString(1) + pattern := L.CheckString(2) + if len(pattern) == 0 { + L.Push(LNumber(1)) + L.Push(LNumber(0)) + return 2 + } + init := luaIndex2StringIndex(str, L.OptInt(3, 1), true) + plain := false + if L.GetTop() == 4 { + plain = LVAsBool(L.Get(4)) + } + + if plain { + pos := strings.Index(str[init:], pattern) + if pos < 0 { + L.Push(LNil) + return 1 + } + L.Push(LNumber(init+pos) + 1) + L.Push(LNumber(init + pos + len(pattern))) + return 2 + } + + mds, err := pm.Find(pattern, unsafeFastStringToReadOnlyBytes(str), init, 1) + if err != nil { + L.RaiseError(err.Error()) + } + if len(mds) == 0 { + L.Push(LNil) + return 1 + } + md := mds[0] + L.Push(LNumber(md.Capture(0) + 1)) + L.Push(LNumber(md.Capture(1))) + for i := 2; i < md.CaptureLength(); i += 2 { + if md.IsPosCapture(i) { + L.Push(LNumber(md.Capture(i))) + } else { + L.Push(LString(str[md.Capture(i):md.Capture(i+1)])) + } + } + return md.CaptureLength()/2 + 1 +} + +func strFormat(L *LState) int { + str := L.CheckString(1) + args := make([]interface{}, L.GetTop()-1) + top := L.GetTop() + for i := 2; i <= top; i++ { + args[i-2] = L.Get(i) + } + npat := strings.Count(str, "%") - strings.Count(str, "%%") + L.Push(LString(fmt.Sprintf(str, args[:intMin(npat, len(args))]...))) + return 1 +} + +func strGsub(L *LState) int { + str := L.CheckString(1) + pat := L.CheckString(2) + L.CheckTypes(3, LTString, LTTable, LTFunction) + repl := L.CheckAny(3) + limit := L.OptInt(4, -1) + + mds, err := pm.Find(pat, unsafeFastStringToReadOnlyBytes(str), 0, limit) + if err != nil { + L.RaiseError(err.Error()) + } + if len(mds) == 0 { + L.SetTop(1) + L.Push(LNumber(0)) + return 2 + } + switch lv := repl.(type) { + case LString: + L.Push(LString(strGsubStr(L, str, string(lv), mds))) + case *LTable: + L.Push(LString(strGsubTable(L, str, lv, mds))) + case *LFunction: + L.Push(LString(strGsubFunc(L, str, lv, mds))) + } + L.Push(LNumber(len(mds))) + return 2 +} + +type replaceInfo struct { + Indicies []int + String string +} + +func checkCaptureIndex(L *LState, m *pm.MatchData, idx int) { + if idx <= 2 { + return + } + if idx >= m.CaptureLength() { + L.RaiseError("invalid capture index") + } +} + +func capturedString(L *LState, m *pm.MatchData, str string, idx int) string { + checkCaptureIndex(L, m, idx) + if idx >= m.CaptureLength() && idx == 2 { + idx = 0 + } + if m.IsPosCapture(idx) { + return fmt.Sprint(m.Capture(idx)) + } else { + return str[m.Capture(idx):m.Capture(idx+1)] + } + +} + +func strGsubDoReplace(str string, info []replaceInfo) string { + offset := 0 + buf := []byte(str) + for _, replace := range info { + oldlen := len(buf) + b1 := append([]byte(""), buf[0:offset+replace.Indicies[0]]...) + b2 := []byte("") + index2 := offset + replace.Indicies[1] + if index2 <= len(buf) { + b2 = append(b2, buf[index2:len(buf)]...) + } + buf = append(b1, replace.String...) + buf = append(buf, b2...) + offset += len(buf) - oldlen + } + return string(buf) +} + +func strGsubStr(L *LState, str string, repl string, matches []*pm.MatchData) string { + infoList := make([]replaceInfo, 0, len(matches)) + for _, match := range matches { + start, end := match.Capture(0), match.Capture(1) + sc := newFlagScanner('%', "", "", repl) + for c, eos := sc.Next(); !eos; c, eos = sc.Next() { + if !sc.ChangeFlag { + if sc.HasFlag { + if c >= '0' && c <= '9' { + sc.AppendString(capturedString(L, match, str, 2*(int(c)-48))) + } else { + sc.AppendChar('%') + sc.AppendChar(c) + } + sc.HasFlag = false + } else { + sc.AppendChar(c) + } + } + } + infoList = append(infoList, replaceInfo{[]int{start, end}, sc.String()}) + } + + return strGsubDoReplace(str, infoList) +} + +func strGsubTable(L *LState, str string, repl *LTable, matches []*pm.MatchData) string { + infoList := make([]replaceInfo, 0, len(matches)) + for _, match := range matches { + idx := 0 + if match.CaptureLength() > 2 { // has captures + idx = 2 + } + var value LValue + if match.IsPosCapture(idx) { + value = L.GetTable(repl, LNumber(match.Capture(idx))) + } else { + value = L.GetField(repl, str[match.Capture(idx):match.Capture(idx+1)]) + } + if !LVIsFalse(value) { + infoList = append(infoList, replaceInfo{[]int{match.Capture(0), match.Capture(1)}, LVAsString(value)}) + } + } + return strGsubDoReplace(str, infoList) +} + +func strGsubFunc(L *LState, str string, repl *LFunction, matches []*pm.MatchData) string { + infoList := make([]replaceInfo, 0, len(matches)) + for _, match := range matches { + start, end := match.Capture(0), match.Capture(1) + L.Push(repl) + nargs := 0 + if match.CaptureLength() > 2 { // has captures + for i := 2; i < match.CaptureLength(); i += 2 { + if match.IsPosCapture(i) { + L.Push(LNumber(match.Capture(i))) + } else { + L.Push(LString(capturedString(L, match, str, i))) + } + nargs++ + } + } else { + L.Push(LString(capturedString(L, match, str, 0))) + nargs++ + } + L.Call(nargs, 1) + ret := L.reg.Pop() + if !LVIsFalse(ret) { + infoList = append(infoList, replaceInfo{[]int{start, end}, LVAsString(ret)}) + } + } + return strGsubDoReplace(str, infoList) +} + +type strMatchData struct { + str string + pos int + matches []*pm.MatchData +} + +func strGmatchIter(L *LState) int { + md := L.CheckUserData(1).Value.(*strMatchData) + str := md.str + matches := md.matches + idx := md.pos + md.pos += 1 + if idx == len(matches) { + return 0 + } + L.Push(L.Get(1)) + match := matches[idx] + if match.CaptureLength() == 2 { + L.Push(LString(str[match.Capture(0):match.Capture(1)])) + return 1 + } + + for i := 2; i < match.CaptureLength(); i += 2 { + if match.IsPosCapture(i) { + L.Push(LNumber(match.Capture(i))) + } else { + L.Push(LString(str[match.Capture(i):match.Capture(i+1)])) + } + } + return match.CaptureLength()/2 - 1 +} + +func strGmatch(L *LState) int { + str := L.CheckString(1) + pattern := L.CheckString(2) + mds, err := pm.Find(pattern, []byte(str), 0, -1) + if err != nil { + L.RaiseError(err.Error()) + } + L.Push(L.Get(UpvalueIndex(1))) + ud := L.NewUserData() + ud.Value = &strMatchData{str, 0, mds} + L.Push(ud) + return 2 +} + +func strLen(L *LState) int { + str := L.CheckString(1) + L.Push(LNumber(len(str))) + return 1 +} + +func strLower(L *LState) int { + str := L.CheckString(1) + L.Push(LString(strings.ToLower(str))) + return 1 +} + +func strMatch(L *LState) int { + str := L.CheckString(1) + pattern := L.CheckString(2) + offset := L.OptInt(3, 1) + l := len(str) + if offset < 0 { + offset = l + offset + 1 + } + offset-- + if offset < 0 { + offset = 0 + } + + mds, err := pm.Find(pattern, unsafeFastStringToReadOnlyBytes(str), offset, 1) + if err != nil { + L.RaiseError(err.Error()) + } + if len(mds) == 0 { + L.Push(LNil) + return 0 + } + md := mds[0] + nsubs := md.CaptureLength() / 2 + switch nsubs { + case 1: + L.Push(LString(str[md.Capture(0):md.Capture(1)])) + return 1 + default: + for i := 2; i < md.CaptureLength(); i += 2 { + if md.IsPosCapture(i) { + L.Push(LNumber(md.Capture(i))) + } else { + L.Push(LString(str[md.Capture(i):md.Capture(i+1)])) + } + } + return nsubs - 1 + } +} + +func strRep(L *LState) int { + str := L.CheckString(1) + n := L.CheckInt(2) + if n < 0 { + L.Push(emptyLString) + } else { + L.Push(LString(strings.Repeat(str, n))) + } + return 1 +} + +func strReverse(L *LState) int { + str := L.CheckString(1) + bts := []byte(str) + out := make([]byte, len(bts)) + for i, j := 0, len(bts)-1; j >= 0; i, j = i+1, j-1 { + out[i] = bts[j] + } + L.Push(LString(string(out))) + return 1 +} + +func strSub(L *LState) int { + str := L.CheckString(1) + start := luaIndex2StringIndex(str, L.CheckInt(2), true) + end := luaIndex2StringIndex(str, L.OptInt(3, -1), false) + l := len(str) + if start >= l || end < start { + L.Push(emptyLString) + } else { + L.Push(LString(str[start:end])) + } + return 1 +} + +func strUpper(L *LState) int { + str := L.CheckString(1) + L.Push(LString(strings.ToUpper(str))) + return 1 +} + +func luaIndex2StringIndex(str string, i int, start bool) int { + if start && i != 0 { + i -= 1 + } + l := len(str) + if i < 0 { + i = l + i + 1 + } + i = intMax(0, i) + if !start && i > l { + i = l + } + return i +} + +// diff --git a/vendor/github.com/yuin/gopher-lua/table.go b/vendor/github.com/yuin/gopher-lua/table.go new file mode 100644 index 00000000000..e220bd9c3b6 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/table.go @@ -0,0 +1,387 @@ +package lua + +const defaultArrayCap = 32 +const defaultHashCap = 32 + +type lValueArraySorter struct { + L *LState + Fn *LFunction + Values []LValue +} + +func (lv lValueArraySorter) Len() int { + return len(lv.Values) +} + +func (lv lValueArraySorter) Swap(i, j int) { + lv.Values[i], lv.Values[j] = lv.Values[j], lv.Values[i] +} + +func (lv lValueArraySorter) Less(i, j int) bool { + if lv.Fn != nil { + lv.L.Push(lv.Fn) + lv.L.Push(lv.Values[i]) + lv.L.Push(lv.Values[j]) + lv.L.Call(2, 1) + return LVAsBool(lv.L.reg.Pop()) + } + return lessThan(lv.L, lv.Values[i], lv.Values[j]) +} + +func newLTable(acap int, hcap int) *LTable { + if acap < 0 { + acap = 0 + } + if hcap < 0 { + hcap = 0 + } + tb := <able{} + tb.Metatable = LNil + if acap != 0 { + tb.array = make([]LValue, 0, acap) + } + if hcap != 0 { + tb.strdict = make(map[string]LValue, hcap) + } + return tb +} + +// Len returns length of this LTable. +func (tb *LTable) Len() int { + if tb.array == nil { + return 0 + } + var prev LValue = LNil + for i := len(tb.array) - 1; i >= 0; i-- { + v := tb.array[i] + if prev == LNil && v != LNil { + return i + 1 + } + prev = v + } + return 0 +} + +// Append appends a given LValue to this LTable. +func (tb *LTable) Append(value LValue) { + if value == LNil { + return + } + if tb.array == nil { + tb.array = make([]LValue, 0, defaultArrayCap) + } + if len(tb.array) == 0 || tb.array[len(tb.array)-1] != LNil { + tb.array = append(tb.array, value) + } else { + i := len(tb.array) - 2 + for ; i >= 0; i-- { + if tb.array[i] != LNil { + break + } + } + tb.array[i+1] = value + } +} + +// Insert inserts a given LValue at position `i` in this table. +func (tb *LTable) Insert(i int, value LValue) { + if tb.array == nil { + tb.array = make([]LValue, 0, defaultArrayCap) + } + if i > len(tb.array) { + tb.RawSetInt(i, value) + return + } + if i <= 0 { + tb.RawSet(LNumber(i), value) + return + } + i -= 1 + tb.array = append(tb.array, LNil) + copy(tb.array[i+1:], tb.array[i:]) + tb.array[i] = value +} + +// MaxN returns a maximum number key that nil value does not exist before it. +func (tb *LTable) MaxN() int { + if tb.array == nil { + return 0 + } + for i := len(tb.array) - 1; i >= 0; i-- { + if tb.array[i] != LNil { + return i + 1 + } + } + return 0 +} + +// Remove removes from this table the element at a given position. +func (tb *LTable) Remove(pos int) LValue { + if tb.array == nil { + return LNil + } + larray := len(tb.array) + if larray == 0 { + return LNil + } + i := pos - 1 + oldval := LNil + switch { + case i >= larray: + // nothing to do + case i == larray-1 || i < 0: + oldval = tb.array[larray-1] + tb.array = tb.array[:larray-1] + default: + oldval = tb.array[i] + copy(tb.array[i:], tb.array[i+1:]) + tb.array[larray-1] = nil + tb.array = tb.array[:larray-1] + } + return oldval +} + +// RawSet sets a given LValue to a given index without the __newindex metamethod. +// It is recommended to use `RawSetString` or `RawSetInt` for performance +// if you already know the given LValue is a string or number. +func (tb *LTable) RawSet(key LValue, value LValue) { + switch v := key.(type) { + case LNumber: + if isArrayKey(v) { + if tb.array == nil { + tb.array = make([]LValue, 0, defaultArrayCap) + } + index := int(v) - 1 + alen := len(tb.array) + switch { + case index == alen: + tb.array = append(tb.array, value) + case index > alen: + for i := 0; i < (index - alen); i++ { + tb.array = append(tb.array, LNil) + } + tb.array = append(tb.array, value) + case index < alen: + tb.array[index] = value + } + return + } + case LString: + tb.RawSetString(string(v), value) + return + } + + tb.RawSetH(key, value) +} + +// RawSetInt sets a given LValue at a position `key` without the __newindex metamethod. +func (tb *LTable) RawSetInt(key int, value LValue) { + if key < 1 || key >= MaxArrayIndex { + tb.RawSetH(LNumber(key), value) + return + } + if tb.array == nil { + tb.array = make([]LValue, 0, 32) + } + index := key - 1 + alen := len(tb.array) + switch { + case index == alen: + tb.array = append(tb.array, value) + case index > alen: + for i := 0; i < (index - alen); i++ { + tb.array = append(tb.array, LNil) + } + tb.array = append(tb.array, value) + case index < alen: + tb.array[index] = value + } +} + +// RawSetString sets a given LValue to a given string index without the __newindex metamethod. +func (tb *LTable) RawSetString(key string, value LValue) { + if tb.strdict == nil { + tb.strdict = make(map[string]LValue, defaultHashCap) + } + if tb.keys == nil { + tb.keys = []LValue{} + tb.k2i = map[LValue]int{} + } + + if value == LNil { + // TODO tb.keys and tb.k2i should also be removed + delete(tb.strdict, key) + } else { + tb.strdict[key] = value + lkey := LString(key) + if _, ok := tb.k2i[lkey]; !ok { + tb.k2i[lkey] = len(tb.keys) + tb.keys = append(tb.keys, lkey) + } + } +} + +// RawSetH sets a given LValue to a given index without the __newindex metamethod. +func (tb *LTable) RawSetH(key LValue, value LValue) { + if s, ok := key.(LString); ok { + tb.RawSetString(string(s), value) + return + } + if tb.dict == nil { + tb.dict = make(map[LValue]LValue, len(tb.strdict)) + } + if tb.keys == nil { + tb.keys = []LValue{} + tb.k2i = map[LValue]int{} + } + + if value == LNil { + // TODO tb.keys and tb.k2i should also be removed + delete(tb.dict, key) + } else { + tb.dict[key] = value + if _, ok := tb.k2i[key]; !ok { + tb.k2i[key] = len(tb.keys) + tb.keys = append(tb.keys, key) + } + } +} + +// RawGet returns an LValue associated with a given key without __index metamethod. +func (tb *LTable) RawGet(key LValue) LValue { + switch v := key.(type) { + case LNumber: + if isArrayKey(v) { + if tb.array == nil { + return LNil + } + index := int(v) - 1 + if index >= len(tb.array) { + return LNil + } + return tb.array[index] + } + case LString: + if tb.strdict == nil { + return LNil + } + if ret, ok := tb.strdict[string(v)]; ok { + return ret + } + return LNil + } + if tb.dict == nil { + return LNil + } + if v, ok := tb.dict[key]; ok { + return v + } + return LNil +} + +// RawGetInt returns an LValue at position `key` without __index metamethod. +func (tb *LTable) RawGetInt(key int) LValue { + if tb.array == nil { + return LNil + } + index := int(key) - 1 + if index >= len(tb.array) || index < 0 { + return LNil + } + return tb.array[index] +} + +// RawGet returns an LValue associated with a given key without __index metamethod. +func (tb *LTable) RawGetH(key LValue) LValue { + if s, sok := key.(LString); sok { + if tb.strdict == nil { + return LNil + } + if v, vok := tb.strdict[string(s)]; vok { + return v + } + return LNil + } + if tb.dict == nil { + return LNil + } + if v, ok := tb.dict[key]; ok { + return v + } + return LNil +} + +// RawGetString returns an LValue associated with a given key without __index metamethod. +func (tb *LTable) RawGetString(key string) LValue { + if tb.strdict == nil { + return LNil + } + if v, vok := tb.strdict[string(key)]; vok { + return v + } + return LNil +} + +// ForEach iterates over this table of elements, yielding each in turn to a given function. +func (tb *LTable) ForEach(cb func(LValue, LValue)) { + if tb.array != nil { + for i, v := range tb.array { + if v != LNil { + cb(LNumber(i+1), v) + } + } + } + if tb.strdict != nil { + for k, v := range tb.strdict { + if v != LNil { + cb(LString(k), v) + } + } + } + if tb.dict != nil { + for k, v := range tb.dict { + if v != LNil { + cb(k, v) + } + } + } +} + +// This function is equivalent to lua_next ( http://www.lua.org/manual/5.1/manual.html#lua_next ). +func (tb *LTable) Next(key LValue) (LValue, LValue) { + init := false + if key == LNil { + key = LNumber(0) + init = true + } + + if init || key != LNumber(0) { + if kv, ok := key.(LNumber); ok && isInteger(kv) && int(kv) >= 0 && kv < LNumber(MaxArrayIndex) { + index := int(kv) + if tb.array != nil { + for ; index < len(tb.array); index++ { + if v := tb.array[index]; v != LNil { + return LNumber(index + 1), v + } + } + } + if tb.array == nil || index == len(tb.array) { + if (tb.dict == nil || len(tb.dict) == 0) && (tb.strdict == nil || len(tb.strdict) == 0) { + return LNil, LNil + } + key = tb.keys[0] + if v := tb.RawGetH(key); v != LNil { + return key, v + } + } + } + } + + for i := tb.k2i[key] + 1; i < len(tb.keys); i++ { + key := tb.keys[i] + if v := tb.RawGetH(key); v != LNil { + return key, v + } + } + return LNil, LNil +} diff --git a/vendor/github.com/yuin/gopher-lua/tablelib.go b/vendor/github.com/yuin/gopher-lua/tablelib.go new file mode 100644 index 00000000000..f3f460702f0 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/tablelib.go @@ -0,0 +1,100 @@ +package lua + +import ( + "sort" +) + +func OpenTable(L *LState) int { + tabmod := L.RegisterModule(TabLibName, tableFuncs) + L.Push(tabmod) + return 1 +} + +var tableFuncs = map[string]LGFunction{ + "getn": tableGetN, + "concat": tableConcat, + "insert": tableInsert, + "maxn": tableMaxN, + "remove": tableRemove, + "sort": tableSort, +} + +func tableSort(L *LState) int { + tbl := L.CheckTable(1) + sorter := lValueArraySorter{L, nil, tbl.array} + if L.GetTop() != 1 { + sorter.Fn = L.CheckFunction(2) + } + sort.Sort(sorter) + return 0 +} + +func tableGetN(L *LState) int { + L.Push(LNumber(L.CheckTable(1).Len())) + return 1 +} + +func tableMaxN(L *LState) int { + L.Push(LNumber(L.CheckTable(1).MaxN())) + return 1 +} + +func tableRemove(L *LState) int { + tbl := L.CheckTable(1) + if L.GetTop() == 1 { + L.Push(tbl.Remove(-1)) + } else { + L.Push(tbl.Remove(L.CheckInt(2))) + } + return 1 +} + +func tableConcat(L *LState) int { + tbl := L.CheckTable(1) + sep := LString(L.OptString(2, "")) + i := L.OptInt(3, 1) + j := L.OptInt(4, tbl.Len()) + if L.GetTop() == 3 { + if i > tbl.Len() || i < 1 { + L.Push(emptyLString) + return 1 + } + } + i = intMax(intMin(i, tbl.Len()), 1) + j = intMin(intMin(j, tbl.Len()), tbl.Len()) + if i > j { + L.Push(emptyLString) + return 1 + } + //TODO should flushing? + retbottom := L.GetTop() + for ; i <= j; i++ { + v := tbl.RawGetInt(i) + if !LVCanConvToString(v) { + L.RaiseError("invalid value (%s) at index %d in table for concat", v.Type().String(), i) + } + L.Push(v) + if i != j { + L.Push(sep) + } + } + L.Push(stringConcat(L, L.GetTop()-retbottom, L.reg.Top()-1)) + return 1 +} + +func tableInsert(L *LState) int { + tbl := L.CheckTable(1) + nargs := L.GetTop() + if nargs == 1 { + L.RaiseError("wrong number of arguments") + } + + if L.GetTop() == 2 { + tbl.Append(L.Get(2)) + return 0 + } + tbl.Insert(int(L.CheckInt(2)), L.CheckAny(3)) + return 0 +} + +// diff --git a/vendor/github.com/yuin/gopher-lua/utils.go b/vendor/github.com/yuin/gopher-lua/utils.go new file mode 100644 index 00000000000..2df68dc73eb --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/utils.go @@ -0,0 +1,265 @@ +package lua + +import ( + "bufio" + "fmt" + "io" + "reflect" + "strconv" + "strings" + "time" + "unsafe" +) + +func intMin(a, b int) int { + if a < b { + return a + } else { + return b + } +} + +func intMax(a, b int) int { + if a > b { + return a + } else { + return b + } +} + +func defaultFormat(v interface{}, f fmt.State, c rune) { + buf := make([]string, 0, 10) + buf = append(buf, "%") + for i := 0; i < 128; i++ { + if f.Flag(i) { + buf = append(buf, string(rune(i))) + } + } + + if w, ok := f.Width(); ok { + buf = append(buf, strconv.Itoa(w)) + } + if p, ok := f.Precision(); ok { + buf = append(buf, "."+strconv.Itoa(p)) + } + buf = append(buf, string(c)) + format := strings.Join(buf, "") + fmt.Fprintf(f, format, v) +} + +type flagScanner struct { + flag byte + start string + end string + buf []byte + str string + Length int + Pos int + HasFlag bool + ChangeFlag bool +} + +func newFlagScanner(flag byte, start, end, str string) *flagScanner { + return &flagScanner{flag, start, end, make([]byte, 0, len(str)), str, len(str), 0, false, false} +} + +func (fs *flagScanner) AppendString(str string) { fs.buf = append(fs.buf, str...) } + +func (fs *flagScanner) AppendChar(ch byte) { fs.buf = append(fs.buf, ch) } + +func (fs *flagScanner) String() string { return string(fs.buf) } + +func (fs *flagScanner) Next() (byte, bool) { + c := byte('\000') + fs.ChangeFlag = false + if fs.Pos == fs.Length { + if fs.HasFlag { + fs.AppendString(fs.end) + } + return c, true + } else { + c = fs.str[fs.Pos] + if c == fs.flag { + if fs.Pos < (fs.Length-1) && fs.str[fs.Pos+1] == fs.flag { + fs.HasFlag = false + fs.AppendChar(fs.flag) + fs.Pos += 2 + return fs.Next() + } else if fs.Pos != fs.Length-1 { + if fs.HasFlag { + fs.AppendString(fs.end) + } + fs.AppendString(fs.start) + fs.ChangeFlag = true + fs.HasFlag = true + } + } + } + fs.Pos++ + return c, false +} + +var cDateFlagToGo = map[byte]string{ + 'a': "mon", 'A': "Monday", 'b': "Jan", 'B': "January", 'c': "02 Jan 06 15:04 MST", 'd': "02", + 'F': "2006-01-02", 'H': "15", 'I': "03", 'm': "01", 'M': "04", 'p': "PM", 'P': "pm", 'S': "05", + 'x': "15/04/05", 'X': "15:04:05", 'y': "06", 'Y': "2006", 'z': "-0700", 'Z': "MST"} + +func strftime(t time.Time, cfmt string) string { + sc := newFlagScanner('%', "", "", cfmt) + for c, eos := sc.Next(); !eos; c, eos = sc.Next() { + if !sc.ChangeFlag { + if sc.HasFlag { + if v, ok := cDateFlagToGo[c]; ok { + sc.AppendString(t.Format(v)) + } else { + switch c { + case 'w': + sc.AppendString(fmt.Sprint(int(t.Weekday()))) + default: + sc.AppendChar('%') + sc.AppendChar(c) + } + } + sc.HasFlag = false + } else { + sc.AppendChar(c) + } + } + } + + return sc.String() +} + +func isInteger(v LNumber) bool { + return float64(v) == float64(int64(v)) + //_, frac := math.Modf(float64(v)) + //return frac == 0.0 +} + +func isArrayKey(v LNumber) bool { + return isInteger(v) && v < LNumber(int((^uint(0))>>1)) && v > LNumber(0) && v < LNumber(MaxArrayIndex) +} + +func parseNumber(number string) (LNumber, error) { + var value LNumber + number = strings.Trim(number, " \t\n") + if v, err := strconv.ParseInt(number, 0, LNumberBit); err != nil { + if v2, err2 := strconv.ParseFloat(number, LNumberBit); err2 != nil { + return LNumber(0), err2 + } else { + value = LNumber(v2) + } + } else { + value = LNumber(v) + } + return value, nil +} + +func popenArgs(arg string) (string, []string) { + cmd := "/bin/sh" + args := []string{"-c"} + if LuaOS == "windows" { + cmd = "C:\\Windows\\system32\\cmd.exe" + args = []string{"/c"} + } + args = append(args, arg) + return cmd, args +} + +func isGoroutineSafe(lv LValue) bool { + switch v := lv.(type) { + case *LFunction, *LUserData, *LState: + return false + case *LTable: + return v.Metatable == LNil + default: + return true + } +} + +func readBufioSize(reader *bufio.Reader, size int64) ([]byte, error, bool) { + result := []byte{} + read := int64(0) + var err error + var n int + for read != size { + buf := make([]byte, size-read) + n, err = reader.Read(buf) + if err != nil { + break + } + read += int64(n) + result = append(result, buf[:n]...) + } + e := err + if e != nil && e == io.EOF { + e = nil + } + + return result, e, len(result) == 0 && err == io.EOF +} + +func readBufioLine(reader *bufio.Reader) ([]byte, error, bool) { + result := []byte{} + var buf []byte + var err error + var isprefix bool = true + for isprefix { + buf, isprefix, err = reader.ReadLine() + if err != nil { + break + } + result = append(result, buf...) + } + e := err + if e != nil && e == io.EOF { + e = nil + } + + return result, e, len(result) == 0 && err == io.EOF +} + +func int2Fb(val int) int { + e := 0 + x := val + for x >= 16 { + x = (x + 1) >> 1 + e++ + } + if x < 8 { + return x + } + return ((e + 1) << 3) | (x - 8) +} + +func strCmp(s1, s2 string) int { + len1 := len(s1) + len2 := len(s2) + for i := 0; ; i++ { + c1 := -1 + if i < len1 { + c1 = int(s1[i]) + } + c2 := -1 + if i != len2 { + c2 = int(s2[i]) + } + switch { + case c1 < c2: + return -1 + case c1 > c2: + return +1 + case c1 < 0: + return 0 + } + } +} + +func unsafeFastStringToReadOnlyBytes(s string) (bs []byte) { + sh := (*reflect.StringHeader)(unsafe.Pointer(&s)) + bh := (*reflect.SliceHeader)(unsafe.Pointer(&bs)) + bh.Data = sh.Data + bh.Cap = sh.Len + bh.Len = sh.Len + return +} diff --git a/vendor/github.com/yuin/gopher-lua/value.go b/vendor/github.com/yuin/gopher-lua/value.go new file mode 100644 index 00000000000..0d4af80816e --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/value.go @@ -0,0 +1,247 @@ +package lua + +import ( + "context" + "fmt" + "os" +) + +type LValueType int + +const ( + LTNil LValueType = iota + LTBool + LTNumber + LTString + LTFunction + LTUserData + LTThread + LTTable + LTChannel +) + +var lValueNames = [9]string{"nil", "boolean", "number", "string", "function", "userdata", "thread", "table", "channel"} + +func (vt LValueType) String() string { + return lValueNames[int(vt)] +} + +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. +func LVIsFalse(v LValue) bool { return v == LNil || v == LFalse } + +// LVIsFalse returns false if a given LValue is a nil or false otherwise true. +func LVAsBool(v LValue) bool { return v != LNil && v != LFalse } + +// LVAsString returns string representation of a given LValue +// if the LValue is a string or number, otherwise an empty string. +func LVAsString(v LValue) string { + switch sn := v.(type) { + case LString, LNumber: + return sn.String() + default: + return "" + } +} + +// LVCanConvToString returns true if a given LValue is a string or number +// otherwise false. +func LVCanConvToString(v LValue) bool { + switch v.(type) { + case LString, LNumber: + return true + default: + return false + } +} + +// LVAsNumber tries to convert a given LValue to a number. +func LVAsNumber(v LValue) LNumber { + switch lv := v.(type) { + case LNumber: + return lv + case LString: + if num, err := parseNumber(string(lv)); err == nil { + return num + } + } + return LNumber(0) +} + +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 } + +var LNil = LValue(&LNilType{}) + +type LBool bool + +func (bl LBool) String() string { + if bool(bl) { + return "true" + } + 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 } + +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 } + +// fmt.Formatter interface +func (st LString) Format(f fmt.State, c rune) { + switch c { + case 'd', 'i': + if nm, err := parseNumber(string(st)); err != nil { + defaultFormat(nm, f, 'd') + } else { + defaultFormat(string(st), f, 's') + } + default: + defaultFormat(string(st), f, c) + } +} + +func (nm LNumber) String() string { + if isInteger(nm) { + return fmt.Sprint(int64(nm)) + } + 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 } + +// fmt.Formatter interface +func (nm LNumber) Format(f fmt.State, c rune) { + switch c { + case 'q', 's': + defaultFormat(nm.String(), f, c) + case 'b', 'c', 'd', 'o', 'x', 'X', 'U': + defaultFormat(int64(nm), f, c) + case 'e', 'E', 'f', 'F', 'g', 'G': + defaultFormat(float64(nm), f, c) + case 'i': + defaultFormat(int64(nm), f, 'd') + default: + if isInteger(nm) { + defaultFormat(int64(nm), f, c) + } else { + defaultFormat(float64(nm), f, c) + } + } +} + +type LTable struct { + Metatable LValue + + array []LValue + dict map[LValue]LValue + strdict map[string]LValue + keys []LValue + 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 } + +type LFunction struct { + IsG bool + Env *LTable + Proto *FunctionProto + GFunction LGFunction + Upvalues []*Upvalue +} +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 } + +type Global struct { + MainThread *LState + CurrentThread *LState + Registry *LTable + Global *LTable + + builtinMts map[int]LValue + tempFiles []*os.File + gccount int32 +} + +type LState struct { + G *Global + Parent *LState + Env *LTable + Panic func(*LState) + Dead bool + Options Options + + stop int32 + reg *registry + stack callFrameStack + alloc *allocator + currentFrame *callFrame + wrapped bool + uvcache *Upvalue + hasErrorFunc bool + mainLoop func(*LState, *callFrame) + ctx context.Context +} + +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 } + +type LUserData struct { + Value interface{} + Env *LTable + 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 } + +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 } diff --git a/vendor/github.com/yuin/gopher-lua/vm.go b/vendor/github.com/yuin/gopher-lua/vm.go new file mode 100644 index 00000000000..c3c17bdb776 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/vm.go @@ -0,0 +1,1718 @@ +package lua + +//////////////////////////////////////////////////////// +// This file was generated by go-inline. DO NOT EDIT. // +//////////////////////////////////////////////////////// + +import ( + "fmt" + "math" + "strings" +) + +func mainLoop(L *LState, baseframe *callFrame) { + var inst uint32 + var cf *callFrame + + if L.stack.IsEmpty() { + return + } + + L.currentFrame = L.stack.Last() + if L.currentFrame.Fn.IsG { + callGFunction(L, false) + return + } + + for { + cf = L.currentFrame + inst = cf.Fn.Proto.Code[cf.Pc] + cf.Pc++ + if jumpTable[int(inst>>26)](L, inst, baseframe) == 1 { + return + } + } +} + +func mainLoopWithContext(L *LState, baseframe *callFrame) { + var inst uint32 + var cf *callFrame + + if L.stack.IsEmpty() { + return + } + + L.currentFrame = L.stack.Last() + if L.currentFrame.Fn.IsG { + callGFunction(L, false) + return + } + + for { + cf = L.currentFrame + inst = cf.Fn.Proto.Code[cf.Pc] + cf.Pc++ + select { + case <-L.ctx.Done(): + L.RaiseError(L.ctx.Err().Error()) + return + default: + if jumpTable[int(inst>>26)](L, inst, baseframe) == 1 { + return + } + } + } +} + +// regv is the first target register to copy the return values to. +// It can be reg.top, indicating that the copied values are going into new registers, or it can be below reg.top +// Indicating that the values should be within the existing registers. +// b is the available number of return values + 1. +// n is the desired number of return values. +// If n more than the available return values then the extra values are set to nil. +// When this function returns the top of the registry will be set to regv+n. +func copyReturnValues(L *LState, regv, start, n, b int) { // +inline-start + if b == 1 { + // this section is inlined by go-inline + // source function is 'func (rg *registry) FillNil(regm, n int) ' in '_state.go' + { + rg := L.reg + regm := regv + newSize := regm + n + // 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) + } + } + for i := 0; i < n; i++ { + rg.array[regm+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 + oldtop := rg.top + rg.top = regm + n + if rg.top < oldtop { + nilRange := rg.array[rg.top:oldtop] + for i := range nilRange { + nilRange[i] = nil + } + } + } + } else { + // this section is inlined by go-inline + // source function is 'func (rg *registry) CopyRange(regv, start, limit, n int) ' in '_state.go' + { + rg := L.reg + limit := -1 + newSize := regv + n + // 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) + } + } + if limit == -1 || limit > rg.top { + limit = rg.top + } + for i := 0; i < n; i++ { + srcIdx := start + i + if srcIdx >= limit || srcIdx < 0 { + rg.array[regv+i] = LNil + } else { + rg.array[regv+i] = rg.array[srcIdx] + } + } + + // 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 + oldtop := rg.top + rg.top = regv + n + if rg.top < oldtop { + nilRange := rg.array[rg.top:oldtop] + for i := range nilRange { + nilRange[i] = nil + } + } + } + if b > 1 && n > (b-1) { + // this section is inlined by go-inline + // source function is 'func (rg *registry) FillNil(regm, n int) ' in '_state.go' + { + rg := L.reg + regm := regv + b - 1 + n := n - (b - 1) + newSize := regm + n + // 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) + } + } + for i := 0; i < n; i++ { + rg.array[regm+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 + oldtop := rg.top + rg.top = regm + n + if rg.top < oldtop { + nilRange := rg.array[rg.top:oldtop] + for i := range nilRange { + nilRange[i] = nil + } + } + } + } + } +} // +inline-end + +func switchToParentThread(L *LState, nargs int, haserror bool, kill bool) { + parent := L.Parent + if parent == nil { + L.RaiseError("can not yield from outside of a coroutine") + } + L.G.CurrentThread = parent + L.Parent = nil + if !L.wrapped { + if haserror { + parent.Push(LFalse) + } else { + parent.Push(LTrue) + } + } + L.XMoveTo(parent, nargs) + L.stack.Pop() + offset := L.currentFrame.LocalBase - L.currentFrame.ReturnBase + L.currentFrame = L.stack.Last() + L.reg.SetTop(L.reg.Top() - offset) // remove 'yield' function(including tailcalled functions) + if kill { + L.kill() + } +} + +func callGFunction(L *LState, tailcall bool) bool { + frame := L.currentFrame + gfnret := frame.Fn.GFunction(L) + if tailcall { + L.currentFrame = L.RemoveCallerFrame() + } + + if gfnret < 0 { + switchToParentThread(L, L.GetTop(), false, false) + return true + } + + wantret := frame.NRet + if wantret == MultRet { + wantret = gfnret + } + + if tailcall && L.Parent != nil && L.stack.Sp() == 1 { + switchToParentThread(L, wantret, false, true) + return true + } + + // this section is inlined by go-inline + // source function is 'func (rg *registry) CopyRange(regv, start, limit, n int) ' in '_state.go' + { + rg := L.reg + regv := frame.ReturnBase + start := L.reg.Top() - gfnret + limit := -1 + n := wantret + newSize := regv + n + // 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) + } + } + if limit == -1 || limit > rg.top { + limit = rg.top + } + for i := 0; i < n; i++ { + srcIdx := start + i + if srcIdx >= limit || srcIdx < 0 { + rg.array[regv+i] = LNil + } else { + rg.array[regv+i] = rg.array[srcIdx] + } + } + + // 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 + oldtop := rg.top + rg.top = regv + n + if rg.top < oldtop { + nilRange := rg.array[rg.top:oldtop] + for i := range nilRange { + nilRange[i] = nil + } + } + } + L.stack.Pop() + L.currentFrame = L.stack.Last() + return false +} + +func threadRun(L *LState) { + if L.stack.IsEmpty() { + return + } + + defer func() { + if rcv := recover(); rcv != nil { + var lv LValue + if v, ok := rcv.(*ApiError); ok { + lv = v.Object + } else { + lv = LString(fmt.Sprint(rcv)) + } + if parent := L.Parent; parent != nil { + if L.wrapped { + L.Push(lv) + parent.Panic(L) + } else { + L.SetTop(0) + L.Push(lv) + switchToParentThread(L, 1, true, true) + } + } else { + panic(rcv) + } + } + }() + L.mainLoop(L, nil) +} + +type instFunc func(*LState, uint32, *callFrame) int + +var jumpTable [opCodeMax + 1]instFunc + +func init() { + jumpTable = [opCodeMax + 1]instFunc{ + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_MOVE + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + reg.Set(RA, reg.Get(lbase+B)) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_MOVEN + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + 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)) + code := cf.Fn.Proto.Code + pc := cf.Pc + for i := 0; i < C; i++ { + inst = code[pc] + pc++ + A = int(inst>>18) & 0xff //GETA + B = int(inst & 0x1ff) //GETB + reg.Set(lbase+A, reg.Get(lbase+B)) + } + cf.Pc = pc + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_LOADK + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + Bx := int(inst & 0x3ffff) //GETBX + reg.Set(RA, cf.Fn.Proto.Constants[Bx]) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_LOADBOOL + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + if B != 0 { + reg.Set(RA, LTrue) + } else { + reg.Set(RA, LFalse) + } + if C != 0 { + cf.Pc++ + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_LOADNIL + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + for i := RA; i <= lbase+B; i++ { + reg.Set(i, LNil) + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_GETUPVAL + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + reg.Set(RA, cf.Fn.Upvalues[B].Value()) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_GETGLOBAL + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + 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])) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_GETTABLE + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + 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))) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_GETTABLEKS + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + 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))) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_SETGLOBAL + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + Bx := int(inst & 0x3ffff) //GETBX + //L.setField(cf.Fn.Env, cf.Fn.Proto.Constants[Bx], reg.Get(RA)) + L.setFieldString(cf.Fn.Env, cf.Fn.Proto.stringConstants[Bx], reg.Get(RA)) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_SETUPVAL + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + cf.Fn.Upvalues[B].SetValue(reg.Get(RA)) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_SETTABLE + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + L.setField(reg.Get(RA), L.rkValue(B), L.rkValue(C)) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_SETTABLEKS + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + L.setFieldString(reg.Get(RA), L.rkString(B), L.rkValue(C)) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_NEWTABLE + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + reg.Set(RA, newLTable(B, C)) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_SELF + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + 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) + return 0 + }, + opArith, // OP_ADD + opArith, // OP_SUB + opArith, // OP_MUL + opArith, // OP_DIV + opArith, // OP_MOD + opArith, // OP_POW + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_UNM + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + unaryv := L.rkValue(B) + if nm, ok := unaryv.(LNumber); ok { + reg.SetNumber(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()) + } else if str, ok1 := unaryv.(LString); ok1 { + if num, err := parseNumber(string(str)); err == nil { + reg.Set(RA, -num) + } else { + L.RaiseError("__unm undefined") + } + } else { + L.RaiseError("__unm undefined") + } + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_NOT + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + if LVIsFalse(reg.Get(lbase + B)) { + reg.Set(RA, LTrue) + } else { + reg.Set(RA, LFalse) + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_LEN + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + switch lv := L.rkValue(B).(type) { + case LString: + reg.SetNumber(RA, LNumber(len(lv))) + default: + op := L.metaOp1(lv, "__len") + if op.Type() == LTFunction { + reg.Push(op) + reg.Push(lv) + L.Call(1, 1) + ret := reg.Pop() + if ret.Type() == LTNumber { + reg.SetNumber(RA, ret.(LNumber)) + } else { + reg.SetNumber(RA, LNumber(0)) + } + } else if lv.Type() == LTTable { + reg.SetNumber(RA, LNumber(lv.(*LTable).Len())) + } else { + L.RaiseError("__len undefined") + } + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_CONCAT + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + RC := lbase + C + RB := lbase + B + reg.Set(RA, stringConcat(L, RC-RB+1, RC)) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_JMP + cf := L.currentFrame + Sbx := int(inst&0x3ffff) - opMaxArgSbx //GETSBX + cf.Pc += Sbx + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_EQ + cf := L.currentFrame + A := int(inst>>18) & 0xff //GETA + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + ret := equals(L, L.rkValue(B), L.rkValue(C), false) + v := 1 + if ret { + v = 0 + } + if v == A { + cf.Pc++ + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_LT + cf := L.currentFrame + A := int(inst>>18) & 0xff //GETA + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + ret := lessThan(L, L.rkValue(B), L.rkValue(C)) + v := 1 + if ret { + v = 0 + } + if v == A { + cf.Pc++ + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_LE + cf := L.currentFrame + A := int(inst>>18) & 0xff //GETA + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + lhs := L.rkValue(B) + rhs := L.rkValue(C) + ret := false + + if v1, ok1 := lhs.assertFloat64(); ok1 { + if v2, ok2 := rhs.assertFloat64(); ok2 { + ret = v1 <= v2 + } else { + L.RaiseError("attempt to compare %v with %v", lhs.Type().String(), rhs.Type().String()) + } + } else { + if lhs.Type() != rhs.Type() { + L.RaiseError("attempt to compare %v with %v", lhs.Type().String(), rhs.Type().String()) + } + switch lhs.Type() { + case LTString: + ret = strCmp(string(lhs.(LString)), string(rhs.(LString))) <= 0 + default: + switch objectRational(L, lhs, rhs, "__le") { + case 1: + ret = true + case 0: + ret = false + default: + ret = !objectRationalWithError(L, rhs, lhs, "__lt") + } + } + } + + v := 1 + if ret { + v = 0 + } + if v == A { + cf.Pc++ + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_TEST + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + C := int(inst>>9) & 0x1ff //GETC + if LVAsBool(reg.Get(RA)) == (C == 0) { + cf.Pc++ + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_TESTSET + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + 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) + } else { + cf.Pc++ + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_CALL + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + nargs := B - 1 + if B == 0 { + nargs = reg.Top() - (RA + 1) + } + lv := reg.Get(RA) + nret := C - 1 + var callable *LFunction + var meta bool + if fn, ok := lv.assertFunction(); ok { + callable = fn + meta = false + } else { + callable, meta = L.metaCall(lv) + } + // this section is inlined by go-inline + // source function is 'func (ls *LState) pushCallFrame(cf callFrame, fn LValue, meta bool) ' in '_state.go' + { + ls := L + cf := callFrame{Fn: callable, Pc: 0, Base: RA, LocalBase: RA + 1, ReturnBase: RA, NArgs: nargs, NRet: nret, Parent: cf, TailCall: 0} + fn := lv + if meta { + cf.NArgs++ + ls.reg.Insert(fn, cf.LocalBase) + } + if cf.Fn == nil { + ls.RaiseError("attempt to call a non-function object") + } + if ls.stack.IsFull() { + ls.RaiseError("stack overflow") + } + ls.stack.Push(cf) + newcf := ls.stack.Last() + // this section is inlined by go-inline + // source function is 'func (ls *LState) initCallFrame(cf *callFrame) ' in '_state.go' + { + cf := newcf + if cf.Fn.IsG { + ls.reg.SetTop(cf.LocalBase + cf.NArgs) + } else { + proto := cf.Fn.Proto + nargs := cf.NArgs + np := int(proto.NumParameters) + newSize := cf.LocalBase + np + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + rg := ls.reg + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + for i := nargs; i < np; i++ { + ls.reg.array[cf.LocalBase+i] = LNil + nargs = np + } + + if (proto.IsVarArg & VarArgIsVarArg) == 0 { + if nargs < int(proto.NumUsedRegisters) { + nargs = int(proto.NumUsedRegisters) + } + newSize = cf.LocalBase + nargs + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + rg := ls.reg + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + for i := np; i < nargs; i++ { + ls.reg.array[cf.LocalBase+i] = LNil + } + ls.reg.top = cf.LocalBase + int(proto.NumUsedRegisters) + } else { + /* swap vararg positions: + closure + namedparam1 <- lbase + namedparam2 + vararg1 + vararg2 + + TO + + closure + nil + nil + vararg1 + vararg2 + namedparam1 <- lbase + namedparam2 + */ + nvarargs := nargs - np + if nvarargs < 0 { + nvarargs = 0 + } + + ls.reg.SetTop(cf.LocalBase + nargs + np) + for i := 0; i < np; i++ { + //ls.reg.Set(cf.LocalBase+nargs+i, ls.reg.Get(cf.LocalBase+i)) + ls.reg.array[cf.LocalBase+nargs+i] = ls.reg.array[cf.LocalBase+i] + //ls.reg.Set(cf.LocalBase+i, LNil) + ls.reg.array[cf.LocalBase+i] = LNil + } + + if CompatVarArg { + ls.reg.SetTop(cf.LocalBase + nargs + np + 1) + if (proto.IsVarArg & VarArgNeedsArg) != 0 { + argtb := newLTable(nvarargs, 0) + for i := 0; i < nvarargs; i++ { + argtb.RawSetInt(i+1, ls.reg.Get(cf.LocalBase+np+i)) + } + argtb.RawSetString("n", LNumber(nvarargs)) + //ls.reg.Set(cf.LocalBase+nargs+np, argtb) + ls.reg.array[cf.LocalBase+nargs+np] = argtb + } else { + ls.reg.array[cf.LocalBase+nargs+np] = LNil + } + } + cf.LocalBase += nargs + maxreg := cf.LocalBase + int(proto.NumUsedRegisters) + ls.reg.SetTop(maxreg) + } + } + } + ls.currentFrame = newcf + } + if callable.IsG && callGFunction(L, false) { + return 1 + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_TAILCALL + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + nargs := B - 1 + if B == 0 { + nargs = reg.Top() - (RA + 1) + } + lv := reg.Get(RA) + var callable *LFunction + var meta bool + if fn, ok := lv.assertFunction(); ok { + callable = fn + meta = false + } else { + callable, meta = L.metaCall(lv) + } + if callable == nil { + L.RaiseError("attempt to call a non-function object") + } + // this section is inlined by go-inline + // source function is 'func (ls *LState) closeUpvalues(idx int) ' in '_state.go' + { + ls := L + idx := lbase + if ls.uvcache != nil { + var prev *Upvalue + for uv := ls.uvcache; uv != nil; uv = uv.next { + if uv.index >= idx { + if prev != nil { + prev.next = nil + } else { + ls.uvcache = nil + } + uv.Close() + } + prev = uv + } + } + } + if callable.IsG { + luaframe := cf + L.pushCallFrame(callFrame{ + Fn: callable, + Pc: 0, + Base: RA, + LocalBase: RA + 1, + ReturnBase: cf.ReturnBase, + NArgs: nargs, + NRet: cf.NRet, + Parent: cf, + TailCall: 0, + }, lv, meta) + if callGFunction(L, true) { + return 1 + } + if L.currentFrame == nil || L.currentFrame.Fn.IsG || luaframe == baseframe { + return 1 + } + } else { + base := cf.Base + cf.Fn = callable + cf.Pc = 0 + cf.Base = RA + cf.LocalBase = RA + 1 + cf.ReturnBase = cf.ReturnBase + cf.NArgs = nargs + cf.NRet = cf.NRet + cf.TailCall++ + lbase := cf.LocalBase + if meta { + cf.NArgs++ + L.reg.Insert(lv, cf.LocalBase) + } + // this section is inlined by go-inline + // source function is 'func (ls *LState) initCallFrame(cf *callFrame) ' in '_state.go' + { + ls := L + if cf.Fn.IsG { + ls.reg.SetTop(cf.LocalBase + cf.NArgs) + } else { + proto := cf.Fn.Proto + nargs := cf.NArgs + np := int(proto.NumParameters) + newSize := cf.LocalBase + np + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + rg := ls.reg + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + for i := nargs; i < np; i++ { + ls.reg.array[cf.LocalBase+i] = LNil + nargs = np + } + + if (proto.IsVarArg & VarArgIsVarArg) == 0 { + if nargs < int(proto.NumUsedRegisters) { + nargs = int(proto.NumUsedRegisters) + } + newSize = cf.LocalBase + nargs + // this section is inlined by go-inline + // source function is 'func (rg *registry) checkSize(requiredSize int) ' in '_state.go' + { + rg := ls.reg + requiredSize := newSize + if requiredSize > cap(rg.array) { + rg.resize(requiredSize) + } + } + for i := np; i < nargs; i++ { + ls.reg.array[cf.LocalBase+i] = LNil + } + ls.reg.top = cf.LocalBase + int(proto.NumUsedRegisters) + } else { + /* swap vararg positions: + closure + namedparam1 <- lbase + namedparam2 + vararg1 + vararg2 + + TO + + closure + nil + nil + vararg1 + vararg2 + namedparam1 <- lbase + namedparam2 + */ + nvarargs := nargs - np + if nvarargs < 0 { + nvarargs = 0 + } + + ls.reg.SetTop(cf.LocalBase + nargs + np) + for i := 0; i < np; i++ { + //ls.reg.Set(cf.LocalBase+nargs+i, ls.reg.Get(cf.LocalBase+i)) + ls.reg.array[cf.LocalBase+nargs+i] = ls.reg.array[cf.LocalBase+i] + //ls.reg.Set(cf.LocalBase+i, LNil) + ls.reg.array[cf.LocalBase+i] = LNil + } + + if CompatVarArg { + ls.reg.SetTop(cf.LocalBase + nargs + np + 1) + if (proto.IsVarArg & VarArgNeedsArg) != 0 { + argtb := newLTable(nvarargs, 0) + for i := 0; i < nvarargs; i++ { + argtb.RawSetInt(i+1, ls.reg.Get(cf.LocalBase+np+i)) + } + argtb.RawSetString("n", LNumber(nvarargs)) + //ls.reg.Set(cf.LocalBase+nargs+np, argtb) + ls.reg.array[cf.LocalBase+nargs+np] = argtb + } else { + ls.reg.array[cf.LocalBase+nargs+np] = LNil + } + } + cf.LocalBase += nargs + maxreg := cf.LocalBase + int(proto.NumUsedRegisters) + ls.reg.SetTop(maxreg) + } + } + } + // this section is inlined by go-inline + // source function is 'func (rg *registry) CopyRange(regv, start, limit, n int) ' in '_state.go' + { + rg := L.reg + regv := base + start := RA + limit := -1 + n := reg.Top() - RA - 1 + newSize := regv + n + // 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) + } + } + if limit == -1 || limit > rg.top { + limit = rg.top + } + for i := 0; i < n; i++ { + srcIdx := start + i + if srcIdx >= limit || srcIdx < 0 { + rg.array[regv+i] = LNil + } else { + rg.array[regv+i] = rg.array[srcIdx] + } + } + + // 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 + oldtop := rg.top + rg.top = regv + n + if rg.top < oldtop { + nilRange := rg.array[rg.top:oldtop] + for i := range nilRange { + nilRange[i] = nil + } + } + } + cf.Base = base + cf.LocalBase = base + (cf.LocalBase - lbase + 1) + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_RETURN + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + // this section is inlined by go-inline + // source function is 'func (ls *LState) closeUpvalues(idx int) ' in '_state.go' + { + ls := L + idx := lbase + if ls.uvcache != nil { + var prev *Upvalue + for uv := ls.uvcache; uv != nil; uv = uv.next { + if uv.index >= idx { + if prev != nil { + prev.next = nil + } else { + ls.uvcache = nil + } + uv.Close() + } + prev = uv + } + } + } + nret := B - 1 + if B == 0 { + nret = reg.Top() - RA + } + n := cf.NRet + if cf.NRet == MultRet { + n = nret + } + + if L.Parent != nil && L.stack.Sp() == 1 { + // this section is inlined by go-inline + // source function is 'func copyReturnValues(L *LState, regv, start, n, b int) ' in '_vm.go' + { + regv := reg.Top() + start := RA + b := B + if b == 1 { + // this section is inlined by go-inline + // source function is 'func (rg *registry) FillNil(regm, n int) ' in '_state.go' + { + rg := L.reg + regm := regv + newSize := regm + n + // 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) + } + } + for i := 0; i < n; i++ { + rg.array[regm+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 + oldtop := rg.top + rg.top = regm + n + if rg.top < oldtop { + nilRange := rg.array[rg.top:oldtop] + for i := range nilRange { + nilRange[i] = nil + } + } + } + } else { + // this section is inlined by go-inline + // source function is 'func (rg *registry) CopyRange(regv, start, limit, n int) ' in '_state.go' + { + rg := L.reg + limit := -1 + newSize := regv + n + // 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) + } + } + if limit == -1 || limit > rg.top { + limit = rg.top + } + for i := 0; i < n; i++ { + srcIdx := start + i + if srcIdx >= limit || srcIdx < 0 { + rg.array[regv+i] = LNil + } else { + rg.array[regv+i] = rg.array[srcIdx] + } + } + + // 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 + oldtop := rg.top + rg.top = regv + n + if rg.top < oldtop { + nilRange := rg.array[rg.top:oldtop] + for i := range nilRange { + nilRange[i] = nil + } + } + } + if b > 1 && n > (b-1) { + // this section is inlined by go-inline + // source function is 'func (rg *registry) FillNil(regm, n int) ' in '_state.go' + { + rg := L.reg + regm := regv + b - 1 + n := n - (b - 1) + newSize := regm + n + // 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) + } + } + for i := 0; i < n; i++ { + rg.array[regm+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 + oldtop := rg.top + rg.top = regm + n + if rg.top < oldtop { + nilRange := rg.array[rg.top:oldtop] + for i := range nilRange { + nilRange[i] = nil + } + } + } + } + } + } + switchToParentThread(L, n, false, true) + return 1 + } + islast := baseframe == L.stack.Pop() || L.stack.IsEmpty() + // this section is inlined by go-inline + // source function is 'func copyReturnValues(L *LState, regv, start, n, b int) ' in '_vm.go' + { + regv := cf.ReturnBase + start := RA + b := B + if b == 1 { + // this section is inlined by go-inline + // source function is 'func (rg *registry) FillNil(regm, n int) ' in '_state.go' + { + rg := L.reg + regm := regv + newSize := regm + n + // 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) + } + } + for i := 0; i < n; i++ { + rg.array[regm+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 + oldtop := rg.top + rg.top = regm + n + if rg.top < oldtop { + nilRange := rg.array[rg.top:oldtop] + for i := range nilRange { + nilRange[i] = nil + } + } + } + } else { + // this section is inlined by go-inline + // source function is 'func (rg *registry) CopyRange(regv, start, limit, n int) ' in '_state.go' + { + rg := L.reg + limit := -1 + newSize := regv + n + // 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) + } + } + if limit == -1 || limit > rg.top { + limit = rg.top + } + for i := 0; i < n; i++ { + srcIdx := start + i + if srcIdx >= limit || srcIdx < 0 { + rg.array[regv+i] = LNil + } else { + rg.array[regv+i] = rg.array[srcIdx] + } + } + + // 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 + oldtop := rg.top + rg.top = regv + n + if rg.top < oldtop { + nilRange := rg.array[rg.top:oldtop] + for i := range nilRange { + nilRange[i] = nil + } + } + } + if b > 1 && n > (b-1) { + // this section is inlined by go-inline + // source function is 'func (rg *registry) FillNil(regm, n int) ' in '_state.go' + { + rg := L.reg + regm := regv + b - 1 + n := n - (b - 1) + newSize := regm + n + // 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) + } + } + for i := 0; i < n; i++ { + rg.array[regm+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 + oldtop := rg.top + rg.top = regm + n + if rg.top < oldtop { + nilRange := rg.array[rg.top:oldtop] + for i := range nilRange { + nilRange[i] = nil + } + } + } + } + } + } + L.currentFrame = L.stack.Last() + if islast || L.currentFrame == nil || L.currentFrame.Fn.IsG { + return 1 + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_FORLOOP + reg := L.reg + cf := L.currentFrame + 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 { + init += step + reg.SetNumber(RA, LNumber(init)) + if (step > 0 && init <= limit) || (step <= 0 && init >= limit) { + Sbx := int(inst&0x3ffff) - opMaxArgSbx //GETSBX + cf.Pc += Sbx + reg.SetNumber(RA+3, LNumber(init)) + } else { + reg.SetTop(RA + 1) + } + } else { + L.RaiseError("for statement step must be a number") + } + } else { + L.RaiseError("for statement limit must be a number") + } + } else { + L.RaiseError("for statement init must be a number") + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_FORPREP + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + 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)) + } else { + L.RaiseError("for statement step must be a number") + } + } else { + L.RaiseError("for statement init must be a number") + } + cf.Pc += Sbx + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_TFORLOOP + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + 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)) + L.callR(2, nret, RA+3) + if value := reg.Get(RA + 3); value != LNil { + reg.Set(RA+2, value) + pc := cf.Fn.Proto.Code[cf.Pc] + cf.Pc += int(pc&0x3ffff) - opMaxArgSbx + } + cf.Pc++ + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_SETLIST + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + if C == 0 { + C = int(cf.Fn.Proto.Code[cf.Pc]) + cf.Pc++ + } + offset := (C - 1) * FieldsPerFlush + table := reg.Get(RA).(*LTable) + nelem := B + if B == 0 { + nelem = reg.Top() - RA - 1 + } + for i := 1; i <= nelem; i++ { + table.RawSetInt(offset+i, reg.Get(RA+i)) + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_CLOSE + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + // this section is inlined by go-inline + // source function is 'func (ls *LState) closeUpvalues(idx int) ' in '_state.go' + { + ls := L + idx := RA + if ls.uvcache != nil { + var prev *Upvalue + for uv := ls.uvcache; uv != nil; uv = uv.next { + if uv.index >= idx { + if prev != nil { + prev.next = nil + } else { + ls.uvcache = nil + } + uv.Close() + } + prev = uv + } + } + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_CLOSURE + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + Bx := int(inst & 0x3ffff) //GETBX + proto := cf.Fn.Proto.FunctionPrototypes[Bx] + closure := newLFunctionL(proto, cf.Fn.Env, int(proto.NumUpvalues)) + reg.Set(RA, closure) + for i := 0; i < int(proto.NumUpvalues); i++ { + inst = cf.Fn.Proto.Code[cf.Pc] + cf.Pc++ + B := opGetArgB(inst) + switch opGetOpCode(inst) { + case OP_MOVE: + closure.Upvalues[i] = L.findUpvalue(lbase + B) + case OP_GETUPVAL: + closure.Upvalues[i] = cf.Fn.Upvalues[B] + } + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_VARARG + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + nparams := int(cf.Fn.Proto.NumParameters) + nvarargs := cf.NArgs - nparams + if nvarargs < 0 { + nvarargs = 0 + } + nwant := B - 1 + if B == 0 { + nwant = nvarargs + } + // this section is inlined by go-inline + // source function is 'func (rg *registry) CopyRange(regv, start, limit, n int) ' in '_state.go' + { + rg := reg + regv := RA + start := cf.Base + nparams + 1 + limit := cf.LocalBase + n := nwant + newSize := regv + n + // 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) + } + } + if limit == -1 || limit > rg.top { + limit = rg.top + } + for i := 0; i < n; i++ { + srcIdx := start + i + if srcIdx >= limit || srcIdx < 0 { + rg.array[regv+i] = LNil + } else { + rg.array[regv+i] = rg.array[srcIdx] + } + } + + // 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 + oldtop := rg.top + rg.top = regv + n + if rg.top < oldtop { + nilRange := rg.array[rg.top:oldtop] + for i := range nilRange { + nilRange[i] = nil + } + } + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_NOP + return 0 + }, + } +} + +func opArith(L *LState, inst uint32, baseframe *callFrame) int { //OP_ADD, OP_SUB, OP_MUL, OP_DIV, OP_MOD, OP_POW + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + opcode := int(inst >> 26) //GETOPCODE + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + lhs := L.rkValue(B) + rhs := L.rkValue(C) + v1, ok1 := lhs.assertFloat64() + v2, ok2 := rhs.assertFloat64() + if ok1 && ok2 { + reg.SetNumber(RA, numberArith(L, opcode, LNumber(v1), LNumber(v2))) + } else { + reg.Set(RA, objectArith(L, opcode, lhs, rhs)) + } + return 0 +} + +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) { + v += frhs + } + return LNumber(v) +} + +func numberArith(L *LState, opcode int, lhs, rhs LNumber) LNumber { + switch opcode { + case OP_ADD: + return lhs + rhs + case OP_SUB: + return lhs - rhs + case OP_MUL: + return lhs * rhs + case OP_DIV: + return lhs / rhs + case OP_MOD: + return luaModulo(lhs, rhs) + case OP_POW: + flhs := float64(lhs) + frhs := float64(rhs) + return LNumber(math.Pow(flhs, frhs)) + } + panic("should not reach here") + return LNumber(0) +} + +func objectArith(L *LState, opcode int, lhs, rhs LValue) LValue { + event := "" + switch opcode { + case OP_ADD: + event = "__add" + case OP_SUB: + event = "__sub" + case OP_MUL: + event = "__mul" + case OP_DIV: + event = "__div" + case OP_MOD: + event = "__mod" + case OP_POW: + event = "__pow" + } + op := L.metaOp2(lhs, rhs, event) + if op.Type() == LTFunction { + L.reg.Push(op) + L.reg.Push(lhs) + L.reg.Push(rhs) + L.Call(2, 1) + return L.reg.Pop() + } + if str, ok := lhs.(LString); ok { + if lnum, err := parseNumber(string(str)); err == nil { + lhs = lnum + } + } + if str, ok := rhs.(LString); ok { + if rnum, err := parseNumber(string(str)); err == nil { + rhs = rnum + } + } + if v1, ok1 := lhs.assertFloat64(); ok1 { + if v2, ok2 := rhs.assertFloat64(); ok2 { + return numberArith(L, opcode, LNumber(v1), LNumber(v2)) + } + } + L.RaiseError(fmt.Sprintf("cannot perform %v operation between %v and %v", + strings.TrimLeft(event, "_"), lhs.Type().String(), rhs.Type().String())) + + return LNil +} + +func stringConcat(L *LState, total, last int) LValue { + rhs := L.reg.Get(last) + total-- + for i := last - 1; total > 0; { + lhs := L.reg.Get(i) + if !(LVCanConvToString(lhs) && LVCanConvToString(rhs)) { + op := L.metaOp2(lhs, rhs, "__concat") + if op.Type() == LTFunction { + L.reg.Push(op) + L.reg.Push(lhs) + L.reg.Push(rhs) + L.Call(2, 1) + rhs = L.reg.Pop() + total-- + i-- + } else { + L.RaiseError("cannot perform concat operation between %v and %v", lhs.Type().String(), rhs.Type().String()) + return LNil + } + } else { + buf := make([]string, total+1) + buf[total] = LVAsString(rhs) + for total > 0 { + lhs = L.reg.Get(i) + if !LVCanConvToString(lhs) { + break + } + buf[total-1] = LVAsString(lhs) + i-- + total-- + } + rhs = LString(strings.Join(buf, "")) + } + } + return rhs +} + +func lessThan(L *LState, lhs, rhs LValue) bool { + // optimization for numbers + if v1, ok1 := lhs.assertFloat64(); ok1 { + if v2, ok2 := rhs.assertFloat64(); ok2 { + return v1 < v2 + } + L.RaiseError("attempt to compare %v with %v", lhs.Type().String(), rhs.Type().String()) + } + if lhs.Type() != rhs.Type() { + L.RaiseError("attempt to compare %v with %v", lhs.Type().String(), rhs.Type().String()) + return false + } + ret := false + switch lhs.Type() { + case LTString: + ret = strCmp(string(lhs.(LString)), string(rhs.(LString))) < 0 + default: + ret = objectRationalWithError(L, lhs, rhs, "__lt") + } + return ret +} + +func equals(L *LState, lhs, rhs LValue, raw bool) bool { + if lhs.Type() != rhs.Type() { + return false + } + + ret := false + switch lhs.Type() { + case LTNil: + ret = true + case LTNumber: + v1, _ := lhs.assertFloat64() + v2, _ := rhs.assertFloat64() + ret = v1 == v2 + case LTBool: + ret = bool(lhs.(LBool)) == bool(rhs.(LBool)) + case LTString: + ret = string(lhs.(LString)) == string(rhs.(LString)) + case LTUserData, LTTable: + if lhs == rhs { + ret = true + } else if !raw { + switch objectRational(L, lhs, rhs, "__eq") { + case 1: + ret = true + default: + ret = false + } + } + default: + ret = lhs == rhs + } + return ret +} + +func objectRationalWithError(L *LState, lhs, rhs LValue, event string) bool { + switch objectRational(L, lhs, rhs, event) { + case 1: + return true + case 0: + return false + } + L.RaiseError("attempt to compare %v with %v", lhs.Type().String(), rhs.Type().String()) + return false +} + +func objectRational(L *LState, lhs, rhs LValue, event string) int { + m1 := L.metaOp1(lhs, event) + m2 := L.metaOp1(rhs, event) + if m1.Type() == LTFunction && m1 == m2 { + L.reg.Push(m1) + L.reg.Push(lhs) + L.reg.Push(rhs) + L.Call(2, 1) + if LVAsBool(L.reg.Pop()) { + return 1 + } + return 0 + } + return -1 +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 72a23042266..4e297dfd98e 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -71,6 +71,12 @@ github.com/alecthomas/template github.com/alecthomas/template/parse # github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d github.com/alecthomas/units +# github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a +github.com/alicebob/gopher-json +# github.com/alicebob/miniredis v2.5.0+incompatible +## explicit +github.com/alicebob/miniredis +github.com/alicebob/miniredis/server # github.com/apache/thrift v0.13.0 github.com/apache/thrift/lib/go/thrift # github.com/armon/go-metrics v0.3.3 @@ -497,6 +503,9 @@ github.com/golangci/prealloc github.com/golangci/revgrep # github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 github.com/golangci/unconvert +# github.com/gomodule/redigo v2.0.0+incompatible +github.com/gomodule/redigo/internal +github.com/gomodule/redigo/redis # github.com/google/addlicense v0.0.0-20200622132530-df58acafd6d5 github.com/google/addlicense # github.com/google/btree v1.0.0 @@ -1148,6 +1157,11 @@ github.com/willf/bitset github.com/willf/bloom # github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 github.com/xiang90/probing +# github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da +github.com/yuin/gopher-lua +github.com/yuin/gopher-lua/ast +github.com/yuin/gopher-lua/parse +github.com/yuin/gopher-lua/pm # go.etcd.io/bbolt v1.3.5-0.20200615073812-232d8fc87f50 go.etcd.io/bbolt # go.etcd.io/etcd v0.5.0-alpha.5.0.20200520232829-54ba9589114f => go.etcd.io/etcd v0.5.0-alpha.5.0.20200520232829-54ba9589114f From 4e682dbacc1f642e2ed51b8bcb109061b023041f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gonz=C3=A1lez=20Lopes?= Date: Tue, 24 Nov 2020 21:58:29 +0100 Subject: [PATCH 11/16] Remove typeBloom and typeIndex MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniel González Lopes --- tempodb/backend/redis/cache.go | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/tempodb/backend/redis/cache.go b/tempodb/backend/redis/cache.go index c99b77f9428..31a4d246c1e 100644 --- a/tempodb/backend/redis/cache.go +++ b/tempodb/backend/redis/cache.go @@ -13,11 +13,6 @@ import ( "github.com/google/uuid" ) -const ( - typeBloom = "bloom" - typeIndex = "index" -) - type Config struct { ClientConfig cache.RedisConfig `yaml:",inline"` @@ -65,7 +60,7 @@ func (r *readerWriter) BlockMeta(ctx context.Context, blockID uuid.UUID, tenantI } func (r *readerWriter) Bloom(ctx context.Context, blockID uuid.UUID, tenantID string, shardNum int) ([]byte, error) { - key := bloomKey(blockID, tenantID, typeBloom, shardNum) + key := bloomKey(blockID, tenantID, shardNum) val := r.get(ctx, key) if val != nil { return val, nil @@ -80,7 +75,7 @@ func (r *readerWriter) Bloom(ctx context.Context, blockID uuid.UUID, tenantID st } func (r *readerWriter) Index(ctx context.Context, blockID uuid.UUID, tenantID string) ([]byte, error) { - key := key(blockID, tenantID, typeIndex) + key := key(blockID, tenantID) val := r.get(ctx, key) if val != nil { return val, nil @@ -106,18 +101,18 @@ func (r *readerWriter) Shutdown() { // Writer func (r *readerWriter) Write(ctx context.Context, meta *encoding.BlockMeta, bBloom [][]byte, bIndex []byte, objectFilePath string) error { for i, b := range bBloom { - r.set(ctx, bloomKey(meta.BlockID, meta.TenantID, typeBloom, i), b) + r.set(ctx, bloomKey(meta.BlockID, meta.TenantID, i), b) } - r.set(ctx, key(meta.BlockID, meta.TenantID, typeIndex), bIndex) + r.set(ctx, key(meta.BlockID, meta.TenantID), bIndex) return r.nextWriter.Write(ctx, meta, bBloom, bIndex, objectFilePath) } func (r *readerWriter) WriteBlockMeta(ctx context.Context, tracker backend.AppendTracker, meta *encoding.BlockMeta, bBloom [][]byte, bIndex []byte) error { for i, b := range bBloom { - r.set(ctx, bloomKey(meta.BlockID, meta.TenantID, typeBloom, i), b) + r.set(ctx, bloomKey(meta.BlockID, meta.TenantID, i), b) } - r.set(ctx, key(meta.BlockID, meta.TenantID, typeIndex), bIndex) + r.set(ctx, key(meta.BlockID, meta.TenantID), bIndex) return r.nextWriter.WriteBlockMeta(ctx, tracker, meta, bBloom, bIndex) } @@ -138,10 +133,10 @@ func (r *readerWriter) set(ctx context.Context, key string, val []byte) { r.client.Store(ctx, []string{key}, [][]byte{val}) } -func key(blockID uuid.UUID, tenantID string, t string) string { - return blockID.String() + ":" + tenantID + ":" + t +func key(blockID uuid.UUID, tenantID string) string { + return blockID.String() + ":" + tenantID + ":bloom" } -func bloomKey(blockID uuid.UUID, tenantID string, t string, shardNum int) string { - return blockID.String() + ":" + tenantID + ":" + t + strconv.Itoa(shardNum) +func bloomKey(blockID uuid.UUID, tenantID string, shardNum int) string { + return blockID.String() + ":" + tenantID + ":index" + strconv.Itoa(shardNum) } From 8be7e1493723819cc5b86f8903b4c318e3ddfc6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gonz=C3=A1lez=20Lopes?= Date: Tue, 24 Nov 2020 21:59:40 +0100 Subject: [PATCH 12/16] Remove strs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniel González Lopes --- tempodb/backend/redis/cache.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tempodb/backend/redis/cache.go b/tempodb/backend/redis/cache.go index 31a4d246c1e..1bef03b2d65 100644 --- a/tempodb/backend/redis/cache.go +++ b/tempodb/backend/redis/cache.go @@ -134,9 +134,9 @@ func (r *readerWriter) set(ctx context.Context, key string, val []byte) { } func key(blockID uuid.UUID, tenantID string) string { - return blockID.String() + ":" + tenantID + ":bloom" + return blockID.String() + ":" + tenantID } func bloomKey(blockID uuid.UUID, tenantID string, shardNum int) string { - return blockID.String() + ":" + tenantID + ":index" + strconv.Itoa(shardNum) + return blockID.String() + ":" + tenantID + strconv.Itoa(shardNum) } From 81ea1e99e9f018f401fff4882c91bb8b101f57a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gonz=C3=A1lez=20Lopes?= Date: Tue, 24 Nov 2020 22:05:11 +0100 Subject: [PATCH 13/16] Add some more redis commands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniel González Lopes --- docs/tempo/website/configuration/_index.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/tempo/website/configuration/_index.md b/docs/tempo/website/configuration/_index.md index 63364643200..3d73e8e8512 100644 --- a/docs/tempo/website/configuration/_index.md +++ b/docs/tempo/website/configuration/_index.md @@ -89,6 +89,8 @@ storage: redis: # optional redis configuration endpoint: redis timeout: 500ms + db: 0 + expiration: 0s pool: # the worker pool is used primarily when finding traces by id, but is also used by other max_workers: 50 # total number of workers pulling jobs from the queue queue_depth: 2000 # length of job queue From 212ce13892ed53f6c5c1caf7e7dbe21ce1c7ff8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gonz=C3=A1lez=20Lopes?= Date: Tue, 24 Nov 2020 23:18:49 +0100 Subject: [PATCH 14/16] Add Redis section MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniel González Lopes --- docs/tempo/website/configuration/_index.md | 3 ++- docs/tempo/website/configuration/redis.md | 23 ++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 docs/tempo/website/configuration/redis.md diff --git a/docs/tempo/website/configuration/_index.md b/docs/tempo/website/configuration/_index.md index 3d73e8e8512..f7024aa0826 100644 --- a/docs/tempo/website/configuration/_index.md +++ b/docs/tempo/website/configuration/_index.md @@ -68,10 +68,11 @@ compactor: ``` ## [Storage](https://github.com/grafana/tempo/blob/master/tempodb/config.go) -The storage block is used to configure TempoDB. It supports S3, GCS, local file system, and optionally can use memcached for increased query performance. +The storage block is used to configure TempoDB. It supports S3, GCS, local file system, and optionally can use Memcached or Redis for increased query performance. The following example shows common options. For platform-specific options refer to the following: * [S3](s3/) +* [Redis](redis/) ``` storage: diff --git a/docs/tempo/website/configuration/redis.md b/docs/tempo/website/configuration/redis.md new file mode 100644 index 00000000000..148812d7b48 --- /dev/null +++ b/docs/tempo/website/configuration/redis.md @@ -0,0 +1,23 @@ +--- +title: Redis +--- + +# Redis Configuration +Redis caching is configured in the storage block. + +``` +storage: + trace: + redis: + endpoint: redis # redis endpoint to use when caching. + timeout: 500ms # optional. maximum time to wait before giving up on redis requests. (default 100ms) + master-name: redis-master # optional. redis Sentinel master name. (default "") + db: 0 # optional. database index. (default 0) + expiration: 0s # optional. how long keys stay in the redis. (default 0) + tls-enabled: false # optional. enable connecting to redis with TLS. (default false) + tls-insecure-skip-verify: false # optional. skip validating server certificate. (default false) + pool-size: 0 # optional. maximum number of connections in the pool. (default 0) + password: ... # optional. password to use when connecting to redis. (default "") + idle-timeout: 0s # optional. close connections after remaining idle for this duration. (default 0s) + max-connection-age: 0s # optional. close connections older than this duration. (default 0s) +``` \ No newline at end of file From 6086822a6a9f0a40994dd6a3779a5ef2675bc473 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gonz=C3=A1lez=20Lopes?= Date: Wed, 25 Nov 2020 09:42:28 +0100 Subject: [PATCH 15/16] Fix missing : MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniel González Lopes --- tempodb/backend/redis/cache.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tempodb/backend/redis/cache.go b/tempodb/backend/redis/cache.go index b8cc487a6a2..94c02fea954 100644 --- a/tempodb/backend/redis/cache.go +++ b/tempodb/backend/redis/cache.go @@ -143,5 +143,5 @@ func key(blockID uuid.UUID, tenantID string) string { } func bloomKey(blockID uuid.UUID, tenantID string, shardNum int) string { - return blockID.String() + ":" + tenantID + typeBloom + strconv.Itoa(shardNum) + return blockID.String() + ":" + tenantID + ":" + typeBloom + strconv.Itoa(shardNum) } From 98b736206c2a483f5d8407d6338a26dda9432851 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gonz=C3=A1lez=20Lopes?= Date: Wed, 25 Nov 2020 14:52:42 +0100 Subject: [PATCH 16/16] Add note about redis support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniel González Lopes --- docs/tempo/website/configuration/redis.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/tempo/website/configuration/redis.md b/docs/tempo/website/configuration/redis.md index 148812d7b48..a9fd999ebdb 100644 --- a/docs/tempo/website/configuration/redis.md +++ b/docs/tempo/website/configuration/redis.md @@ -3,6 +3,9 @@ title: Redis --- # Redis Configuration + +> Note: Redis support is experimental + Redis caching is configured in the storage block. ```