From a0c08b0577677d34fa45b66cc1df5a62f480e124 Mon Sep 17 00:00:00 2001 From: Rob Pike Date: Sun, 2 Jul 2023 13:45:27 +1000 Subject: [PATCH] ivy: support non-scalar elements of vectors and matrices In #117, @smasher164 observed that ,\1 2 3 generates 1 (1 2) (1 2 3) despite the claim that ivy does not support non-scalar elements. I looked into preventing them from ever appearing, but that was messy and slow as it requires a lot of post-construction checking of things. So I tried the other direction, and made them legal. That turned out to be much more straightforward than I thought, although there were a number of associated changes to make it work. Whenever the existing barriers were removed, the code must actually work. Significant changes and bug fixes: - Fixed a trivial bug in parse that did not allow indexed expressions in vectors, for example 1 2 x[3] 4 - Can now input compound elements of vectors: 1 (1 2) 3 - They must also be printable clearly, and for simplicity I made the output model the input, at least for these simple vector cases. - Indexed assignment needed some tweaks, including fixing an existing bug: The rhs was not copied, so aliasing could happen and it was easy to demostrate bad behavior. Added a Copy method to Value to clean this up. - Support matrices and vectors in OrderedCompare. - Update and extend the tests. Some failure tests now worked! I didn't touch the docs. Not clear a change is needed, although this might be worth a phrase somewhere, perhaps in the demo. Also simplify the method for printing matrices, which will make it easier to fix a layout issue: A multiline matrix as an element of an array or matrix results in ugly output. Relatively easy to fix, but to be a separate change. Update #117 --- demo/demo.out | 28 ++++++------- order_test.go | 73 ++++++++++++++++++++++++++------ parse/parse.go | 22 +++++----- testdata/binary_vector.ivy | 25 ++++++++++- testdata/char.ivy | 19 +++++++++ testdata/exec_fail.ivy | 25 +++++------ testdata/statement.ivy | 7 ++++ testdata/unary_matrix.ivy | 4 ++ testdata/unary_vector.ivy | 6 +++ value/bigfloat.go | 6 +++ value/bigint.go | 6 +++ value/bigrat.go | 6 +++ value/binary.go | 2 +- value/char.go | 4 ++ value/complex.go | 7 ++++ value/eval.go | 9 +++- value/index.go | 62 +++++++++++++++------------ value/int.go | 4 ++ value/matrix.go | 40 +++++++++--------- value/sets.go | 85 +++++++++++++++++++++++++++++--------- value/unary.go | 6 +-- value/value.go | 5 +++ value/vector.go | 38 +++++++++++++---- 23 files changed, 352 insertions(+), 137 deletions(-) diff --git a/demo/demo.out b/demo/demo.out index 7d68428a..60ebb7b8 100644 --- a/demo/demo.out +++ b/demo/demo.out @@ -305,18 +305,18 @@ wroo 105 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 6 4 7 3 1 -A♠ A♡ A♣ A♢ -2♠ 2♡ 2♣ 2♢ -3♠ 3♡ 3♣ 3♢ -4♠ 4♡ 4♣ 4♢ -5♠ 5♡ 5♣ 5♢ -6♠ 6♡ 6♣ 6♢ -7♠ 7♡ 7♣ 7♢ -8♠ 8♡ 8♣ 8♢ -9♠ 9♡ 9♣ 9♢ -0♠ 0♡ 0♣ 0♢ -J♠ J♡ J♣ J♢ -Q♠ Q♡ Q♣ Q♢ -K♠ K♡ K♣ K♢ -J♠ 6♡ 4♢ 5♢ 4♡ 7♢ 8♡ 2♢ 4♣ Q♣ 9♡ 3♣ Q♠ 2♣ J♡ J♣ A♠ K♡ 3♢ 0♣ 6♢ 8♠ A♢ A♣ 0♡ 9♢ 8♣ K♠ 2♠ 3♡ Q♡ J♢ 9♠ 3♠ K♢ 9♣ Q♢ 7♡ 0♢ 5♠ 4♠ 0♠ 7♠ 7♣ 5♡ 6♣ 6♠ 5♣ 8♢ A♡ K♣ 2♡ +(A♠) (A♡) (A♣) (A♢) +(2♠) (2♡) (2♣) (2♢) +(3♠) (3♡) (3♣) (3♢) +(4♠) (4♡) (4♣) (4♢) +(5♠) (5♡) (5♣) (5♢) +(6♠) (6♡) (6♣) (6♢) +(7♠) (7♡) (7♣) (7♢) +(8♠) (8♡) (8♣) (8♢) +(9♠) (9♡) (9♣) (9♢) +(0♠) (0♡) (0♣) (0♢) +(J♠) (J♡) (J♣) (J♢) +(Q♠) (Q♡) (Q♣) (Q♢) +(K♠) (K♡) (K♣) (K♢) +(J♠) (6♡) (4♢) (5♢) (4♡) (7♢) (8♡) (2♢) (4♣) (Q♣) (9♡) (3♣) (Q♠) (2♣) (J♡) (J♣) (A♠) (K♡) (3♢) (0♣) (6♢) (8♠) (A♢) (A♣) (0♡) (9♢) (8♣) (K♠) (2♠) (3♡) (Q♡) (J♢) (9♠) (3♠) (K♢) (9♣) (Q♢) (7♡) (0♢) (5♠) (4♠) (0♠) (7♠) (7♣) (5♡) (6♣) (6♠) (5♣) (8♢) (A♡) (K♣) (2♡) 22 diff --git a/order_test.go b/order_test.go index 23447c8b..7325d8fe 100644 --- a/order_test.go +++ b/order_test.go @@ -52,8 +52,24 @@ var ( complex1j2 = value.NewComplex(int1, int2) // Same real, bigger imaginary. complex2j1 = value.NewComplex(int2, int1) // Bigger real, lesser imaginary complex2j2 = value.NewComplex(int2, int2) // Same real, bigger imaginary + + vector0000 = value.NewIntVector([]int{0, 0, 0, 0}) + vector012 = value.NewIntVector([]int{0, 1, 2}) + vector022 = value.NewIntVector([]int{0, 2, 2}) + + matrix000_000 = value.NewMatrix([]int{2, 3}, newMatrixData(0, 0, 0, 0, 0, 0)) + matrix12_34 = value.NewMatrix([]int{2, 2}, newMatrixData(1, 2, 3, 4)) + matrix12_44 = value.NewMatrix([]int{2, 2}, newMatrixData(1, 2, 4, 4)) ) +func newMatrixData(data ...int) []value.Value { + v := make([]value.Value, len(data)) + for i := range data { + v[i] = value.Int(data[i]) + } + return v +} + func TestOrderedCompare(t *testing.T) { var tests = []orderTest{ // Same types. @@ -130,73 +146,73 @@ func TestOrderedCompare(t *testing.T) { {complex2j2, complex2j1, 1}, {complex2j2, complex2j2, 0}, - // Int less than every possible type. + // Int less than every possible scalar type. {int0, bigInt1, -1}, {int0, bigRat1o1, -1}, {int0, bigFloat1p0, -1}, {int0, complex1j0, -1}, - // Int equal to every possible type. + // Int equal to every possible scalar type. {int1, bigInt1, 0}, {int1, bigRat1o1, 0}, {int1, bigFloat1p0, 0}, {int1, complex1j0, 0}, - // Int greater than every possible type. + // Int greater than every possible scalar type. {int2, bigInt1, 1}, {int2, bigRat1o1, 1}, {int2, bigFloat1p0, 1}, {int2, complex1j0, 1}, - // BigInt less than every possible type. + // BigInt less than every possible scalar type. {bigInt0, int1, -1}, {bigInt0, bigRat1o1, -1}, {bigInt0, bigFloat1p0, -1}, {bigInt0, complex1j0, -1}, - // BigInt equal to every possible type. + // BigInt equal to every possible scalar type. {bigInt1, int1, 0}, {bigInt1, bigRat1o1, 0}, {bigInt1, bigFloat1p0, 0}, {bigInt1, complex1j0, 0}, - // BigInt greater than every possible type. + // BigInt greater than every possible scalar type. {bigInt2, int1, 1}, {bigInt2, bigRat1o1, 1}, {bigInt2, bigFloat1p0, 1}, {bigInt2, complex1j0, 1}, - // BigRat less than every possible type. + // BigRat less than every possible scalar type. {bigRat0o1, int1, -1}, {bigRat0o1, bigInt1, -1}, {bigRat0o1, bigFloat1p0, -1}, {bigRat0o1, complex1j0, -1}, - // BigRat equal to every possible type. + // BigRat equal to every possible scalar type. {bigRat1o1, int1, 0}, {bigRat1o1, bigInt1, 0}, {bigRat1o1, bigFloat1p0, 0}, {bigRat1o1, complex1j0, 0}, - // BigRat greater than every possible type. + // BigRat greater than every possible scalar type. {bigRat2o1, int1, 1}, {bigRat2o1, bigInt1, 1}, {bigRat2o1, bigFloat1p0, 1}, {bigRat2o1, complex1j0, 1}, - // BigFloat less than every possible type. + // BigFloat less than every possible scalar type. {bigFloat0p0, int1, -1}, {bigFloat0p0, bigInt1, -1}, {bigFloat0p0, bigFloat1p0, -1}, {bigFloat0p0, complex1j0, -1}, - // BigFloat equal to every possible type. + // BigFloat equal to every possible scalar type. {bigFloat1p0, int1, 0}, {bigFloat1p0, bigInt1, 0}, {bigFloat1p0, bigFloat1p0, 0}, {bigFloat1p0, complex1j0, 0}, - // BigFloat greater than every possible type. + // BigFloat greater than every possible scalar type. {bigFloat2p0, int1, 1}, {bigFloat2p0, bigInt1, 1}, {bigFloat2p0, bigFloat1p0, 1}, @@ -218,12 +234,43 @@ func TestOrderedCompare(t *testing.T) { {complex1j0, bigRat1o1, 0}, {complex1j0, bigFloat1p0, 0}, - // Complex with imaginary part is always greater than every other type. + // Complex with imaginary part is always greater than every other scalar type. {complex1j1, int1, 1}, {complex1j1, char1, 1}, {complex1j1, bigInt1, 1}, {complex1j1, bigRat1o1, 1}, {complex1j1, bigFloat1p0, 1}, + + // Vector bigger than every type. + {vector012, int3, 1}, + {vector012, char3, 1}, + {vector012, char3, 1}, + {vector012, bigInt3, 1}, + {vector012, bigRat3o7, 1}, + {vector012, bigFloat3p5, 1}, + {vector012, complex2j2, 1}, + + // Vector comparisons. + {vector0000, vector012, 1}, // Length dominates. + {vector012, vector022, -1}, + {vector012, vector012, 0}, + {vector022, vector012, 1}, + + // Matrix bigger than every type. + {matrix12_34, int3, 1}, + {matrix12_34, char3, 1}, + {matrix12_34, char3, 1}, + {matrix12_34, bigInt3, 1}, + {matrix12_34, bigRat3o7, 1}, + {matrix12_34, bigFloat3p5, 1}, + {matrix12_34, complex2j2, 1}, + {matrix12_34, vector012, 1}, + + // Matrix comparisons. + {matrix000_000, matrix12_34, 1}, // Length dominates. + {matrix12_34, matrix12_44, -1}, + {matrix12_34, matrix12_34, 0}, + {matrix12_44, matrix12_34, 1}, } var testConf config.Config c := exec.NewContext(&testConf) diff --git a/parse/parse.go b/parse/parse.go index d1dde715..3760c78e 100644 --- a/parse/parse.go +++ b/parse/parse.go @@ -84,12 +84,7 @@ func (s sliceExpr) Eval(context value.Context) value.Value { // x=1000; x + x=2 // (yielding 4) work. for i := len(s) - 1; i >= 0; i-- { - elem := s[i].Eval(context) - // Each element must be a singleton. - if !isScalar(elem) { - value.Errorf("vector element must be scalar; have %s", elem) - } - v[i] = elem + v[i] = s[i].Eval(context) } return value.NewVector(v) } @@ -627,7 +622,7 @@ func (p *Parser) numberOrVector(tok scan.Token) value.Expr { var slice sliceExpr if expr == nil { // Must be a string. - slice = append(slice, evalString(str)...) + slice = sliceExpr{evalString(str)} } else { slice = sliceExpr{expr} } @@ -647,7 +642,7 @@ func (p *Parser) numberOrVector(tok scan.Token) value.Expr { expr, str = p.number(p.next()) if expr == nil { // Must be a string. - slice = append(slice, evalString(str)...) + slice = append(slice, evalString(str)) continue } default: @@ -672,13 +667,16 @@ func (p *Parser) variable(name string) *variableExpr { } } -// evalString turns a parsed string constant into a slice of -// value.Exprs each of which is a value.Char. -func evalString(str string) []value.Expr { +// evalString turns a string constant into an Expr +// that is either a single Char or a slice of Chars. +func evalString(str string) value.Expr { r := ([]rune)(str) + if len(r) == 1 { + return value.Char(r[0]) + } v := make([]value.Expr, len(r)) for i, c := range r { v[i] = value.Char(c) } - return v + return sliceExpr(v) } diff --git a/testdata/binary_vector.ivy b/testdata/binary_vector.ivy index e8183b3c..9ce5b617 100644 --- a/testdata/binary_vector.ivy +++ b/testdata/binary_vector.ivy @@ -187,6 +187,18 @@ 23 45 5 == 5 0 0 1 +x=(1 2) 3 4 5; x==x + (1 1) 1 1 1 + +x=(1 2) 3 4 5; y=1 (2 3) 4 5; x==y + (1 0) (0 1) 1 1 + +x=(1 2) 3 4 5; y=1 (2 3) 4 5; y==x + (1 0) (0 1) 1 1 + +x=(1 0) 3 4 5; y=1 (2 4) 5 4; x 0: if n > len { panic(bad) diff --git a/value/char.go b/value/char.go index 3ba114b8..103a189a 100644 --- a/value/char.go +++ b/value/char.go @@ -40,6 +40,10 @@ func (c Char) Eval(Context) Value { return c } +func (c Char) Copy() Value { + return c +} + func (c Char) Inner() Value { return c } diff --git a/value/complex.go b/value/complex.go index 2c544941..8787359f 100644 --- a/value/complex.go +++ b/value/complex.go @@ -54,6 +54,13 @@ func (c Complex) Eval(Context) Value { return c } +func (c Complex) Copy() Value { + return Complex{ + real: c.real.Copy(), + imag: c.real.Copy(), + } +} + func (c Complex) Inner() Value { return c } diff --git a/value/eval.go b/value/eval.go index e1e1d49b..b99ac35a 100644 --- a/value/eval.go +++ b/value/eval.go @@ -276,7 +276,7 @@ func outerProduct(c Context, u Value, op string, v Value) Value { v := v.(Vector) m := Matrix{ shape: []int{len(u), len(v)}, - data: NewVector(make(Vector, len(u)*len(v))), + data: make([]Value, len(u)*len(v)), } pfor(safeBinary(op), 1, len(m.data), func(lo, hi int) { for x := lo; x < hi; x++ { @@ -288,7 +288,7 @@ func outerProduct(c Context, u Value, op string, v Value) Value { v := v.(*Matrix) m := Matrix{ shape: append(u.Shape(), v.Shape()...), - data: NewVector(make(Vector, len(u.Data())*len(v.Data()))), + data: make([]Value, len(u.Data())*len(v.Data())), } vdata := v.Data() udata := u.Data() @@ -525,6 +525,11 @@ func binaryMatrixOp(c Context, i Value, op string, j Value) Value { return NewMatrix(shape, NewVector(n)) } +// isScalarType reports whether u is an actual scalar, an int or float etc. +func isScalarType(v Value) bool { + return whichType(v) < vectorType +} + // isScalar reports whether u is a 1x1x1x... item, that is, a scalar promoted to matrix. func isScalar(u *Matrix) bool { for _, dim := range u.shape { diff --git a/value/index.go b/value/index.go index bc383a27..192fd08b 100644 --- a/value/index.go +++ b/value/index.go @@ -189,33 +189,38 @@ func IndexAssign(context Context, top, left Expr, index []Expr, right Expr, rhs var ix indexState ix.init(context, top, left, index) - // RHS must be scalar or have same shape as indexed expression. + // Unless assigning to a single cell, RHS must be scalar or + // have same shape as indexed expression. var rscalar Value var rslice []Value - switch rhs := rhs.(type) { - default: + if len(ix.outShape) == 0 { rscalar = rhs - case *Matrix: - if !sameShape(ix.outShape, rhs.Shape()) { - Errorf("shape mismatch %v != %v in assignment %v = %v", - NewIntVector(ix.outShape), NewIntVector(rhs.Shape()), - top.ProgString(), right.ProgString()) - } - rslice = rhs.Data() - if rhs == ix.lhs { - // Assigning entire rhs to some permutation of lhs. - // Make copy of values to avoid problems with overwriting - // values we need to read later. Uncommon. - rslice = make([]Value, len(rslice)) - copy(rslice, rhs.Data()) - } - case Vector: - if len(ix.outShape) != 1 || ix.outShape[0] != len(rhs) { - Errorf("shape mismatch %v != %v in assignment %v = %v", - NewIntVector(ix.outShape), NewIntVector([]int{len(rhs)}), - top.ProgString(), right.ProgString()) + } else { + switch rhs := rhs.(type) { + default: + rscalar = rhs + case Vector: + if len(ix.outShape) != 1 || ix.outShape[0] != len(rhs) { + Errorf("shape mismatch %v != %v in assignment %v = %v", + NewIntVector(ix.outShape), NewIntVector([]int{len(rhs)}), + top.ProgString(), right.ProgString()) + } + rslice = rhs + case *Matrix: + if !sameShape(ix.outShape, rhs.Shape()) { + Errorf("shape mismatch %v != %v in assignment %v = %v", + NewIntVector(ix.outShape), NewIntVector(rhs.Shape()), + top.ProgString(), right.ProgString()) + } + rslice = rhs.Data() + if rhs == ix.lhs { + // Assigning entire rhs to some permutation of lhs. + // Make copy of slice to avoid problems with overwriting + // values we need to read later. Uncommon. + rslice = make([]Value, len(rslice)) + copy(rslice, rhs.Data()) + } } - rslice = rhs } origin := Int(context.Config().Origin()) @@ -228,7 +233,8 @@ func IndexAssign(context Context, top, left Expr, index []Expr, right Expr, rhs } offset += int(ix.indexes[j][0].(Int) - origin) } - ix.slice[offset] = rscalar + ix.slice[offset] = rscalar.Copy() + return } copySize := int(size(ix.shape[len(ix.indexes):])) @@ -255,11 +261,13 @@ func IndexAssign(context Context, top, left Expr, index []Expr, right Expr, rhs } dst := ix.slice[offset*copySize : (offset+1)*copySize] if rscalar != nil { - for i := range dst { - dst[i] = rscalar + for j := range dst { + dst[j] = rscalar.Copy() } } else { - copy(dst, rslice[i*copySize:(i+1)*copySize]) + for j := range dst { + dst[j] = rslice[j+i*copySize].Copy() + } } // Increment coord. diff --git a/value/int.go b/value/int.go index 2fe30758..f089998a 100644 --- a/value/int.go +++ b/value/int.go @@ -136,6 +136,10 @@ func (i Int) Eval(Context) Value { return i } +func (i Int) Copy() Value { + return i +} + func (i Int) Inner() Value { return i } diff --git a/value/matrix.go b/value/matrix.go index d5f68fa8..769622af 100644 --- a/value/matrix.go +++ b/value/matrix.go @@ -46,7 +46,7 @@ func (m *Matrix) Data() Vector { return m.data } -func (m *Matrix) Copy() *Matrix { +func (m *Matrix) Copy() Value { shape := make([]int, len(m.shape)) data := make([]Value, len(m.data)) copy(shape, m.shape) @@ -57,6 +57,23 @@ func (m *Matrix) Copy() *Matrix { } } +// elemStrs returns the formatted elements of the matrix and the width of the widest element. +func (m *Matrix) elemStrs(conf *config.Config) ([]string, int) { + strs := make([]string, len(m.data)) + wid := 1 + for i, elem := range m.data { + s := elem.Sprint(conf) + if !isScalarType(elem) { + s = "(" + s + ")" + } + strs[i] = s + if len(s) > wid { + wid = len(s) + } + } + return strs, wid +} + // write2d prints the 2d matrix m into the buffer. // value is a slice of already-printed values. // The receiver provides only the shape of the matrix. @@ -145,18 +162,7 @@ func (m *Matrix) Sprint(conf *config.Config) string { } break } - // We print the elements into one big string, - // slice that, and then format so they line up. - // Will need some rethinking when decimal points - // can appear. - // Vector.String does what we want for the first part. - strs := strings.Split(m.data.makeString(conf, true), " ") - wid := 1 - for _, s := range strs { - if wid < len(s) { - wid = len(s) - } - } + strs, wid := m.elemStrs(conf) m.write2d(&b, strs, wid) case 3: // If it's all chars, print it without padding or quotes. @@ -175,15 +181,9 @@ func (m *Matrix) Sprint(conf *config.Config) string { } // As for 2d: print the vector elements, compute the // global width, and use that to print each 2d submatrix. - strs := strings.Split(m.data.Sprint(conf), " ") - wid := 1 - for _, s := range strs { - if wid < len(s) { - wid = len(s) - } - } n2d := m.shape[0] // number of 2d submatrices. size := m.ElemSize() // number of elems in each submatrix. + strs, wid := m.elemStrs(conf) start := int64(0) for i := 0; i < n2d; i++ { if i > 0 { diff --git a/value/sets.go b/value/sets.go index fb00e945..052ef9f2 100644 --- a/value/sets.go +++ b/value/sets.go @@ -27,7 +27,7 @@ func union(c Context, u, v Value) Value { // At least one is a Vector. switch { case vType != vectorType: - uu := u.(Vector).Copy() + uu := u.(Vector).Copy().(Vector) for _, x := range uu { if scalarEqual(c, x, v) { return uu @@ -44,7 +44,7 @@ func union(c Context, u, v Value) Value { } return NewVector(elems) default: // Both vectors. - uu := u.(Vector).Copy() + uu := u.(Vector).Copy().(Vector) vv := v.(Vector) present := membership(c, vv, uu) for i, x := range vv { @@ -66,12 +66,12 @@ func intersect(c Context, u, v Value) Value { } return NewVector([]Value{}) } - // Neither can be a matrix. + // Neither can be a matrix. Yet. TODO if uType == matrixType || vType == matrixType { Errorf("binary intersect not implemented on type matrix") } // At least one is a Vector. - var elems []Value + elems := []Value{} switch { case vType != vectorType: uu := u.(Vector) @@ -158,24 +158,31 @@ func scalarEqual(c Context, u, v Value) bool { return OrderedCompare(c, u, v) == 0 } -// OrderedCompare returns -1, 0, or 1 according to whether u is -// less than, equal to, or greater than v, according to total ordering -// rules. Total ordering is not the usual mathematical definition, -// as we honor things like 1.0 == 1, comparison of int and char -// is forbidden, and complex numbers do not implement <. +// OrderedCompare returns -1, 0, or 1 according to whether u is less than, equal +// to, or greater than v, according to total ordering rules. Total ordering is not +// the usual mathematical definition, as we honor things like 1.0 == 1, comparison +// of int and char is forbidden, and complex numbers do not implement <. Plus other +// than for scalars we don't need to follow any particular rules at all, just what +// works for sorting sets. +// // Thus we amend the usual orderings: // - Char is below all other types -// - Complex is above all other types, unless on the real line: 1j0 == 1. +// - All other scalars are compared directly, except... +// - ...Complex is above all other scalars, unless on the real line: 1j0 == 1. +// - Vector is above all other types except... +// - ...Matrix, which is above all other types. +// +// When comparing identically-typed values: +// - Complex is ordered first by real component, then by imaginary. +// - Vector and Matrix are ordered first by number of elements, +// then in lexical order of elements. // -// Exported only for testing, which is done by the parent directory. -// TODO: Expand to vectors and matrices? +// These are unusual rules, but they are provide a unique ordering of elements +// sufficient for set membership. // Exported for testing, which is done by the +// parent directory to avoid a dependency cycle. func OrderedCompare(c Context, u, v Value) int { uType := whichType(u) vType := whichType(v) - if uType >= vectorType || vType >= vectorType { - Errorf("internal error: non-scalar type %T in orderedCompare", u) - } - // We know we have scalars. if uType != vType { // If either is a Char, that orders below all others. if uType == charType { @@ -185,8 +192,21 @@ func OrderedCompare(c Context, u, v Value) int { return 1 } // Need to do it the hard way. - // If either is a Complex, that orders above all others, - // unless it is on the real line. + // First, if one is a Matrix, it orders above all others. + if uType == matrixType { + return 1 + } + if vType == matrixType { + return -1 + } + // Next, if one is a Vector, it orders above all others. + if uType == vectorType { + return 1 + } + if vType == vectorType { + return -1 + } + // If a complex is on the real line, treat it as a number. if uC, ok := u.(Complex); ok && uC.isReal() { return OrderedCompare(c, uC.real, v) } @@ -222,9 +242,34 @@ func OrderedCompare(c Context, u, v Value) int { return s } return OrderedCompare(c, uu.imag, vv.imag) - + case vectorType: + uu := u.(Vector) + vv := v.(Vector) + if len(uu) != len(vv) { + return sgn2Int(len(uu), len(vv)) + } + for i, x := range uu { + s := OrderedCompare(c, x, vv[i]) + if s != 0 { + return s + } + } + return 0 + case matrixType: + uu := u.(*Matrix) + vv := v.(*Matrix) + if len(uu.data) != len(vv.data) { + return sgn2Int(len(uu.data), len(vv.data)) + } + for i, x := range uu.data { + s := OrderedCompare(c, x, vv.data[i]) + if s != 0 { + return s + } + } + return 0 } - Errorf("internal error: unknown type %T in orderedCompare", u) + Errorf("internal error: unknown type %T in OrderedCompare", u) return -1 } diff --git a/value/unary.go b/value/unary.go index 1f7d8a41..fdda0a90 100644 --- a/value/unary.go +++ b/value/unary.go @@ -608,7 +608,7 @@ func init() { return v.(Vector).reverse() }, matrixType: func(c Context, v Value) Value { - m := v.(*Matrix).Copy() + m := v.(*Matrix).Copy().(*Matrix) if m.Rank() == 0 { return m } @@ -641,7 +641,7 @@ func init() { return v.(Vector).reverse() }, matrixType: func(c Context, v Value) Value { - m := v.(*Matrix).Copy() + m := v.(*Matrix).Copy().(*Matrix) if m.Rank() == 0 { return m } @@ -908,7 +908,7 @@ func init() { if !text.AllChars() { Errorf("ivy: value is not a vector of char") } - return IvyEval(c, text.makeString(c.Config(), false)) + return IvyEval(c, text.makeString(c.Config(), false, false)) }, }, }, diff --git a/value/value.go b/value/value.go index 3eff9b77..2c073069 100644 --- a/value/value.go +++ b/value/value.go @@ -20,8 +20,13 @@ type Value interface { // All user output should call Sprint instead. String() string Sprint(*config.Config) string + + // Eval evaluates (simplifies) the Value. Eval(Context) Value + // Copy returns a copy of the Value. + Copy() Value + // Inner retrieves the value, without evaluation. But for Assignments, // it returns the right-hand side. Inner() Value diff --git a/value/vector.go b/value/vector.go index 24b7da35..c9c418e2 100644 --- a/value/vector.go +++ b/value/vector.go @@ -20,7 +20,7 @@ func (v Vector) String() string { } func (v Vector) Sprint(conf *config.Config) string { - return v.makeString(conf, !v.AllChars()) + return v.makeString(conf, !v.allScalars(), !v.AllChars()) } func (v Vector) Rank() int { @@ -33,17 +33,25 @@ func (v Vector) ProgString() string { panic("vector.ProgString - cannot happen") } -// makeString is like String but takes a flag specifying +// makeString is like String but takes flags specifying +// whether parentheses will be needed and // whether to put spaces between the elements. By // default (that is, by calling String) spaces are suppressed // if all the elements of the Vector are Chars. -func (v Vector) makeString(conf *config.Config, spaces bool) string { +func (v Vector) makeString(conf *config.Config, parens, spaces bool) string { var b bytes.Buffer + if parens { + spaces = true + } for i, elem := range v { if spaces && i > 0 { fmt.Fprint(&b, " ") } - fmt.Fprintf(&b, "%s", elem.Sprint(conf)) + if parens && !isScalarType(elem) { + fmt.Fprintf(&b, "(%s)", elem.Sprint(conf)) + } else { + fmt.Fprintf(&b, "%s", elem.Sprint(conf)) + } } return b.String() } @@ -58,6 +66,16 @@ func (v Vector) AllChars() bool { return true } +// allScalars reports whether all the elements are scalar. +func (v Vector) allScalars() bool { + for _, x := range v { + if !isScalarType(x) { + return false + } + } + return true +} + // AllInts reports whether the vector contains only Ints. func (v Vector) AllInts() bool { for _, c := range v { @@ -69,6 +87,10 @@ func (v Vector) AllInts() bool { } func NewVector(elems []Value) Vector { + if elems == nil { + // Really shouldn't happen, so catch it if it does. + Errorf("internal error: nil vector") + } return Vector(elems) } @@ -88,7 +110,7 @@ func (v Vector) Inner() Value { return v } -func (v Vector) Copy() Vector { +func (v Vector) Copy() Value { elem := make([]Value, len(v)) copy(elem, v) return NewVector(elem) @@ -151,7 +173,7 @@ func (v Vector) grade(c Context) Vector { // reverse returns the reversal of a vector. func (v Vector) reverse() Vector { - r := v.Copy() + r := v.Copy().(Vector) for i, j := 0, len(r)-1; i < j; i, j = i+1, j-1 { r[i], r[j] = r[j], r[i] } @@ -159,7 +181,7 @@ func (v Vector) reverse() Vector { } // membership creates a vector of size len(u) reporting -// whether each element is an element of v. +// whether each element of u is an element of v. // Algorithm is O(nV log nV + nU log nV) where nU==len(u) and nV==len(V). func membership(c Context, u, v Vector) []Value { values := make([]Value, len(u)) @@ -189,7 +211,7 @@ func (v Vector) contains(c Context, x Value) bool { pos := sort.Search(len(v), func(j int) bool { return OrderedCompare(c, v[j], x) >= 0 }) - return pos < len(v) && c.EvalBinary(v[pos], "==", x) == Int(1) + return pos < len(v) && OrderedCompare(c, v[pos], x) == 0 } func (v Vector) shrink() Value {