Commit 130e6f42 authored by Robert Griesemer's avatar Robert Griesemer

- implemented arbitrary padding char for tabwriter

- implemented right-to-left alignment (numerical results)
- better comments and error handling
- added more tests
- updated dependent files

R=r
DELTA=232  (175 added, 11 deleted, 46 changed)
OCL=19761
CL=19780
parent 60db3d6d
......@@ -63,34 +63,38 @@ func (b *ByteArray) Append(s *[]byte) {
// ----------------------------------------------------------------------------
// Writer is a filter implementing the io.Write interface. It assumes
// that the incoming bytes represent ASCII encoded text consisting of
// lines of tab-separated "cells". Cells in adjacent lines constitute
// lines of tab-terminated "cells". Cells in adjacent lines constitute
// a column. Writer rewrites the incoming text such that all cells in
// a column have the same width; thus it effectively aligns cells. It
// does this by adding padding where necessary.
//
// Formatting can be controlled via parameters:
// Note that any text at the end of a line that is not tab-terminated
// is not a cell and does not enforce alignment of cells in adjacent
// rows. To make it a cell it needs to be tab-terminated. (For more
// information see http://nickgravgaard.com/elastictabstops/index.html)
//
// tabwidth the minimal with of a cell
// padding additional padding
// usetabs use tabs instead of blanks for padding
// (for correct-looking results, tabwidth must correspond
// to the tabwidth in the editor used to look at the result)
// Formatting can be controlled via parameters:
//
// (See alse http://nickgravgaard.com/elastictabstops/index.html)
// cellwidth minimal cell width
// padding additional cell padding
// padchar ASCII char used for padding
// if padchar == '\t', the Writer will assume that the
// width of a '\t' in the formatted output is tabwith,
// and cells are left-aligned independent of align_left
// (for correct-looking results, cellwidth must correspond
// to the tabwidth in the editor used to look at the result)
// TODO Should support UTF-8
// TODO Should probably implement a couple of trivial customization options
// such as arbitrary padding character, left/right alignment, and inde-
// pendant cell and tab width.
// TODO Should support UTF-8 (requires more complicated width bookkeeping)
export type Writer struct {
// TODO should not export any of the fields
// configuration
writer io.Write;
tabwidth int;
cellwidth int;
padding int;
usetabs bool;
padbytes [8]byte;
align_left bool;
// current state
buf ByteArray; // the collected text w/o tabs and newlines
......@@ -105,11 +109,20 @@ func (b *Writer) AddLine() {
}
func (b *Writer) Init(writer io.Write, tabwidth, padding int, usetabs bool) *Writer {
func (b *Writer) Init(writer io.Write, cellwidth, padding int, padchar byte, align_left bool) *Writer {
if cellwidth < 0 {
panic("negative cellwidth");
}
if padding < 0 {
panic("negative padding");
}
b.writer = writer;
b.tabwidth = tabwidth;
b.cellwidth = cellwidth;
b.padding = padding;
b.usetabs = usetabs;
for i := len(b.padbytes) - 1; i >= 0; i-- {
b.padbytes[i] = padchar;
}
b.align_left = align_left || padchar == '\t'; // tab enforces left-alignment
b.buf.Init(1024);
b.lines.Init(0);
......@@ -156,15 +169,12 @@ func (b *Writer) Write0(buf *[]byte) *os.Error {
}
var Tabs = &[]byte{'\t', '\t', '\t', '\t', '\t', '\t', '\t', '\t'}
var Blanks = &[]byte{' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '}
var Newline = &[]byte{'\n'}
func (b *Writer) WritePadding(textw, cellw int) (err *os.Error) {
if b.usetabs {
// make cell width a multiple of tabwidth
cellw = ((cellw + b.tabwidth - 1) / b.tabwidth) * b.tabwidth;
if b.padbytes[0] == '\t' {
// make cell width a multiple of cellwidth
cellw = ((cellw + b.cellwidth - 1) / b.cellwidth) * b.cellwidth;
}
n := cellw - textw;
......@@ -172,20 +182,18 @@ func (b *Writer) WritePadding(textw, cellw int) (err *os.Error) {
panic("internal error");
}
padding := Blanks;
if b.usetabs {
n = (n + b.tabwidth - 1) / b.tabwidth;
padding = Tabs;
if b.padbytes[0] == '\t' {
n = (n + b.cellwidth - 1) / b.cellwidth;
}
for n > len(padding) {
err = b.Write0(padding);
for n > len(b.padbytes) {
err = b.Write0(&b.padbytes);
if err != nil {
goto exit;
}
n -= len(padding);
n -= len(b.padbytes);
}
err = b.Write0(padding[0 : n]);
err = b.Write0((&b.padbytes)[0 : n]); // BUG 6g should not require ()'s
exit:
return err;
......@@ -198,16 +206,33 @@ func (b *Writer) WriteLines(pos0 int, line0, line1 int) (pos int, err *os.Error)
line := b.Line(i);
for j := 0; j < line.Len(); j++ {
w := line.At(j);
err = b.Write0(b.buf.a[pos : pos + w]);
if err != nil {
goto exit;
}
pos += w;
if j < b.widths.Len() {
err = b.WritePadding(w, b.widths.At(j));
if b.align_left {
err = b.Write0(b.buf.a[pos : pos + w]);
if err != nil {
goto exit;
}
pos += w;
if j < b.widths.Len() {
err = b.WritePadding(w, b.widths.At(j));
if err != nil {
goto exit;
}
}
} else { // align right
if j < b.widths.Len() {
err = b.WritePadding(w, b.widths.At(j));
if err != nil {
goto exit;
}
}
err = b.Write0(b.buf.a[pos : pos + w]);
if err != nil {
goto exit;
}
pos += w;
}
}
err = b.Write0(Newline);
......@@ -252,7 +277,7 @@ func (b *Writer) Format(pos0 int, line0, line1 int) (pos int, err *os.Error) {
last = this;
// column block begin
width := b.tabwidth; // minimal width
width := b.cellwidth; // minimal width
for ; this < line1; this++ {
line = b.Line(this);
if column < line.Len() - 1 {
......@@ -338,6 +363,6 @@ func (b *Writer) Append(buf *[]byte) {
}
export func New(writer io.Write, tabwidth, padding int, usetabs bool) *Writer {
return new(Writer).Init(writer, tabwidth, padding, usetabs)
export func New(writer io.Write, cellwidth, padding int, padchar byte, align_left bool) *Writer {
return new(Writer).Init(writer, cellwidth, padding, padchar, align_left)
}
......@@ -42,40 +42,171 @@ func (b *Buffer) String() string {
}
func Check(t *testing.T, tabwidth, padding int, usetabs bool, src, expected string) {
func Check(t *testing.T, tabwidth, padding int, padchar byte, align_left bool, src, expected string) {
var b Buffer;
b.Init(1000);
var w tabwriter.Writer;
w.Init(&b, tabwidth, padding, usetabs);
w.Init(&b, tabwidth, padding, padchar, align_left);
io.WriteString(&w, src);
res := b.String();
if res != expected {
t.Errorf("src:\n%s\nfound:\n%s\nexpected:\n%s\n", src, res, expected)
t.Errorf("--- src:\n%s\n--- found:\n%s\n--- expected:\n%s\n", src, res, expected)
}
}
export func Test1(t *testing.T) {
Check(
t, 8, 1, false,
t, 8, 1, ' ', true,
"\n",
"\n"
);
Check(
t, 8, 1, false,
t, 8, 1, '*', true,
"Hello, world!\n",
"Hello, world!\n"
);
Check(
t, 8, 1, false,
"a\tb\tc\naa\tbbb\tcccc\naaa\tbbbb\n\n",
t, 0, 0, '.', true,
"1\t2\t3\t4\n"
"11\t222\t3333\t44444\n\n",
"1.2..3...4\n"
"11222333344444\n\n"
);
Check(
t, 5, 0, '.', true,
"1\t2\t3\t4\n\n",
"1....2....3....4\n\n"
);
Check(
t, 5, 0, '.', true,
"1\t2\t3\t4\t\n\n",
"1....2....3....4....\n\n"
);
Check(
t, 8, 1, ' ', true,
"a\tb\tc\n"
"aa\tbbb\tcccc\tddddd\n"
"aaa\tbbbb\n\n",
"a b c\n"
"aa bbb cccc\n"
"aa bbb cccc ddddd\n"
"aaa bbbb\n\n"
);
Check(
t, 8, 1, ' ', false,
"a\tb\tc\t\n"
"aa\tbbb\tcccc\tddddd\t\n"
"aaa\tbbbb\t\n\n",
" a b c\n"
" aa bbb cccc ddddd\n"
" aaa bbbb\n\n"
);
Check(
t, 2, 0, ' ', true,
"a\tb\tc\n"
"aa\tbbb\tcccc\n"
"aaa\tbbbb\n\n",
"a b c\n"
"aa bbbcccc\n"
"aaabbbb\n\n"
);
Check(
t, 8, 1, '_', true,
"a\tb\tc\n"
"aa\tbbb\tcccc\n"
"aaa\tbbbb\n\n",
"a_______b_______c\n"
"aa______bbb_____cccc\n"
"aaa_____bbbb\n\n"
);
Check(
t, 4, 1, '-', true,
"4444\t333\t22\t1\t333\n"
"999999999\t22\n"
"7\t22\n"
"\t\t\t88888888\n"
"\n"
"666666\t666666\t666666\t4444\n"
"1\t1\t999999999\t0000000000\n\n",
"4444------333-22--1---333\n"
"999999999-22\n"
"7---------22\n"
"------------------88888888\n"
"\n"
"666666-666666-666666----4444\n"
"1------1------999999999-0000000000\n\n"
);
Check(
t, 4, 3, '.', true,
"4444\t333\t22\t1\t333\n"
"999999999\t22\n"
"7\t22\n"
"\t\t\t88888888\n"
"\n"
"666666\t666666\t666666\t4444\n"
"1\t1\t999999999\t0000000000\n\n",
"4444........333...22...1...333\n"
"999999999...22\n"
"7...........22\n"
"....................88888888\n"
"\n"
"666666...666666...666666......4444\n"
"1........1........999999999...0000000000\n\n"
);
Check(
t, 8, 1, '\t', true,
"4444\t333\t22\t1\t333\n"
"999999999\t22\n"
"7\t22\n"
"\t\t\t88888888\n"
"\n"
"666666\t666666\t666666\t4444\n"
"1\t1\t999999999\t0000000000\n\n",
"4444\t\t333\t22\t1\t333\n"
"999999999\t22\n"
"7\t\t22\n"
"\t\t\t\t88888888\n"
"\n"
"666666\t666666\t666666\t\t4444\n"
"1\t1\t999999999\t0000000000\n\n"
);
Check(
t, 4, 2, ' ', false,
".0\t.3\t2.4\t-5.1\t\n"
"23.0\t12345678.9\t2.4\t-989.4\t\n"
"5.1\t12.0\t2.4\t-7.0\t\n"
".0\t0.0\t332.0\t8908.0\t\n"
".0\t-.3\t456.4\t22.1\t\n"
".0\t1.2\t44.4\t-13.3\t\n\n",
" .0 .3 2.4 -5.1\n"
" 23.0 12345678.9 2.4 -989.4\n"
" 5.1 12.0 2.4 -7.0\n"
" .0 0.0 332.0 8908.0\n"
" .0 -.3 456.4 22.1\n"
" .0 1.2 44.4 -13.3\n\n"
);
}
......@@ -604,7 +604,11 @@ func (P *Printer) Declaration(d *AST.Decl, parenthesized bool) {
func (P *Printer) Program(p *AST.Program) {
// TODO should initialize all fields?
P.writer = TabWriter.New(OS.Stdout, int(tabwidth.IVal()), 1, usetabs.BVal());
padchar := byte(' ');
if usetabs.BVal() {
padchar = '\t';
}
P.writer = TabWriter.New(OS.Stdout, int(tabwidth.IVal()), 1, padchar, true);
P.clist = p.comments;
P.cindex = 0;
......
......@@ -36,7 +36,11 @@ func Untab(name string, src *os.FD, dst *tabwriter.Writer) {
func main() {
flag.Parse();
dst := tabwriter.New(os.Stdout, int(tabwidth.IVal()), 1, usetabs.BVal());
padchar := byte(' ');
if usetabs.BVal() {
padchar = '\t';
}
dst := tabwriter.New(os.Stdout, int(tabwidth.IVal()), 1, padchar, true);
if flag.NArg() > 0 {
for i := 0; i < flag.NArg(); i++ {
name := flag.Arg(i);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment