Skip to content

Commit

Permalink
ivy: improve matrix printing when elements are compound
Browse files Browse the repository at this point in the history
Building on the work done in the previous commit to make vector
printing handle multiline objects well, provide the same service
for matrices. This step was arguably simpler: all we need to do is
treat the matrix as a vector, print it, and then cut and paste the
pieces together. It works well and looks as well as it is likely
to.

x= 3 4 5 rho iota 100; x[2;2;2]=3 4 rho iota 12; 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  4|            28            29            30
              | 5  6  7  8|
              | 9 10 11 12)
           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

Fixes #117
  • Loading branch information
robpike committed Jul 5, 2023
1 parent 2d2539c commit 07efe1f
Show file tree
Hide file tree
Showing 4 changed files with 166 additions and 66 deletions.
64 changes: 64 additions & 0 deletions testdata/print.ivy
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,67 @@ x = 3 4 5 rho iota 60; x[2;2;2] = 1 2 3; x
46 47 48 49 50
51 52 53 54 55
56 57 58 59 60

x= 3 4 5 rho iota 100; 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

x= 3 4 5 rho iota 100; x[2;2;2]=3 4 rho iota 12; 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 4| 28 29 30
| 5 6 7 8|
| 9 10 11 12)
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

3 4 rho 1 (2 3 rho iota 6)
1 (1 2 3| 1 (1 2 3|
|4 5 6) |4 5 6)
1 (1 2 3| 1 (1 2 3|
|4 5 6) |4 5 6)
1 (1 2 3| 1 (1 2 3|
|4 5 6) |4 5 6)

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

3 3 rho (2 2 rho 'ab') (3 3 rho 'cd')
(ab| (cdc| (ab|
|ab) |dcd| |ab)
|cdc)
(cdc| (ab| (cdc|
|dcd| |ab) |dcd|
|cdc) |cdc)
(ab| (cdc| (ab|
|ab) |dcd| |ab)
|cdc)
77 changes: 52 additions & 25 deletions value/matrix.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,48 +58,72 @@ func (m *Matrix) Copy() Value {
}

// 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 + ")"
// Each element is represented by a slice of lines, that is, the return value is indexed by
// [elem][line].
func (m *Matrix) elemStrs(conf *config.Config) ([][]string, int) {
// Format the matrix as a vector, and then in write2d we rearrange the pieces.
// In the formatting, there's no need for spacing the elements as we'll cut
// them apart ourselves using column information. Spaces will be added
// when needed in write2d.
v := NewVector(m.data)
lines, cols := v.multiLineSprint(conf, v.allScalars(), v.AllChars(), !withSpaces, !trimTrailingSpace)
strs := make([][]string, len(m.data))
wid := 0
for i := range m.data {
rows := make([]string, len(lines))
for j, line := range lines {
if i == 0 {
rows[j] = line[:cols[0]]
} else {
rows[j] = line[cols[i-1]:cols[i]]
}
}
strs[i] = s
if len(s) > wid {
wid = len(s)
if len(rows[0]) > wid {
wid = len(rows[0])
}
strs[i] = rows
}
return strs, wid
}

// write2d prints the 2d matrix m into the buffer.
// value is a slice of already-printed values.
// elems is a slice (of slices) of already-printed values.
// The receiver provides only the shape of the matrix.
func (m *Matrix) write2d(b *bytes.Buffer, value []string, width int) {
func (m *Matrix) write2d(b *bytes.Buffer, elems [][]string, width int) {
nrows := m.shape[0]
ncols := m.shape[1]
index := 0
for row := 0; row < nrows; row++ {
if row > 0 {
b.WriteByte('\n')
}
index := row * ncols
// Don't print the line if it has no content.
nonBlankLine := 0
for col := 0; col < ncols; col++ {
if col > 0 {
b.WriteByte(' ')
strs := elems[index+col]
for line := nonBlankLine; line < len(strs); line++ {
for _, r := range strs[line] {
if r != ' ' {
nonBlankLine = line
break
}
}
}
s := value[index]
pad := width - len(s)
for ; pad >= 10; pad -= 10 {
b.WriteString(" ")
}
for line := 0; line < nonBlankLine+1; line++ {
if line > 0 {
b.WriteByte('\n')
}
for ; pad > 0; pad-- {
b.WriteString(" ")
for col := 0; col < ncols; col++ {
str := elems[index+col][line]
b.WriteString(blanks(width - len(str)))
b.WriteString(str)
if (col+1)%ncols != 0 {
b.WriteString(" ")
}
}
b.WriteString(s)
index++
}
index += ncols
}
}

Expand Down Expand Up @@ -249,12 +273,12 @@ func indent(indentation int, format string, args ...interface{}) string {
return b.String()
}

// spaces returns 2*n space characters.
// spaces returns 2*n space characters, maxing out at 2*10.
func spaces(n int) string {
if n > 10 {
n = 10
}
return " "[:2*n]
return blanks(2 * n)
}

// Size returns number of elements of the matrix.
Expand Down Expand Up @@ -640,6 +664,9 @@ func (m *Matrix) sel(c Context, v Vector) *Matrix {

// take returns v take m.
func (m *Matrix) take(c Context, v Vector) *Matrix {
if !v.AllInts() {
Errorf("take: left operand must be small integers")
}
// Extend short vector to full rank using shape.
if len(v) > m.Rank() {
Errorf("take: bad length %d for shape %s", len(v), NewIntVector(m.Shape()))
Expand Down
3 changes: 2 additions & 1 deletion value/unary.go
Original file line number Diff line number Diff line change
Expand Up @@ -908,7 +908,8 @@ func init() {
if !text.AllChars() {
Errorf("ivy: value is not a vector of char")
}
return IvyEval(c, text.oneLineString(c.Config(), false, false))
str, _ := text.oneLineSprint(c.Config(), !withParens, !withSpaces)
return IvyEval(c, str)
},
},
},
Expand Down
88 changes: 48 additions & 40 deletions value/vector.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,7 @@ func (v Vector) String() string {

func (v Vector) Sprint(conf *config.Config) string {
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)
lines, _ := v.multiLineSprint(conf, v.allScalars(), allChars, !allChars, trimTrailingSpace)
switch len(lines) {
case 0:
return ""
Expand Down Expand Up @@ -56,15 +51,23 @@ func (v Vector) ProgString() string {
panic("vector.ProgString - cannot happen")
}

// oneLineString prints a vector as a single line (assuming
// Constants to make it easier to read calls to the printing routines.
const (
withParens = true
withSpaces = true
trimTrailingSpace = true
)

// oneLineSprint 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 {
func (v Vector) oneLineSprint(conf *config.Config, parens, spaces bool) (string, []int) {
var b bytes.Buffer
if parens {
spaces = true
}
cols := make([]int, len(v))
for i, elem := range v {
if spaces && i > 0 {
fmt.Fprint(&b, " ")
Expand All @@ -74,35 +77,43 @@ func (v Vector) oneLineString(conf *config.Config, parens, spaces bool) string {
} else {
fmt.Fprintf(&b, "%s", elem.Sprint(conf))
}
cols[i] = b.Len()
}
return b.String()
return b.String(), cols
}

// mutiLineString formats a vector that may span multiple lines,
// returning the results as a slice of strings, one per line.
// multiLineSprint formats a vector that may span multiple lines,
// returning the result 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.
// allScalars: the vector is all scalar values and can be printed without parens.
// allChars: the vector is all chars and can be printed extra simply.
// 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 {
//
// The return values are the printed lines and, along the other axis,
// byte positions after each column.
func (v Vector) multiLineSprint(conf *config.Config, allScalars, allChars, spaces, trim bool) ([]string, []int) {
if allScalars {
// Easy case, might as well be efficient.
str, cols := v.oneLineSprint(conf, false, spaces)
return []string{str}, cols
}
cols := make([]int, len(v))
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.
// in the general case handled below.
b := strings.Builder{}
for _, c := range v {
for i, c := range v {
b.WriteRune(rune(c.Inner().(Char)))
cols[i] = b.Len()
}
return strings.Split(b.String(), "\n")
return strings.Split(b.String(), "\n"), cols // We shouldn't need cols, but be safe.
}
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) {
Expand All @@ -128,49 +139,46 @@ func (v Vector) mutiLineString(conf *config.Config, trim, allChars, parens, spac
line.WriteString(" ")
}
}
doParens := parens && !isScalarType(elem)
doParens := !allScalars && !isScalarType(elem)
if doParens {
lines[0].WriteString("(")
lastColumn[0] = lines[0].Len()
}
maxWid := 0
for i, s := range strs {
for n, 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 := lines[n]
if doParens && n > 0 {
line.WriteString("|")
lastColumn[i] = line.Len()
w = 1
lastColumn[n] = line.Len()
}
line.WriteString(s)
lastColumn[i] = line.Len()
w += len(s)
if doParens && i < len(strs)-1 {
lastColumn[n] = line.Len()
if doParens && n < len(strs)-1 {
line.WriteString("|")
lastColumn[i] = line.Len()
w++
}
if w > maxWid {
maxWid = w
lastColumn[n] = line.Len()
}
}
cols[i] = lines[0].Len() // By construction all lines have same length.
if len(strs) < len(lines) {
// Right-fill the lines below this element.
padding := blanks(maxWid)
padding := blanks(cols[i] - lines[len(lines)-1].Len())
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()
line := lines[last]
line.WriteString(")")
lastColumn[last] = line.Len()
if line.Len() > cols[i] {
cols[i] = line.Len()
}
}
}
s := make([]string, len(lines))
Expand All @@ -180,7 +188,7 @@ func (v Vector) mutiLineString(conf *config.Config, trim, allChars, parens, spac
s[i] = s[i][:lastColumn[i]]
}
}
return s
return s, cols
}

var (
Expand Down

0 comments on commit 07efe1f

Please sign in to comment.