From be0ef80c8d8e7a9c35b7d56f119506b9646a2c0e Mon Sep 17 00:00:00 2001 From: Mattias Wadman Date: Sat, 8 Jan 2022 22:26:49 +0100 Subject: [PATCH] interp,fq: Make bit operators normal functions Uses same rule as jq math functions, 1 arg uses input, more than one all passed as args. So "bnot 1" -> "1 | bnot", "1 bsl 1" -> "bsl(1; 1)" Don't think it's worth changing the jq syntax for this and also it could make fq scripts no compatiblr with other jq tools. Non-10 base number literals are still allowed but should probably not be used in scripts, only in repl and with cli. --- go.mod | 2 +- go.sum | 4 +- internal/gojqextra/error.go | 18 +++++ pkg/interp/bitops.go | 109 ++++++++++++++++++++++++++++++ pkg/interp/testdata/bitops.fqtest | 27 ++++++++ 5 files changed, 157 insertions(+), 3 deletions(-) create mode 100644 pkg/interp/bitops.go create mode 100644 pkg/interp/testdata/bitops.fqtest diff --git a/go.mod b/go.mod index e3fa65b1..316d4bac 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( require ( // fork of github.com/itchyny/gojq, see github.com/wader/gojq fq branch - github.com/wader/gojq v0.12.1-0.20211211101122-3894ded312be + github.com/wader/gojq v0.12.1-0.20220108235115-6a05b6c59ace // fork of github.com/chzyer/readline, see github.com/wader/readline fq branch github.com/wader/readline v0.0.0-20210920124728-5a81f7707bac ) diff --git a/go.sum b/go.sum index 2fc3d317..43e3795e 100644 --- a/go.sum +++ b/go.sum @@ -16,8 +16,8 @@ github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354/go.mod h1:KSVJ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/wader/gojq v0.12.1-0.20211211101122-3894ded312be h1:Bc8ZRZxUqQPPwHqdY1c99mKInhJ0UeWEMjgsjHMRGUA= -github.com/wader/gojq v0.12.1-0.20211211101122-3894ded312be/go.mod h1:tdC5h6dXdwAJs7eJUw4681AzsgfOSBrAV+cZzEbCZs4= +github.com/wader/gojq v0.12.1-0.20220108235115-6a05b6c59ace h1:pt07NaC7OhePrQVRKRxZy9umeWkjr28AmbtQC9CrtVQ= +github.com/wader/gojq v0.12.1-0.20220108235115-6a05b6c59ace/go.mod h1:tdC5h6dXdwAJs7eJUw4681AzsgfOSBrAV+cZzEbCZs4= github.com/wader/readline v0.0.0-20210920124728-5a81f7707bac h1:F5x54dwg6vGyf+8XhujiyXr651E3tKpcL1mqGmS7/MU= github.com/wader/readline v0.0.0-20210920124728-5a81f7707bac/go.mod h1:jYXyt9wQg3DifxQ8FM5M/ZoskO23GIwmo05QLHtO9CQ= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= diff --git a/internal/gojqextra/error.go b/internal/gojqextra/error.go index ec89cb8f..e1cfaed6 100644 --- a/internal/gojqextra/error.go +++ b/internal/gojqextra/error.go @@ -11,6 +11,24 @@ import ( // TODO: refactor to use errors from gojq? // TODO: preview from gojq? +type UnaryTypeError struct { + Name string + V interface{} +} + +func (err *UnaryTypeError) Error() string { + return fmt.Sprintf("cannot %s: %s", err.Name, typeof(err.V)) +} + +type BinopTypeError struct { + Name string + L, R interface{} +} + +func (err *BinopTypeError) Error() string { + return "cannot " + err.Name + ": " + typeof(err.L) + " and " + typeof(err.R) +} + type NonUpdatableTypeError struct { Typ string Key string diff --git a/pkg/interp/bitops.go b/pkg/interp/bitops.go new file mode 100644 index 00000000..8ccc5878 --- /dev/null +++ b/pkg/interp/bitops.go @@ -0,0 +1,109 @@ +package interp + +import ( + "math/big" + + "github.com/wader/fq/internal/gojqextra" + "github.com/wader/gojq" +) + +func init() { + functionRegisterFns = append(functionRegisterFns, func(i *Interp) []Function { + return []Function{ + {"bnot", 0, 0, i.bnot, nil}, + {"bsl", 2, 2, i.bsl, nil}, + {"bsr", 2, 2, i.bsr, nil}, + {"band", 2, 2, i.band, nil}, + {"bor", 2, 2, i.bor, nil}, + {"bxor", 2, 2, i.bxor, nil}, + } + }) +} + +func (i *Interp) bnot(c interface{}, a []interface{}) interface{} { + switch c := c.(type) { + case int: + return ^c + case *big.Int: + return new(big.Int).Not(c) + case gojq.JQValue: + return i.bnot(c.JQValueToGoJQ(), a) + default: + return &gojqextra.UnaryTypeError{Name: "bnot", V: c} + } +} + +func (i *Interp) bsl(c interface{}, a []interface{}) interface{} { + return gojq.BinopTypeSwitch(a[0], a[1], + func(l, r int) bool { return false }, // TODO: can be int safe i think + func(l, r int) interface{} { return l << r }, + func(l, r float64) interface{} { return int(l) << int(r) }, + func(l, r *big.Int) interface{} { return new(big.Int).Lsh(l, uint(r.Uint64())) }, + func(l, r string) interface{} { return &gojqextra.BinopTypeError{Name: "bsl", L: l, R: r} }, + func(l, r []interface{}) interface{} { return &gojqextra.BinopTypeError{Name: "bsl", L: l, R: r} }, + func(l, r map[string]interface{}) interface{} { + return &gojqextra.BinopTypeError{Name: "bsl", L: l, R: r} + }, + func(l, r interface{}) interface{} { return &gojqextra.BinopTypeError{Name: "bsl", L: l, R: r} }, + ) +} + +func (i *Interp) bsr(c interface{}, a []interface{}) interface{} { + return gojq.BinopTypeSwitch(a[0], a[1], + func(l, r int) bool { return true }, + func(l, r int) interface{} { return l >> r }, + func(l, r float64) interface{} { return int(l) >> int(r) }, + func(l, r *big.Int) interface{} { return new(big.Int).Rsh(l, uint(r.Uint64())) }, + func(l, r string) interface{} { return &gojqextra.BinopTypeError{Name: "bsr", L: l, R: r} }, + func(l, r []interface{}) interface{} { return &gojqextra.BinopTypeError{Name: "bsr", L: l, R: r} }, + func(l, r map[string]interface{}) interface{} { + return &gojqextra.BinopTypeError{Name: "bsr", L: l, R: r} + }, + func(l, r interface{}) interface{} { return &gojqextra.BinopTypeError{Name: "bsr", L: l, R: r} }, + ) +} + +func (i *Interp) band(c interface{}, a []interface{}) interface{} { + return gojq.BinopTypeSwitch(a[0], a[1], + func(l, r int) bool { return true }, + func(l, r int) interface{} { return l & r }, + func(l, r float64) interface{} { return int(l) & int(r) }, + func(l, r *big.Int) interface{} { return new(big.Int).And(l, r) }, + func(l, r string) interface{} { return &gojqextra.BinopTypeError{Name: "band", L: l, R: r} }, + func(l, r []interface{}) interface{} { return &gojqextra.BinopTypeError{Name: "band", L: l, R: r} }, + func(l, r map[string]interface{}) interface{} { + return &gojqextra.BinopTypeError{Name: "band", L: l, R: r} + }, + func(l, r interface{}) interface{} { return &gojqextra.BinopTypeError{Name: "band", L: l, R: r} }, + ) +} + +func (i *Interp) bor(c interface{}, a []interface{}) interface{} { + return gojq.BinopTypeSwitch(a[0], a[1], + func(l, r int) bool { return true }, + func(l, r int) interface{} { return l | r }, + func(l, r float64) interface{} { return int(l) | int(r) }, + func(l, r *big.Int) interface{} { return new(big.Int).Or(l, r) }, + func(l, r string) interface{} { return &gojqextra.BinopTypeError{Name: "bor", L: l, R: r} }, + func(l, r []interface{}) interface{} { return &gojqextra.BinopTypeError{Name: "bor", L: l, R: r} }, + func(l, r map[string]interface{}) interface{} { + return &gojqextra.BinopTypeError{Name: "bor", L: l, R: r} + }, + func(l, r interface{}) interface{} { return &gojqextra.BinopTypeError{Name: "bor", L: l, R: r} }, + ) +} + +func (i *Interp) bxor(c interface{}, a []interface{}) interface{} { + return gojq.BinopTypeSwitch(a[0], a[1], + func(l, r int) bool { return true }, + func(l, r int) interface{} { return l ^ r }, + func(l, r float64) interface{} { return int(l) ^ int(r) }, + func(l, r *big.Int) interface{} { return new(big.Int).Xor(l, r) }, + func(l, r string) interface{} { return &gojqextra.BinopTypeError{Name: "bxor", L: l, R: r} }, + func(l, r []interface{}) interface{} { return &gojqextra.BinopTypeError{Name: "bxor", L: l, R: r} }, + func(l, r map[string]interface{}) interface{} { + return &gojqextra.BinopTypeError{Name: "bxor", L: l, R: r} + }, + func(l, r interface{}) interface{} { return &gojqextra.BinopTypeError{Name: "bxor", L: l, R: r} }, + ) +} diff --git a/pkg/interp/testdata/bitops.fqtest b/pkg/interp/testdata/bitops.fqtest new file mode 100644 index 00000000..b5a97e49 --- /dev/null +++ b/pkg/interp/testdata/bitops.fqtest @@ -0,0 +1,27 @@ +$ fq -i +null> 0, -1, 1208925819614629174706175, -1208925819614629174706176 | bnot +-1 +0 +-1208925819614629174706176 +1208925819614629174706175 +null> [0,0], [8,1], [0xffff_ffff_ffff_ffff,1] | bsl(.[0]; .[1]) +0 +16 +36893488147419103230 +null> [0,0], [8,1], [0x1_ffff_ffff_ffff_fffe,1] | bsr(.[0]; .[1]) +0 +4 +18446744073709551615 +null> [0,0], [0xffff_ffff_ffff_ffff_ffff,0x1234], [0x1234,0xffff_ffff_ffff_ffff_ffff,0x1234] | band(.[0]; .[1]) +0 +4660 +4660 +null> [0,0], [0xffff_ffff_ffff_ffff_0000,0x1234], [0x1234,0xffff_ffff_ffff_ffff_0000,0x1234] | bor(.[0]; .[1]) +0 +1208925819614629174645300 +1208925819614629174645300 +null> [0,0], [0xffff_ffff_ffff_ffff_ffff,0x1234], [0x1234,0xffff_ffff_ffff_ffff_ffff,0x1234] | bxor(.[0]; .[1]) +0 +1208925819614629174701515 +1208925819614629174701515 +null> ^D