mirror of
https://github.com/go-sylixos/elvish.git
synced 2024-12-12 17:27:50 +08:00
parent
0724478faf
commit
1cd192daaf
|
@ -3,6 +3,8 @@ package eval
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
|
||||
"github.com/elves/elvish/pkg/eval/errs"
|
||||
"github.com/elves/elvish/pkg/eval/vals"
|
||||
|
@ -391,6 +393,8 @@ func init() {
|
|||
"count": count,
|
||||
|
||||
"keys": keys,
|
||||
|
||||
"order": order,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -626,3 +630,234 @@ func keys(fm *Frame, v interface{}) error {
|
|||
return true
|
||||
})
|
||||
}
|
||||
|
||||
//elvdoc:fn order
|
||||
//
|
||||
// ```elvish
|
||||
// order &reverse=$false &stable=$false $less-than=$nil~ $inputs?
|
||||
// ```
|
||||
//
|
||||
// Outputs the input values sorted in ascending order.
|
||||
//
|
||||
// The `&reverse` option, if true, reverses the order of output.
|
||||
//
|
||||
// The `&stable` option, if true, makes the sort
|
||||
// [stable](https://en.wikipedia.org/wiki/Sorting_algorithm#Stability).
|
||||
//
|
||||
// The `&less-than` option, if given, establishes the ordering of the elements.
|
||||
// Its value should be a function that takes two arguments and outputs a single
|
||||
// boolean indicating whether the first argument is less than the second
|
||||
// argument. If the function throws an exception, `order` rethrows the exception
|
||||
// without outputting any value.
|
||||
//
|
||||
// If `&less-than` has value `$nil` (the default if not set), the following
|
||||
// comparison algorithm is used:
|
||||
//
|
||||
// - Numbers are compared numerically. For the sake of sorting, `NaN` is treated
|
||||
// as smaller than all other numbers.
|
||||
//
|
||||
// - Strings are compared lexicographically by bytes, which is equivalent to
|
||||
// comparing by codepoints under UTF-8.
|
||||
//
|
||||
// - Lists are compared lexicographically by elements, if the elements at the
|
||||
// same positions are comparable.
|
||||
//
|
||||
// If the ordering between two elements are not defined by the conditions above,
|
||||
// no value is outputted and an exception is thrown.
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// ```elvish-transcript
|
||||
// ~> put foo bar ipsum | order
|
||||
// ▶ bar
|
||||
// ▶ foo
|
||||
// ▶ ipsum
|
||||
// ~> order [(float64 10) (float64 1) (float64 5)]
|
||||
// ▶ (float64 1)
|
||||
// ▶ (float64 5)
|
||||
// ▶ (float64 10)
|
||||
// ~> order [[a b] [a] [b b] [a c]]
|
||||
// ▶ [a]
|
||||
// ▶ [a b]
|
||||
// ▶ [a c]
|
||||
// ▶ [b b]
|
||||
// ~> order &reverse [a c b]
|
||||
// ▶ c
|
||||
// ▶ b
|
||||
// ▶ a
|
||||
// ~> order &less-than=[a b]{ eq $a x } &stable [l x o r x e x m]
|
||||
// ▶ x
|
||||
// ▶ x
|
||||
// ▶ x
|
||||
// ▶ l
|
||||
// ▶ o
|
||||
// ▶ r
|
||||
// ▶ e
|
||||
// ▶ m
|
||||
// ```
|
||||
//
|
||||
// Beware that strings that look like numbers are treated as strings, not
|
||||
// numbers. To sort strings as numbers, use an explicit `&less-than` option:
|
||||
//
|
||||
// ```elvish-transcript
|
||||
// ~> order [5 1 10]
|
||||
// ▶ 1
|
||||
// ▶ 10
|
||||
// ▶ 5
|
||||
// ~> order &less-than=[a b]{ < $a $b } [5 1 10]
|
||||
// ▶ 1
|
||||
// ▶ 5
|
||||
// ▶ 10
|
||||
// ```
|
||||
|
||||
type orderOptions struct {
|
||||
Reverse bool
|
||||
Stable bool
|
||||
LessThan Callable
|
||||
}
|
||||
|
||||
func (opt *orderOptions) SetDefaultOptions() {}
|
||||
|
||||
var errUncomparable = errs.BadValue{
|
||||
What: `inputs to "order"`,
|
||||
Valid: "comparable values", Actual: "uncomparable values"}
|
||||
|
||||
func order(fm *Frame, opts orderOptions, inputs Inputs) error {
|
||||
var values []interface{}
|
||||
inputs(func(v interface{}) { values = append(values, v) })
|
||||
|
||||
var errSort error
|
||||
var lessFn func(i, j int) bool
|
||||
if opts.LessThan != nil {
|
||||
lessFn = func(i, j int) bool {
|
||||
if errSort != nil {
|
||||
return true
|
||||
}
|
||||
var args []interface{}
|
||||
if opts.Reverse {
|
||||
args = []interface{}{values[j], values[i]}
|
||||
} else {
|
||||
args = []interface{}{values[i], values[j]}
|
||||
}
|
||||
outputs, err := fm.CaptureOutput(func(fm *Frame) error {
|
||||
return opts.LessThan.Call(fm, args, NoOpts)
|
||||
})
|
||||
if err != nil {
|
||||
errSort = err
|
||||
return true
|
||||
}
|
||||
if len(outputs) != 1 {
|
||||
errSort = errs.BadValue{
|
||||
What: "output of the &less-than callback",
|
||||
Valid: "a single boolean",
|
||||
Actual: fmt.Sprintf("%d values", len(outputs))}
|
||||
return true
|
||||
}
|
||||
if b, ok := outputs[0].(bool); ok {
|
||||
return b
|
||||
}
|
||||
errSort = errs.BadValue{
|
||||
What: "output of the &less-than callback",
|
||||
Valid: "boolean", Actual: vals.Kind(outputs[0])}
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
// Use default comparison implemented by compare.
|
||||
lessFn = func(i, j int) bool {
|
||||
if errSort != nil {
|
||||
return true
|
||||
}
|
||||
o := compare(values[i], values[j])
|
||||
if o == uncomparable {
|
||||
errSort = errUncomparable
|
||||
return true
|
||||
}
|
||||
if opts.Reverse {
|
||||
return o == more
|
||||
}
|
||||
return o == less
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Stable {
|
||||
sort.SliceStable(values, lessFn)
|
||||
} else {
|
||||
sort.Slice(values, lessFn)
|
||||
}
|
||||
|
||||
if errSort != nil {
|
||||
return errSort
|
||||
}
|
||||
for _, v := range values {
|
||||
fm.OutputChan() <- v
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type ordering uint8
|
||||
|
||||
const (
|
||||
less ordering = iota
|
||||
equal
|
||||
more
|
||||
uncomparable
|
||||
)
|
||||
|
||||
func compare(a, b interface{}) ordering {
|
||||
switch a := a.(type) {
|
||||
case float64:
|
||||
if b, ok := b.(float64); ok {
|
||||
switch {
|
||||
case math.IsNaN(a):
|
||||
if math.IsNaN(b) {
|
||||
return equal
|
||||
}
|
||||
return less
|
||||
case math.IsNaN(b):
|
||||
return more
|
||||
case a == b:
|
||||
return equal
|
||||
case a < b:
|
||||
return less
|
||||
default:
|
||||
// a > b
|
||||
return more
|
||||
}
|
||||
}
|
||||
case string:
|
||||
if b, ok := b.(string); ok {
|
||||
switch {
|
||||
case a == b:
|
||||
return equal
|
||||
case a < b:
|
||||
return less
|
||||
default:
|
||||
// a > b
|
||||
return more
|
||||
}
|
||||
}
|
||||
case vals.List:
|
||||
if b, ok := b.(vals.List); ok {
|
||||
aIt := a.Iterator()
|
||||
bIt := b.Iterator()
|
||||
for aIt.HasElem() && bIt.HasElem() {
|
||||
o := compare(aIt.Elem(), bIt.Elem())
|
||||
if o != equal {
|
||||
return o
|
||||
}
|
||||
aIt.Next()
|
||||
bIt.Next()
|
||||
}
|
||||
switch {
|
||||
case a.Len() == b.Len():
|
||||
return equal
|
||||
case a.Len() < b.Len():
|
||||
return less
|
||||
default:
|
||||
// a.Len() > b.Len()
|
||||
return more
|
||||
}
|
||||
}
|
||||
}
|
||||
return uncomparable
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package eval
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math"
|
||||
"testing"
|
||||
|
||||
"github.com/elves/elvish/pkg/eval/errs"
|
||||
|
@ -77,6 +79,66 @@ func TestBuiltinFnContainer(t *testing.T) {
|
|||
That(`keys [&a=foo]`).Puts("a"),
|
||||
// Windows does not have an external sort command. Disabled until we have a
|
||||
// builtin sort command.
|
||||
// That(`keys [&a=foo &b=bar] | each echo | sort | each $put~`).Puts("a", "b"),
|
||||
That(`keys [&a=foo &b=bar] | order`).Puts("a", "b"),
|
||||
|
||||
// Ordering strings
|
||||
That("put foo bar ipsum | order").Puts("bar", "foo", "ipsum"),
|
||||
That("put foo bar bar | order").Puts("bar", "bar", "foo"),
|
||||
That("put 10 1 5 2 | order").Puts("1", "10", "2", "5"),
|
||||
// Ordering numbers
|
||||
That("put 10 1 5 2 | each $float64~ | order").Puts(1.0, 2.0, 5.0, 10.0),
|
||||
That("put 10 1 1 | each $float64~ | order").Puts(1.0, 1.0, 10.0),
|
||||
That("put 10 NaN 1 | each $float64~ | order").Puts(math.NaN(), 1.0, 10.0),
|
||||
That("put NaN NaN 1 | each $float64~ | order").
|
||||
Puts(math.NaN(), math.NaN(), 1.0),
|
||||
// Ordering lists
|
||||
That("put [b] [a] | order").Puts(vals.MakeList("a"), vals.MakeList("b")),
|
||||
That("put [a] [b] [a] | order").
|
||||
Puts(vals.MakeList("a"), vals.MakeList("a"), vals.MakeList("b")),
|
||||
That("put [(float64 10)] [(float64 2)] | order").
|
||||
Puts(vals.MakeList(2.0), vals.MakeList(10.0)),
|
||||
That("put [a b] [b b] [a c] | order").
|
||||
Puts(
|
||||
vals.MakeList("a", "b"),
|
||||
vals.MakeList("a", "c"), vals.MakeList("b", "b")),
|
||||
That("put [a] [] [a (float64 2)] [a (float64 1)] | order").
|
||||
Puts(vals.EmptyList, vals.MakeList("a"),
|
||||
vals.MakeList("a", 1.0), vals.MakeList("a", 2.0)),
|
||||
// Attempting to order uncomparable values
|
||||
That("put a (float64 1) b (float64 2) | order").
|
||||
Throws(errUncomparable, "order"),
|
||||
That("put [a] [(float64 1)] | order").
|
||||
Throws(errUncomparable, "order"),
|
||||
// &reverse
|
||||
That("put foo bar ipsum | order &reverse").Puts("ipsum", "foo", "bar"),
|
||||
// &less-than
|
||||
That("put 1 10 2 5 | order &less-than=[a b]{ < $a $b }").
|
||||
Puts("1", "2", "5", "10"),
|
||||
// &less-than writing more than one value
|
||||
That("put 1 10 2 5 | order &less-than=[a b]{ put $true $false }").
|
||||
Throws(
|
||||
errs.BadValue{
|
||||
What: "output of the &less-than callback",
|
||||
Valid: "a single boolean", Actual: "2 values"},
|
||||
"order &less-than=[a b]{ put $true $false }"),
|
||||
// &less-than writing non-boolean value
|
||||
That("put 1 10 2 5 | order &less-than=[a b]{ put x }").
|
||||
Throws(
|
||||
errs.BadValue{
|
||||
What: "output of the &less-than callback",
|
||||
Valid: "boolean", Actual: "string"},
|
||||
"order &less-than=[a b]{ put x }"),
|
||||
// &less-than throwing an exception
|
||||
That("put 1 10 2 5 | order &less-than=[a b]{ fail bad }").
|
||||
Throws(
|
||||
errors.New("bad"),
|
||||
"fail bad ", "order &less-than=[a b]{ fail bad }"),
|
||||
// &less-than and &reverse
|
||||
That("put 1 10 2 5 | order &reverse &less-than=[a b]{ < $a $b }").
|
||||
Puts("10", "5", "2", "1"),
|
||||
// &stable - test by pretending that all values but one are equal, and
|
||||
// check that the order among them has not changed
|
||||
That("put l x o x r x e x m | order &stable &less-than=[a b]{ eq $a x }").
|
||||
Puts("x", "x", "x", "x", "l", "o", "r", "e", "m"),
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user