- steps towards "flexible tab stops" simulation in pretty
  printing output
- not yet enabled

R=r
OCL=18842
CL=18842
diff --git a/usr/gri/pretty/ast.go b/usr/gri/pretty/ast.go
index 91c61ec..d566361 100644
--- a/usr/gri/pretty/ast.go
+++ b/usr/gri/pretty/ast.go
@@ -27,6 +27,11 @@
 }
 
 
+func (p *List) Init() {
+	p.a = new([] Any, 10) [0 : 0];
+}
+
+
 func (p *List) len() int {
 	if p == nil { return 0; }
 	return len(p.a);
@@ -78,9 +83,14 @@
 }
 
 
+func (p *List) Clear() {
+	p.a = p.a[0 : 0];
+}
+
+
 export func NewList() *List {
 	p := new(List);
-	p.a = new([] Any, 10) [0 : 0];
+	p.Init();
 	return p;
 }
 
diff --git a/usr/gri/pretty/parser.go b/usr/gri/pretty/parser.go
index d9e7c92..d091529 100644
--- a/usr/gri/pretty/parser.go
+++ b/usr/gri/pretty/parser.go
@@ -77,8 +77,13 @@
 
 
 func (P *Parser) Next() {
-	for P.Next0(); P.tok == Scanner.COMMENT; P.Next0() {
-		P.comments.Add(AST.NewComment(P.pos, P.val));
+	P.Next0();
+	if P.tok == Scanner.COMMENT {
+		pos, s := P.pos, P.val;
+		for P.Next0(); P.tok == Scanner.COMMENT; P.Next0() {
+			s += P.val;
+		}
+		P.comments.Add(AST.NewComment(pos, s));
 	}
 }
 
diff --git a/usr/gri/pretty/printer.go b/usr/gri/pretty/printer.go
index cd20adb..4c886ad 100644
--- a/usr/gri/pretty/printer.go
+++ b/usr/gri/pretty/printer.go
@@ -7,11 +7,128 @@
 import Strings "strings"
 import Scanner "scanner"
 import AST "ast"
+import Flag "flag"
+import Fmt "fmt"
+
+var tabwith = Flag.Int("tabwidth", 4, nil, "tab width");
+
+
+// ----------------------------------------------------------------------------
+// Support
+
+func assert(p bool) {
+	if !p {
+		panic("assert failed");
+	}
+}
+
+
+func PrintBlanks(n int) {
+	// TODO make this faster
+	for ; n > 0; n-- {
+		print(" ");
+	}
+}
+
+
+// ----------------------------------------------------------------------------
+// Implemententation of flexible tab stops.
+// (http://nickgravgaard.com/elastictabstops/index.html)
+
+type Buffer struct {
+	lines AST.List;  // a list of lines; and each line is a list of strings
+	widths AST.List;
+}
+
+
+func (b *Buffer) Newline() {
+	b.lines.Add(AST.NewList());
+}
+
+
+func (b *Buffer) Init() {
+	b.lines.Init();
+	b.widths.Init();
+	b.Newline();
+}
+
+
+func (b *Buffer) ComputeWidths() {
+	// iterate through all columns j
+	for j := 0; ; j++ {
+		width := -1;  // initial column width
+		
+		// iterate through all lines i
+		for i := 0; i < b.lines.len(); i++ {
+			line := b.lines.at(i).(*AST.List);
+			if j < line.len() {
+				// the j.th column exists in this line
+				w := len(line.at(j).(string));
+				if w > width {
+					width = w;
+				}
+			}
+		}
+	
+		if width >= 0 {
+			assert(b.widths.len() == j);
+			b.widths.Add(width);
+		} else {
+			// no column j - we are done
+			return;
+		}
+	}
+}
+
+
+func (b *Buffer) Flush() {
+	b.ComputeWidths();
+	
+	// print the lines
+	for i := 0; i < b.lines.len(); i++ {
+		line := b.lines.at(i).(*AST.List);
+		for j := 0; j < line.len(); j++ {
+			s := line.at(j).(string);
+			d := b.widths.at(j).(int) - len(s);
+			assert(d >= 0);
+			if d < int(tabwith.IVal()) {
+				d = int(tabwith.IVal());
+			}
+			PrintBlanks(d);  // +1 padding
+			print(s);
+		}
+		println();
+	}
+	
+	b.lines.Clear();
+	b.widths.Clear();
+	b.Newline();
+}
+
+
+func (b *Buffer) Indent(n int) {
+	line := b.lines.at(b.lines.len() - 1).(*AST.List);
+	for ; n > 0; n-- {
+		line.Add("");
+	}
+}
+
+
+func (b *Buffer) Print(s string) {
+	i := b.lines.len() - 1;
+	line := b.lines.at(i).(*AST.List);
+	j := line.len() - 1;
+	if j < 0 {
+		line.Add(s);
+	} else {
+		line.set(j, line.at(j).(string) + s);
+	}
+}
 
 
 export type Printer struct {
-	pos int;  // actual output position
-
+	buf Buffer;
+	
 	// formatting control
 	level int;  // true scope level
 	indent int;  // indentation level
@@ -25,24 +142,22 @@
 }
 
 
-// Bottleneck interface - all output goes through here.
-func (P *Printer) print(s string) {
-	print(s);
-	// TODO do we need the code below?
-	// P.pos += Strings.utflen(s);
-}
-
+const NEW_CODE = false;
 
 func (P *Printer) String(pos int, s string) {
 	if P.semi && P.level > 0 {  // no semicolons at level 0
-		print(";");
+		if NEW_CODE {
+			P.buf.Print(";");
+		} else {
+			print(";");
+		}
 	}
 
 	/*
 	for pos > P.cpos {
 		// we have a comment
 		c := P.clist.at(P.cindex).(*AST.Comment);
-		if c.text[1] == '/' {
+		if len(c.text) > 1 && c.text[1] == '/' {
 			print("  " + c.text);
 			if P.newl <= 0 {
 				P.newl = 1;  // line comments must have a newline
@@ -60,15 +175,30 @@
 	*/
 
 	if P.newl > 0 {
-		for i := P.newl; i > 0; i-- {
-			print("\n");
+		if NEW_CODE {
+			P.buf.Flush();
 		}
-		for i := P.indent; i > 0; i-- {
-			print("\t");
+		for i := P.newl; i > 0; i-- {
+			if NEW_CODE {
+				P.buf.Newline();
+			} else {
+				print("\n");
+			}
+		}
+		if NEW_CODE {
+			P.buf.Indent(P.indent);
+		} else {
+			for i := P.indent; i > 0; i-- {
+				print("\t");
+			}
 		}
 	}
 
-	print(s);
+	if NEW_CODE {
+		P.buf.Print(s);
+	} else {
+		print(s);
+	}
 
 	P.semi, P.newl = false, 0;
 }
@@ -519,6 +649,8 @@
 
 func (P *Printer) Program(p *AST.Program) {
 	// TODO should initialize all fields?
+	P.buf.Init();
+	
 	P.clist = p.comments;
 	P.cindex = 0;
 	if p.comments.len() > 0 {
@@ -527,6 +659,7 @@
 		P.cpos = 1000000000;  // infinite
 	}
 
+	// Print package
 	P.String(p.pos, "package ");
 	P.Expr(p.ident);
 	P.newl = 2;
@@ -534,5 +667,6 @@
 		P.Declaration(p.decls.at(i), false);
 	}
 	P.newl = 1;
+
 	P.String(0, "");  // flush
 }
diff --git a/usr/gri/pretty/scanner.go b/usr/gri/pretty/scanner.go
index 4b331b5..06428e9 100644
--- a/usr/gri/pretty/scanner.go
+++ b/usr/gri/pretty/scanner.go
@@ -109,7 +109,7 @@
 
 
 export func TokenString(tok int) string {
-	switch (tok) {
+	switch tok {
 	case ILLEGAL: return "ILLEGAL";
 	
 	case IDENT: return "IDENT";
@@ -249,11 +249,6 @@
 }
 
 
-func is_whitespace(ch int) bool {
-	return ch == ' ' || ch == '\r' || ch == '\n' || ch == '\t';
-}
-
-
 func is_letter(ch int) bool {
 	return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' || ch >= 128 ;
 }
@@ -524,12 +519,20 @@
 
 
 func (S *Scanner) SkipWhitespace() {
-	for is_whitespace(S.ch) {
+	for S.ch == ' ' || S.ch == '\r' {
 		S.Next();
 	}
 }
 
 
+func (S *Scanner) ScanWhitespace() string {
+	// first char ('\n' or '\t', 1 byte) already consumed
+	pos := S.chpos - 1;
+	S.SkipWhitespace();
+	return S.src[pos : S.chpos];
+}
+
+
 func (S *Scanner) ScanComment() string {
 	// first '/' already consumed
 	pos := S.chpos - 1;
@@ -686,7 +689,7 @@
 	ch := S.ch;
 	pos := S.chpos;
 	S.Next();
-	switch (ch) {
+	switch ch {
 	case 'a', 'b', 'f', 'n', 'r', 't', 'v', '\\':
 		return string(ch);
 		
@@ -825,6 +828,7 @@
 		S.Next();  // always make progress
 		switch ch {
 		case -1: tok = EOF;
+		case '\n', '\t': tok, val = COMMENT, S.ScanWhitespace();
 		case '"': tok, val = STRING, S.ScanString();
 		case '\'': tok, val = INT, S.ScanChar();
 		case '`': tok, val = STRING, S.ScanRawString();