Skip to content

Commit

Permalink
ivy: better formatting for vectors containing multiline elements
Browse files Browse the repository at this point in the history
Make the columns and rows line up in a meaningful way when there
is, for example, a 2d matrix in the middle of a vector.  There are
surely many cases this won't handle well, but they should be quite
rare, such as a 5d matrix inside a vector. And for the common cases
that might actually arise, the output is reasonable.

Also add testdata/print.ivy to have a single place to write
tests for this. There may be many as things evolve.

The same treatment for matrices will come next, or soon at least.

Update #117
  • Loading branch information
robpike committed Jul 4, 2023
1 parent 05a8373 commit 2d2539c
Show file tree
Hide file tree
Showing 3 changed files with 287 additions and 8 deletions.
128 changes: 128 additions & 0 deletions testdata/print.ivy
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# Copyright 2023 The Go Authors. All rights reserved.
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.

# Test printing, particularly when the output is multline.

# First, the easy stuff.

1
1

float(1.01)
1.01

1/3
1/3

10000000000+1
10000000001

sqrt 2
1.41421356237

3j4
3j4

iota 5
1 2 3 4 5

3 4 rho iota 12
1 2 3 4
5 6 7 8
9 10 11 12

'a' 'b' 2 'c'
a b 2 c

'abc'
abc

# Vector with vector elements
1 (1 2) (1 2 3)
1 (1 2) (1 2 3)

(1 2) (3 4) (5 6)
(1 2) (3 4) (5 6)

1 'ab' (1 2 3)
1 (ab) (1 2 3)

1 2 3 (10 11 12) 5 6 7
1 2 3 (10 11 12) 5 6 7

# Vector with matrix elements. Various versions to test
# alignment and indenting

1 2 3 (3 3 rho iota 10) 5 6 7
1 2 3 (1 2 3| 5 6 7
|4 5 6|
|7 8 9)

1 2 3 (3 3 rho iota 10) 5 6 (2 2 rho iota 10) 7
1 2 3 (1 2 3| 5 6 (1 2| 7
|4 5 6| |3 4)
|7 8 9)

1 2 3 5 6 (4 4 rho iota 10) 7 5 6 (4 4 rho iota 10) 7
1 2 3 5 6 ( 1 2 3 4| 7 5 6 ( 1 2 3 4| 7
| 5 6 7 8| | 5 6 7 8|
| 9 10 1 2| | 9 10 1 2|
| 3 4 5 6) | 3 4 5 6)

1 2 3 (2 2 rho iota 10) 5 6 (4 4 rho iota 10) 7 5 6 (4 4 rho iota 10) 7
1 2 3 (1 2| 5 6 ( 1 2 3 4| 7 5 6 ( 1 2 3 4| 7
|3 4) | 5 6 7 8| | 5 6 7 8|
| 9 10 1 2| | 9 10 1 2|
| 3 4 5 6) | 3 4 5 6)

1 2 3 (2 2 rho iota 10) 5 6 (4 4 rho iota 10) 7 5 6 (4 4 rho iota 10) 7 8 (5 5 rho iota 10) 9
1 2 3 (1 2| 5 6 ( 1 2 3 4| 7 5 6 ( 1 2 3 4| 7 8 ( 1 2 3 4 5| 9
|3 4) | 5 6 7 8| | 5 6 7 8| | 6 7 8 9 10|
| 9 10 1 2| | 9 10 1 2| | 1 2 3 4 5|
| 3 4 5 6) | 3 4 5 6) | 6 7 8 9 10|
| 1 2 3 4 5)

x=1 2 3; x[2]=3 4 5 rho iota 100; x
1 ( 1 2 3 4 5| 3
| 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 34 35|
|36 37 38 39 40|

|41 42 43 44 45|
|46 47 48 49 50|
|51 52 53 54 55|
|56 57 58 59 60)


# Matrix with vector elements
3 4 rho (1 2) (3 4)
(1 2) (3 4) (1 2) (3 4)
(1 2) (3 4) (1 2) (3 4)
(1 2) (3 4) (1 2) (3 4)

x = 3 4 rho iota 12; x[2;2] = 1 2 3; x
1 2 3 4
5 (1 2 3) 7 8
9 10 11 12

x = 3 4 5 rho iota 60; x[2;2;2] = 1 2 3; x
1 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 (1 2 3) 28 29 30
31 32 33 34 35
36 37 38 39 40

41 42 43 44 45
46 47 48 49 50
51 52 53 54 55
56 57 58 59 60
2 changes: 1 addition & 1 deletion value/unary.go
Original file line number Diff line number Diff line change
Expand Up @@ -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, false))
return IvyEval(c, text.oneLineString(c.Config(), false, false))
},
},
},
Expand Down
165 changes: 158 additions & 7 deletions value/vector.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"fmt"
"math"
"sort"
"strings"
"sync"

"robpike.io/ivy/config"
)
Expand All @@ -20,7 +22,28 @@ func (v Vector) String() string {
}

func (v Vector) Sprint(conf *config.Config) string {
return v.makeString(conf, !v.allScalars(), !v.AllChars())
allChars := v.AllChars()
allScalars := v.allScalars()
if allScalars {
// Easy case, might as well be efficient.
return v.oneLineString(conf, false, !allChars)
}
lines := v.mutiLineString(conf, true, allChars, !allScalars, !allChars)
switch len(lines) {
case 0:
return ""
case 1:
return lines[0]
default:
var b strings.Builder
for i, line := range lines {
if i > 0 {
b.WriteString("\n")
}
b.WriteString(line)
}
return b.String()
}
}

func (v Vector) Rank() int {
Expand All @@ -33,12 +56,11 @@ func (v Vector) ProgString() string {
panic("vector.ProgString - cannot happen")
}

// 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, parens, spaces bool) string {
// oneLineString prints a vector as a single line (assuming
// there are no hidden newlines within) and returns the result.
// Flags report whether parentheses will be needed and
// whether to put spaces between the elements.
func (v Vector) oneLineString(conf *config.Config, parens, spaces bool) string {
var b bytes.Buffer
if parens {
spaces = true
Expand All @@ -56,6 +78,135 @@ func (v Vector) makeString(conf *config.Config, parens, spaces bool) string {
return b.String()
}

// mutiLineString formats a vector that may span multiple lines,
// returning the results as a slice of strings, one per line.
// Lots of flags:
// allChars: the vector is all chars and can be printed simply.
// parens: may need parens around an element.
// spaces: put spaces between elements.
// trim: remove trailing spaces from each line.
// If trim is not set, the lines are all of equal length, bytewise.
func (v Vector) mutiLineString(conf *config.Config, trim, allChars, parens, spaces bool) []string {
if allChars {
// Special handling as the array may contain newlines.
// Ignore all the other flags.
// TODO: We can still get newlines for individual elements
// the general case handled below.
b := strings.Builder{}
for _, c := range v {
b.WriteRune(rune(c.Inner().(Char)))
}
return strings.Split(b.String(), "\n")
}
lines := []*strings.Builder{}
lastColumn := []int{} // For each line, last column with a non-padding character.
if parens {
spaces = true
}
for i, elem := range v {
strs := strings.Split(elem.Sprint(conf), "\n")
if len(strs) > len(lines) {
wid := 0
for _, line := range lines {
if line.Len() > wid {
wid = line.Len()
}
}
leading := blanks(wid)
for j := range strs {
if j >= len(lines) {
lastColumn = append(lastColumn, 0)
lines = append(lines, &strings.Builder{})
if j > 0 {
lines[j].WriteString(leading)
}
}
}
}
if spaces && i > 0 {
for _, line := range lines {
line.WriteString(" ")
}
}
doParens := parens && !isScalarType(elem)
if doParens {
lines[0].WriteString("(")
lastColumn[0] = lines[0].Len()
}
maxWid := 0
for i, s := range strs {
if s == "" {
if _, ok := elem.(*Matrix); ok {
// Blank line in matrix output; ignore
continue
}
}
line := lines[i]
w := 0
if doParens && i > 0 {
line.WriteString("|")
lastColumn[i] = line.Len()
w = 1
}
line.WriteString(s)
lastColumn[i] = line.Len()
w += len(s)
if doParens && i < len(strs)-1 {
line.WriteString("|")
lastColumn[i] = line.Len()
w++
}
if w > maxWid {
maxWid = w
}
}
if len(strs) < len(lines) {
// Right-fill the lines below this element.
padding := blanks(maxWid)
for j := len(strs); j < len(lines); j++ {
lines[j].WriteString(padding)
}
}
if doParens {
last := len(strs) - 1
lines[last].WriteString(")")
lastColumn[last] = lines[last].Len()
}
}
s := make([]string, len(lines))
for i := range s {
s[i] = lines[i].String()
if trim {
s[i] = s[i][:lastColumn[i]]
}
}
return s
}

var (
blanksLock sync.RWMutex
staticBlanks string
)

// blanks returns a string of n blanks.
func blanks(n int) string {
for {
blanksLock.RLock()
if len(staticBlanks) >= n {
result := staticBlanks[:n]
blanksLock.RUnlock()
return result
}
blanksLock.RUnlock()
blanksLock.Lock()
if len(staticBlanks) < n {
staticBlanks = strings.Repeat(" ", n+32)
}
blanksLock.Unlock()
}

}

// AllChars reports whether the vector contains only Chars.
func (v Vector) AllChars() bool {
for _, c := range v {
Expand Down

0 comments on commit 2d2539c

Please sign in to comment.