Commit 3c2f0ae1 authored by Robert Griesemer's avatar Robert Griesemer

* pretty printing snapshot: towards printing comments nicely

- implemented elastic tabstops algorithm, now correct and documented
- first cut at printing comments (use -comments flag, disabled for now)
- struct field types are now aligned (using elastic tab stops)
- needs more fine-tuning

* fixed a bug in test script
* added quick smoke test to makefile and invoke it in run.bash
  instead of the full test

R=r
OCL=19220
CL=19220
parent e2621b80
...@@ -32,7 +32,7 @@ time make ...@@ -32,7 +32,7 @@ time make
(xcd ../usr/gri/pretty (xcd ../usr/gri/pretty
make clean make clean
time make time make
make test make smoketest
) || exit $? ) || exit $?
(xcd ../test (xcd ../test
......
...@@ -11,6 +11,9 @@ pretty: pretty.6 ...@@ -11,6 +11,9 @@ pretty: pretty.6
test: pretty test: pretty
./test.sh ./test.sh
smoketest: pretty
./test.sh parser.go
install: pretty install: pretty
cp pretty $(HOME)/bin/pretty cp pretty $(HOME)/bin/pretty
......
...@@ -43,6 +43,11 @@ func (p *List) at(i int) Any { ...@@ -43,6 +43,11 @@ func (p *List) at(i int) Any {
} }
func (p *List) last() Any {
return p.a[len(p.a) - 1];
}
func (p *List) set(i int, x Any) { func (p *List) set(i int, x Any) {
p.a[i] = x; p.a[i] = x;
} }
......
...@@ -77,13 +77,8 @@ func (P *Parser) Next0() { ...@@ -77,13 +77,8 @@ func (P *Parser) Next0() {
func (P *Parser) Next() { func (P *Parser) Next() {
P.Next0();
if P.tok == Scanner.COMMENT {
pos, s := P.pos, P.val;
for P.Next0(); P.tok == Scanner.COMMENT; P.Next0() { for P.Next0(); P.tok == Scanner.COMMENT; P.Next0() {
s += P.val; P.comments.Add(AST.NewComment(P.pos, P.val));
}
P.comments.Add(AST.NewComment(pos, s));
} }
} }
......
...@@ -11,6 +11,7 @@ import Flag "flag" ...@@ -11,6 +11,7 @@ import Flag "flag"
import Fmt "fmt" import Fmt "fmt"
var tabwith = Flag.Int("tabwidth", 4, nil, "tab width"); var tabwith = Flag.Int("tabwidth", 4, nil, "tab width");
var comments = Flag.Bool("comments", false, nil, "enable printing of comments");
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
...@@ -33,58 +34,66 @@ func PrintBlanks(n int) { ...@@ -33,58 +34,66 @@ func PrintBlanks(n int) {
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Implemententation of flexible tab stops. // Implemententation of flexible tab stops.
// (http://nickgravgaard.com/elastictabstops/index.html)
// Buffer is a representation for a list of lines consisting of
// cells. A new cell is added for each Tab() call, and a new line
// is added for each Newline() call.
//
// The lines are formatted and printed such that all cells in a column
// of adjacent cells have the same width (by adding padding). For more
// details see: http://nickgravgaard.com/elastictabstops/index.html .
type Buffer struct { type Buffer struct {
segment string; // current line segment cell string; // current cell (last cell in last line, not in lines yet)
lines AST.List; // a list of lines; and each line is a list of strings lines AST.List; // list of lines; each line is a list of cells (strings)
widths AST.List; // list of column widths - (re-)used during formatting
} }
func (b *Buffer) Line(i int) *AST.List { // Implementation
return b.lines.at(i).(*AST.List); // (Do not use these functions outside the Buffer implementation).
}
func (b *Buffer) Tab() { func (b *Buffer) AddLine() {
b.lines.at(b.lines.len() - 1).(*AST.List).Add(b.segment); b.lines.Add(AST.NewList());
b.segment = "";
} }
func (b *Buffer) Newline() { func (b *Buffer) Line(i int) *AST.List {
b.Tab(); // add last segment to current line return b.lines.at(i).(*AST.List);
b.lines.Add(AST.NewList());
} }
func (b *Buffer) Print(s string) { func (b *Buffer) LastLine() *AST.List {
b.segment += s; return b.lines.last().(*AST.List);
} }
func (b *Buffer) Init() { // debugging support
b.lines.Init(); func (b *Buffer) Dump() {
b.lines.Add(AST.NewList()); for i := 0; i < b.lines.len(); i++ {
line := b.Line(i);
print("(", i, ") ");
for j := 0; j < line.len(); j++ {
print("[", line.at(j).(string), "]");
}
print("\n");
}
print("\n");
} }
func (b *Buffer) PrintLines(line0, line1 int, widths *AST.List) { func (b *Buffer) PrintLines(line0, line1 int) {
for i := line0; i < line1; i++ { for i := line0; i < line1; i++ {
nsep := 0;
line := b.Line(i); line := b.Line(i);
for j := 0; j < line.len(); j++ { for j := 0; j < line.len(); j++ {
s := line.at(j).(string); s := line.at(j).(string);
PrintBlanks(nsep);
print(s); print(s);
if j < widths.len() { if j < b.widths.len() {
nsep = widths.at(j).(int) - len(s); nsep := b.widths.at(j).(int) - len(s);
assert(nsep >= 0); assert(nsep >= 0);
if nsep < int(tabwith.IVal()) { PrintBlanks(nsep);
nsep = int(tabwith.IVal());
}
} else { } else {
nsep = 0; assert(j == b.widths.len());
} }
} }
println(); println();
...@@ -92,55 +101,95 @@ func (b *Buffer) PrintLines(line0, line1 int, widths *AST.List) { ...@@ -92,55 +101,95 @@ func (b *Buffer) PrintLines(line0, line1 int, widths *AST.List) {
} }
func (b *Buffer) Format(line0, line1 int, widths *AST.List) { func (b *Buffer) Format(line0, line1 int) {
i0, i1 := line0, line0; column := b.widths.len();
column := widths.len();
width := -1; last := line0;
for i := line0; i < line1; i++ { for this := line0; this < line1; this++ {
line := b.Line(i); line := b.Line(this);
if column < line.len() - 1 { if column < line.len() - 1 {
if width < 0 { // cell exists in this column
// column start // (note that the last cell per line is ignored)
i1 = i;
b.PrintLines(i0, i1, widths); // print unprinted lines until beginning of block
} b.PrintLines(last, this);
w := len(line.at(column).(string)); last = this;
// column block begin
width := int(tabwith.IVal()); // minimal width
for ; this < line1; this++ {
line := b.Line(this);
if column < line.len() - 1 {
// cell exists in this column
// update width
w := len(line.at(column).(string)) + 1; // 1 = minimum space between cells
if w > width { if w > width {
width = w; width = w;
} }
} else { } else {
if width >= 0 { break
// column end
i0 = i;
widths.Add(width);
b.Format(i1, i0, widths);
widths.Pop();
width = -1;
} }
} }
// column block end
// format and print all columns to the right of this column
// (we know the widths of this column and all columns to the left)
b.widths.Add(width);
b.Format(last, this);
b.widths.Pop();
last = this;
}
} }
b.PrintLines(i0, line1, widths);
// print unprinted lines until end
b.PrintLines(last, line1);
} }
func (b *Buffer) Dump() { // Buffer interface
for i := 0; i < b.lines.len(); i++ { // (Use these functions to interact with Buffers).
line := b.Line(i);
print("(", i, ") "); func (b *Buffer) Init() {
for j := 0; j < line.len(); j++ { b.lines.Init();
print("[", line.at(j).(string), "]"); b.widths.Init();
} b.AddLine(); // the very first line
print("\n"); }
}
print("\n");
func (b *Buffer) EmptyLine() bool {
return b.LastLine().len() == 0 && len(b.cell) == 0;
}
func (b *Buffer) Tab() {
b.LastLine().Add(b.cell);
b.cell = "";
} }
func (b *Buffer) Flush() { func (b *Buffer) Newline() {
b.Tab(); // add last segment to current line b.Tab(); // add last cell to current line
b.Format(0, b.lines.len(), AST.NewList());
if b.LastLine().len() == 1 {
// The current line has only one cell which does not have an impact
// on the formatting of the following lines (the last cell per line
// is ignored by Format), thus we can print the buffer contents.
assert(b.widths.len() == 0);
b.Format(0, b.lines.len());
assert(b.widths.len() == 0);
// reset the buffer
b.lines.Clear(); b.lines.Clear();
b.lines.Add(AST.NewList()); }
b.AddLine();
assert(len(b.cell) == 0);
}
func (b *Buffer) Print(s string) {
b.cell += s;
} }
...@@ -151,6 +200,7 @@ export type Printer struct { ...@@ -151,6 +200,7 @@ export type Printer struct {
buf Buffer; buf Buffer;
// formatting control // formatting control
lastpos int; // pos after last string
level int; // true scope level level int; // true scope level
indent int; // indentation level indent int; // indentation level
semi bool; // pending ";" semi bool; // pending ";"
...@@ -163,43 +213,75 @@ export type Printer struct { ...@@ -163,43 +213,75 @@ export type Printer struct {
} }
func CountNewlinesAndTabs(s string) (int, int, string) { func (P *Printer) String(pos int, s string) {
nls, tabs := 0, 0; if pos == 0 {
for i := 0; i < len(s); i++ { pos = P.lastpos; // estimate
switch ch := s[i]; ch {
case '\n': nls++;
case '\t': tabs++;
case ' ':
default:
// non-whitespace char
assert(ch == '/');
return nls, tabs, s[i : len(s)];
}
} }
return nls, tabs, "";
}
func (P *Printer) String(pos int, s string) {
if P.semi && P.level > 0 { // no semicolons at level 0 if P.semi && P.level > 0 { // no semicolons at level 0
P.buf.Print(";"); P.buf.Print(";");
} }
/* //print("--", pos, "[", s, "]\n");
for pos > P.cpos {
// we have a comment at_line_begin := false;
for comments.BVal() && P.cpos < pos {
//print("cc", P.cpos, "\n");
// we have a comment that comes before s
comment := P.clist.at(P.cindex).(*AST.Comment); comment := P.clist.at(P.cindex).(*AST.Comment);
nls, tabs, text := CountNewlinesAndTabs(comment.text); text := comment.text;
assert(len(text) >= 3); // classification char + "//" or "/*"
if nls == 0 && len(text) > 1 && text[1] == '/' { // classify comment
switch text[0] {
case ' ':
// not only white space before comment on the same line
// - put into next cell if //-style comment
// - preceed with a space if /*-style comment
//print("[case a][", text[1 : len(text)], "]");
if text[2] == '/' {
P.buf.Tab(); P.buf.Tab();
P.buf.Print(text);
if P.newl <= 0 {
//P.newl = 1; // line comments must have a newline
}
} else { } else {
P.buf.Print(text); P.buf.Print(" ");
} }
/*
case '\n':
// comment starts at beginning of line
// - reproduce exactly
//print("[case b][", text[1 : len(text)], "]");
if !P.buf.AtLineBegin() {
P.buf.Newline();
}
*/
case '\n', '\t':
// only white space before comment on the same line
// - indent
//print("[case c][", text[1 : len(text)], "]");
if !P.buf.EmptyLine() {
P.buf.Newline();
}
for i := P.indent; i > 0; i-- {
P.buf.Tab();
}
default:
panic("UNREACHABLE");
}
P.buf.Print(text[1 : len(text)]);
if text[2] == '/' {
// line comments must end in newline
// TODO should we set P.newl instead?
P.buf.Newline();
for i := P.indent; i > 0; i-- {
P.buf.Tab();
}
at_line_begin = true;
}
P.cindex++; P.cindex++;
if P.cindex < P.clist.len() { if P.cindex < P.clist.len() {
P.cpos = P.clist.at(P.cindex).(*AST.Comment).pos; P.cpos = P.clist.at(P.cindex).(*AST.Comment).pos;
...@@ -207,7 +289,10 @@ func (P *Printer) String(pos int, s string) { ...@@ -207,7 +289,10 @@ func (P *Printer) String(pos int, s string) {
P.cpos = 1000000000; // infinite P.cpos = 1000000000; // infinite
} }
} }
*/
if at_line_begin && P.newl > 0 {
P.newl--;
}
if P.newl > 0 { if P.newl > 0 {
P.buf.Newline(); P.buf.Newline();
...@@ -224,6 +309,7 @@ func (P *Printer) String(pos int, s string) { ...@@ -224,6 +309,7 @@ func (P *Printer) String(pos int, s string) {
P.buf.Print(s); P.buf.Print(s);
P.lastpos = pos + len(s);
P.semi, P.newl = false, 0; P.semi, P.newl = false, 0;
} }
...@@ -233,6 +319,12 @@ func (P *Printer) Blank() { ...@@ -233,6 +319,12 @@ func (P *Printer) Blank() {
} }
func (P *Printer) Tab() {
P.String(0, "");
P.buf.Tab();
}
func (P *Printer) Token(pos int, tok int) { func (P *Printer) Token(pos int, tok int) {
P.String(pos, Scanner.TokenString(tok)); P.String(pos, Scanner.TokenString(tok));
} }
...@@ -255,6 +347,7 @@ func (P *Printer) CloseScope(paren string) { ...@@ -255,6 +347,7 @@ func (P *Printer) CloseScope(paren string) {
P.semi, P.newl = false, 1; P.semi, P.newl = false, 1;
} }
func (P *Printer) Error(pos int, tok int, msg string) { func (P *Printer) Error(pos int, tok int, msg string) {
P.String(0, "<"); P.String(0, "<");
P.Token(pos, tok); P.Token(pos, tok);
...@@ -298,7 +391,7 @@ func (P *Printer) Fields(list *AST.List) { ...@@ -298,7 +391,7 @@ func (P *Printer) Fields(list *AST.List) {
} else if prev == x.tok { } else if prev == x.tok {
P.String(0, ", "); P.String(0, ", ");
} else { } else {
P.Blank(); P.Tab();
} }
} }
P.Expr(x); P.Expr(x);
...@@ -605,7 +698,7 @@ func (P *Printer) Stat(s *AST.Stat) { ...@@ -605,7 +698,7 @@ func (P *Printer) Stat(s *AST.Stat) {
func (P *Printer) Declaration(d *AST.Decl, parenthesized bool) { func (P *Printer) Declaration(d *AST.Decl, parenthesized bool) {
if !parenthesized { if !parenthesized {
if d.exported { if d.exported {
P.String(0, "export "); P.String(d.pos, "export ");
} }
P.Token(d.pos, d.tok); P.Token(d.pos, d.tok);
P.Blank(); P.Blank();
...@@ -633,10 +726,9 @@ func (P *Printer) Declaration(d *AST.Decl, parenthesized bool) { ...@@ -633,10 +726,9 @@ func (P *Printer) Declaration(d *AST.Decl, parenthesized bool) {
} }
if d.val != nil { if d.val != nil {
if d.tok == Scanner.IMPORT { P.Tab();
P.Blank(); if d.tok != Scanner.IMPORT {
} else { P.String(0, "= ");
P.String(0, " = ");
} }
P.Expr(d.val); P.Expr(d.val);
} }
...@@ -690,7 +782,8 @@ func (P *Printer) Program(p *AST.Program) { ...@@ -690,7 +782,8 @@ func (P *Printer) Program(p *AST.Program) {
for i := 0; i < p.decls.len(); i++ { for i := 0; i < p.decls.len(); i++ {
P.Declaration(p.decls.at(i), false); P.Declaration(p.decls.at(i), false);
} }
P.newl = 1; P.newl = 2; // TODO we should be able to do this with 1 instead of 2
// but we are loosing the last buffer flush in that case
P.buf.Flush(); // TODO should not access P.buf directly here P.String(0, ""); // flush buffer
} }
...@@ -518,22 +518,29 @@ func (S *Scanner) Expect(ch int) { ...@@ -518,22 +518,29 @@ func (S *Scanner) Expect(ch int) {
} }
func (S *Scanner) SkipWhitespace() { func (S *Scanner) SkipWhitespace() int {
for S.ch == ' ' || S.ch == '\r' { pos := -1; // no new line position yet
S.Next();
if S.chpos == 0 {
// file beginning is always start of a new line
pos = 0;
} }
}
for {
switch S.ch {
case '\t', '\r', ' ': // nothing to do
case '\n': pos = S.pos; // remember start of new line
default: goto exit;
}
S.Next();
}
func (S *Scanner) ScanWhitespace() string { exit:
// first char ('\n' or '\t', 1 byte) already consumed return pos;
pos := S.chpos - 1;
S.SkipWhitespace();
return S.src[pos : S.chpos];
} }
func (S *Scanner) ScanComment() string { func (S *Scanner) ScanComment(nlpos int) string {
// first '/' already consumed // first '/' already consumed
pos := S.chpos - 1; pos := S.chpos - 1;
...@@ -543,6 +550,9 @@ func (S *Scanner) ScanComment() string { ...@@ -543,6 +550,9 @@ func (S *Scanner) ScanComment() string {
for S.ch >= 0 { for S.ch >= 0 {
S.Next(); S.Next();
if S.ch == '\n' { if S.ch == '\n' {
// '\n' terminates comment but we do not include
// it in the comment (otherwise we cannot see the
// start of a newline in SkipWhitespace()).
goto exit; goto exit;
} }
} }
...@@ -554,6 +564,7 @@ func (S *Scanner) ScanComment() string { ...@@ -554,6 +564,7 @@ func (S *Scanner) ScanComment() string {
ch := S.ch; ch := S.ch;
S.Next(); S.Next();
if ch == '*' && S.ch == '/' { if ch == '*' && S.ch == '/' {
S.Next();
goto exit; goto exit;
} }
} }
...@@ -562,7 +573,6 @@ func (S *Scanner) ScanComment() string { ...@@ -562,7 +573,6 @@ func (S *Scanner) ScanComment() string {
S.Error(pos, "comment not terminated"); S.Error(pos, "comment not terminated");
exit: exit:
S.Next();
comment := S.src[pos : S.chpos]; comment := S.src[pos : S.chpos];
if S.testmode { if S.testmode {
...@@ -586,6 +596,16 @@ exit: ...@@ -586,6 +596,16 @@ exit:
} }
} }
if nlpos < 0 {
// not only whitespace before comment on this line
comment = " " + comment;
} else if nlpos == pos {
// comment starts at the beginning of the line
comment = "\n" + comment;
} else {
// only whitespace before comment on this line
comment = "\t" + comment;
}
return comment; return comment;
} }
...@@ -815,20 +835,17 @@ func (S *Scanner) Select4(tok0, tok1, ch2, tok2, tok3 int) int { ...@@ -815,20 +835,17 @@ func (S *Scanner) Select4(tok0, tok1, ch2, tok2, tok3 int) int {
func (S *Scanner) Scan() (pos, tok int, val string) { func (S *Scanner) Scan() (pos, tok int, val string) {
S.SkipWhitespace(); nlpos := S.SkipWhitespace();
ch := S.ch; pos, tok = S.chpos, ILLEGAL;
pos = S.chpos;
tok = ILLEGAL;
switch { switch ch := S.ch; {
case is_letter(ch): tok, val = S.ScanIdentifier(); case is_letter(ch): tok, val = S.ScanIdentifier();
case digit_val(ch) < 10: tok, val = S.ScanNumber(false); case digit_val(ch) < 10: tok, val = S.ScanNumber(false);
default: default:
S.Next(); // always make progress S.Next(); // always make progress
switch ch { switch ch {
case -1: tok = EOF; case -1: tok = EOF;
case '\n', '\t': tok, val = COMMENT, S.ScanWhitespace();
case '"': tok, val = STRING, S.ScanString(); case '"': tok, val = STRING, S.ScanString();
case '\'': tok, val = INT, S.ScanChar(); case '\'': tok, val = INT, S.ScanChar();
case '`': tok, val = STRING, S.ScanRawString(); case '`': tok, val = STRING, S.ScanRawString();
...@@ -858,7 +875,7 @@ func (S *Scanner) Scan() (pos, tok int, val string) { ...@@ -858,7 +875,7 @@ func (S *Scanner) Scan() (pos, tok int, val string) {
case '*': tok = S.Select2(MUL, MUL_ASSIGN); case '*': tok = S.Select2(MUL, MUL_ASSIGN);
case '/': case '/':
if S.ch == '/' || S.ch == '*' { if S.ch == '/' || S.ch == '*' {
tok, val = COMMENT, S.ScanComment(); tok, val = COMMENT, S.ScanComment(nlpos);
} else { } else {
tok = S.Select2(QUO, QUO_ASSIGN); tok = S.Select2(QUO, QUO_ASSIGN);
} }
......
// Copyright 2009 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.
package main
import Fmt "fmt"
type T struct {
x, y int;
s string;
next_t *T
}
var (
A = 5;
a, b, c int = 0, 0, 0;
foo = "foo";
)
func main() {
// the prolog
for i := 0; i <= 10 /* limit */; i++ {
println(i); // the index
println(i + 1); // the index + 1
println(i + 1000); // the index + 1000
println();
}
// the epilog
println("foo"); // foo
println("foobar"); // foobar
var x int;
}
...@@ -96,11 +96,11 @@ valid() { ...@@ -96,11 +96,11 @@ valid() {
runtest() { runtest() {
#echo "Testing silent mode" #echo "Testing silent mode"
cleanup cleanup
$1 silent $1 silent $2
#echo "Testing idempotency" #echo "Testing idempotency"
cleanup cleanup
$1 idempotent $1 idempotent $2
} }
......
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