mirror of
https://github.com/gopl-zh/gopl-zh.github.com.git
synced 2024-12-29 08:16:21 +00:00
184 lines
3.7 KiB
Go
184 lines
3.7 KiB
Go
|
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
|
||
|
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||
|
|
||
|
package sexpr
|
||
|
|
||
|
// This file implements the algorithm described in Derek C. Oppen's
|
||
|
// 1979 Stanford technical report, "Pretty Printing".
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"fmt"
|
||
|
"reflect"
|
||
|
)
|
||
|
|
||
|
func MarshalIndent(v interface{}) ([]byte, error) {
|
||
|
p := printer{width: margin}
|
||
|
if err := pretty(&p, reflect.ValueOf(v)); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return p.Bytes(), nil
|
||
|
}
|
||
|
|
||
|
const margin = 80
|
||
|
|
||
|
type token struct {
|
||
|
kind rune // one of "s ()" (string, blank, start, end)
|
||
|
str string
|
||
|
size int
|
||
|
}
|
||
|
|
||
|
type printer struct {
|
||
|
tokens []*token // FIFO buffer
|
||
|
stack []*token // stack of open ' ' and '(' tokens
|
||
|
rtotal int // total number of spaces needed to print stream
|
||
|
|
||
|
bytes.Buffer
|
||
|
indents []int
|
||
|
width int // remaining space
|
||
|
}
|
||
|
|
||
|
func (p *printer) string(str string) {
|
||
|
tok := &token{kind: 's', str: str, size: len(str)}
|
||
|
if len(p.stack) == 0 {
|
||
|
p.print(tok)
|
||
|
} else {
|
||
|
p.tokens = append(p.tokens, tok)
|
||
|
p.rtotal += len(str)
|
||
|
}
|
||
|
}
|
||
|
func (p *printer) pop() (top *token) {
|
||
|
last := len(p.stack) - 1
|
||
|
top, p.stack = p.stack[last], p.stack[:last]
|
||
|
return
|
||
|
}
|
||
|
func (p *printer) begin() {
|
||
|
if len(p.stack) == 0 {
|
||
|
p.rtotal = 1
|
||
|
}
|
||
|
t := &token{kind: '(', size: -p.rtotal}
|
||
|
p.tokens = append(p.tokens, t)
|
||
|
p.stack = append(p.stack, t) // push
|
||
|
p.string("(")
|
||
|
}
|
||
|
func (p *printer) end() {
|
||
|
p.string(")")
|
||
|
p.tokens = append(p.tokens, &token{kind: ')'})
|
||
|
x := p.pop()
|
||
|
x.size += p.rtotal
|
||
|
if x.kind == ' ' {
|
||
|
p.pop().size += p.rtotal
|
||
|
}
|
||
|
if len(p.stack) == 0 {
|
||
|
for _, tok := range p.tokens {
|
||
|
p.print(tok)
|
||
|
}
|
||
|
p.tokens = nil
|
||
|
}
|
||
|
}
|
||
|
func (p *printer) space() {
|
||
|
last := len(p.stack) - 1
|
||
|
x := p.stack[last]
|
||
|
if x.kind == ' ' {
|
||
|
x.size += p.rtotal
|
||
|
p.stack = p.stack[:last] // pop
|
||
|
}
|
||
|
t := &token{kind: ' ', size: -p.rtotal}
|
||
|
p.tokens = append(p.tokens, t)
|
||
|
p.stack = append(p.stack, t)
|
||
|
p.rtotal++
|
||
|
}
|
||
|
func (p *printer) print(t *token) {
|
||
|
switch t.kind {
|
||
|
case 's':
|
||
|
p.WriteString(t.str)
|
||
|
p.width -= len(t.str)
|
||
|
case '(':
|
||
|
p.indents = append(p.indents, p.width)
|
||
|
case ')':
|
||
|
p.indents = p.indents[:len(p.indents)-1] // pop
|
||
|
case ' ':
|
||
|
if t.size > p.width {
|
||
|
p.width = p.indents[len(p.indents)-1] - 1
|
||
|
fmt.Fprintf(&p.Buffer, "\n%*s", margin-p.width, "")
|
||
|
} else {
|
||
|
p.WriteByte(' ')
|
||
|
p.width--
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
func (p *printer) stringf(format string, args ...interface{}) {
|
||
|
p.string(fmt.Sprintf(format, args...))
|
||
|
}
|
||
|
|
||
|
func pretty(p *printer, v reflect.Value) error {
|
||
|
switch v.Kind() {
|
||
|
case reflect.Invalid:
|
||
|
p.string("nil")
|
||
|
|
||
|
case reflect.Int, reflect.Int8, reflect.Int16,
|
||
|
reflect.Int32, reflect.Int64:
|
||
|
p.stringf("%d", v.Int())
|
||
|
|
||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16,
|
||
|
reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||
|
p.stringf("%d", v.Uint())
|
||
|
|
||
|
case reflect.String:
|
||
|
p.stringf("%q", v.String())
|
||
|
|
||
|
case reflect.Array, reflect.Slice: // (value ...)
|
||
|
p.begin()
|
||
|
for i := 0; i < v.Len(); i++ {
|
||
|
if i > 0 {
|
||
|
p.space()
|
||
|
}
|
||
|
if err := pretty(p, v.Index(i)); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
p.end()
|
||
|
|
||
|
case reflect.Struct: // ((name value ...)
|
||
|
p.begin()
|
||
|
for i := 0; i < v.NumField(); i++ {
|
||
|
if i > 0 {
|
||
|
p.space()
|
||
|
}
|
||
|
p.begin()
|
||
|
p.string(v.Type().Field(i).Name)
|
||
|
p.space()
|
||
|
if err := pretty(p, v.Field(i)); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
p.end()
|
||
|
}
|
||
|
p.end()
|
||
|
|
||
|
case reflect.Map: // ((key value ...)
|
||
|
p.begin()
|
||
|
for i, key := range v.MapKeys() {
|
||
|
if i > 0 {
|
||
|
p.space()
|
||
|
}
|
||
|
p.begin()
|
||
|
if err := pretty(p, key); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
p.space()
|
||
|
if err := pretty(p, v.MapIndex(key)); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
p.end()
|
||
|
}
|
||
|
p.end()
|
||
|
|
||
|
case reflect.Ptr:
|
||
|
return pretty(p, v.Elem())
|
||
|
|
||
|
default: // float, complex, bool, chan, func, interface
|
||
|
return fmt.Errorf("unsupported type: %s", v.Type())
|
||
|
}
|
||
|
return nil
|
||
|
}
|