package Printer
-import "array"
-import Strings "strings"
-import Scanner "scanner"
-import AST "ast"
-import Flag "flag"
-import Fmt "fmt"
-import IO "io"
-import OS "os"
-import TabWriter "tabwriter"
+import (
+ "os";
+ "array";
+ "tabwriter";
+ "flag";
+ "fmt";
+ Scanner "scanner";
+ AST "ast";
+)
var (
- tabwidth = Flag.Int("tabwidth", 4, nil, "tab width");
- usetabs = Flag.Bool("usetabs", false, nil, "align with tabs instead of blanks");
- comments = Flag.Bool("comments", false, nil, "enable printing of comments");
+ tabwidth = flag.Int("tabwidth", 4, nil, "tab width");
+ usetabs = flag.Bool("usetabs", true, nil, "align with tabs instead of blanks");
+ comments = flag.Bool("comments", false, nil, "enable printing of comments");
)
// ----------------------------------------------------------------------------
// Printer
-export type Printer struct {
- writer IO.Write;
+type Printer struct {
+ // output
+ writer *tabwriter.Writer;
+ // comments
+ comments *array.Array;
+ cindex int;
+ cpos int;
+
// formatting control
lastpos int; // pos after last string
level int; // true scope level
indent int; // indentation level
semi bool; // pending ";"
newl int; // pending "\n"'s
+}
+
+
+func (P *Printer) NextComment() {
+ P.cindex++;
+ if P.comments != nil && P.cindex < P.comments.Len() {
+ P.cpos = P.comments.At(P.cindex).(*AST.Comment).pos;
+ } else {
+ P.cpos = 1<<30; // infinite
+ }
+}
+
+
+func (P *Printer) Init(writer *tabwriter.Writer, comments *array.Array) {
+ // writer
+ padchar := byte(' ');
+ if usetabs.BVal() {
+ padchar = '\t';
+ }
+ P.writer = tabwriter.New(os.Stdout, int(tabwidth.IVal()), 1, padchar, true);
// comments
- clist *array.Array;
- cindex int;
- cpos int;
+ P.comments = comments;
+ P.cindex = -1;
+ P.NextComment();
+
+ // formatting control initialized correctly by default
}
-func (P *Printer) Printf(fmt string, s ...) {
- Fmt.fprintf(P.writer, fmt, s);
+// ----------------------------------------------------------------------------
+// Printing support
+
+func (P *Printer) Printf(format string, s ...) {
+ n, err := fmt.fprintf(P.writer, format, s);
+ if err != nil {
+ panic("print error - exiting");
+ }
+ P.lastpos += n;
}
if pos == 0 {
pos = P.lastpos; // estimate
}
+ P.lastpos = pos;
if P.semi && P.level > 0 { // no semicolons at level 0
P.Printf(";");
//print("--", pos, "[", s, "]\n");
+ src_nl := 0;
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);
- text := comment.text;
- assert(len(text) >= 3); // classification char + "//" or "/*"
+ // we have a comment/newline that comes before s
+ comment := P.comments.At(P.cindex).(*AST.Comment);
+ ctext := comment.text;
- // classify comment
- switch comment.tok {
- case Scanner.COMMENT_BB:
- // black space before and after comment on the same line
- // - print surrounded by blanks
- P.Printf(" %s ", text);
-
- case Scanner.COMMENT_BW:
- // only white space after comment on the same line
- // - put into next cell
- P.Printf("\t%s", text);
-
- case Scanner.COMMENT_WW, Scanner.COMMENT_WB:
- // only white space before comment on the same line
- // - indent
- /*
- if !P.buf.EmptyLine() {
- P.buf.Newline();
- }
- */
- for i := P.indent; i > 0; i-- {
- P.Printf("\t");
+ if ctext == "\n" {
+ // found a newline in src
+ src_nl++;
+
+ } else {
+ // classify comment
+ assert(len(ctext) >= 3); // classification char + "//" or "/*"
+ //-style comment
+ if src_nl > 0 || P.cpos == 0 {
+ // only white space before comment on this line
+ // or file starts with comment
+ // - indent
+ P.Printf("\n");
+ for i := P.indent; i > 0; i-- {
+ P.Printf("\t");
+ }
+ P.Printf("%s", ctext);
+ } else {
+ // black space before comment on this line
+ if ctext[1] == '/' {
+ //-style comment
+ // - put in next cell
+ P.Printf("\t%s", ctext);
+ } else {
+ /*-style comment */
+ // - print surrounded by blanks
+ P.Printf(" %s ", ctext);
+ }
}
- P.Printf("%s", text);
- default:
- panic("UNREACHABLE");
- }
-
- if text[1] == '/' {
- // line comments must end in newline
- // TODO should we set P.newl instead?
- P.Printf("\n");
- for i := P.indent; i > 0; i-- {
- P.Printf("\t");
+ if ctext[1] == '/' {
+ //-style comments must end in newline
+ if P.newl == 0 {
+ P.newl = 1;
+ }
+ /*
+ // TODO should we set P.newl instead?
+ P.Printf("\n");
+ for i := P.indent; i > 0; i-- {
+ P.Printf("\t");
+ }
+ at_line_begin = true;
+ */
}
- at_line_begin = true;
+
+ src_nl = 0;
}
- P.cindex++;
- if P.cindex < P.clist.Len() {
- P.cpos = P.clist.At(P.cindex).(*AST.Comment).pos;
- } else {
- P.cpos = 1000000000; // infinite
- }
+ P.NextComment();
}
if at_line_begin && P.newl > 0 {
P.newl--;
}
+ if src_nl > P.newl {
+ P.newl = src_nl;
+ }
+
+ if P.newl > 2 {
+ P.newl = 2;
+ }
+
if P.newl > 0 {
P.Printf("\n");
if P.newl > 1 {
P.Printf("%s", s);
- P.lastpos = pos + len(s);
P.semi, P.newl = false, 0;
}
}
-func (P *Printer) Tab() {
- P.String(0, "\t");
-}
-
-
func (P *Printer) Token(pos int, tok int) {
P.String(pos, Scanner.TokenString(tok));
}
} else if prev == x.tok {
P.String(0, ", ");
} else {
- P.Tab();
+ P.String(0, "\t");
}
}
P.Expr(x);
}
if d.val != nil {
- P.Tab();
+ P.String(0, "\t");
if d.tok != Scanner.IMPORT {
P.String(0, "= ");
}
// Program
func (P *Printer) Program(p *AST.Program) {
- // TODO should initialize all fields?
- 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;
- if p.comments.Len() > 0 {
- P.cpos = p.comments.At(0).(*AST.Comment).pos;
- } else {
- P.cpos = 1000000000; // infinite
- }
-
- // Print package
P.String(p.pos, "package ");
P.Expr(p.ident);
P.newl = 2;
for i := 0; i < p.decls.Len(); i++ {
P.Declaration(p.decls.At(i), false);
}
- 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
+
+ // end program with '\n'
+ P.newl = 1;
+}
+
+
+// ----------------------------------------------------------------------------
+// External interface
+
+export func Print(prog *AST.Program) {
+ // setup
+ padchar := byte(' ');
+ if usetabs.BVal() {
+ padchar = '\t';
+ }
+ writer := tabwriter.New(os.Stdout, int(tabwidth.IVal()), 1, padchar, true);
+ var P Printer;
+ P.Init(writer, prog.comments);
- P.String(0, ""); // flush buffer
+ P.Program(prog);
+
+ // flush
+ P.String(0, "");
+ err := P.writer.Flush();
+ if err != nil {
+ panic("print error - exiting");
+ }
}
STRING;
EOF;
- COMMENT_BB;
- COMMENT_BW;
- COMMENT_WB;
- COMMENT_WW;
+ COMMENT;
ADD;
SUB;
case STRING: return "STRING";
case EOF: return "EOF";
- case COMMENT_BB: return "COMMENT_BB";
- case COMMENT_BW: return "COMMENT_BW";
- case COMMENT_WB: return "COMMENT_WB";
- case COMMENT_WW: return "COMMENT_WW";
+ case COMMENT: return "COMMENT";
case ADD: return "+";
case SUB: return "-";
export type Scanner struct {
+ // setup
err ErrorHandler;
+ src string; // source
+ scan_comments bool;
// scanning
- src string; // source
pos int; // current reading position
ch int; // one char look-ahead
chpos int; // position of ch
}
-func (S *Scanner) Init(err ErrorHandler, src string, testmode bool) {
+func (S *Scanner) Init(err ErrorHandler, src string, scan_comments, testmode bool) {
S.err = err;
-
S.src = src;
+ S.scan_comments = scan_comments;
+
S.pos = 0;
S.linepos = 0;
}
-// Returns true if a newline was seen, returns false otherwise.
-func (S *Scanner) SkipWhitespace() bool {
- sawnl := S.chpos == 0; // file beginning is always start of a new line
+func (S *Scanner) SkipWhitespace() {
for {
switch S.ch {
- case '\t', '\r', ' ': // nothing to do
- case '\n': sawnl = true;
- default: return sawnl;
+ case '\t', '\r', ' ':
+ // nothing to do
+ case '\n':
+ if S.scan_comments {
+ return;
+ }
+ default:
+ return;
}
S.Next();
}
panic("UNREACHABLE");
- return false;
}
-func (S *Scanner) ScanComment(leading_ws bool) (tok int, val string) {
+func (S *Scanner) ScanComment() string {
// first '/' already consumed
pos := S.chpos - 1;
if S.ch == '/' {
- // comment
+ //-style comment
S.Next();
for S.ch >= 0 {
S.Next();
if S.ch == '\n' {
// '\n' terminates comment but we do not include
- // it in the comment (otherwise we cannot see the
+ // it in the comment (otherwise we don't see the
// start of a newline in SkipWhitespace()).
goto exit;
}
}
} else {
- /* comment */
+ /*-style comment */
S.Expect('*');
for S.ch >= 0 {
ch := S.ch;
exit:
comment := S.src[pos : S.chpos];
- // skip whitespace but stop at line end
- for S.ch == '\t' || S.ch == '\r' || S.ch == ' ' {
- S.Next();
- }
- trailing_ws := S.ch == '\n';
-
if S.testmode {
// interpret ERROR and SYNC comments
oldpos := -1;
}
}
- if leading_ws {
- if trailing_ws {
- tok = COMMENT_WW;
- } else {
- tok = COMMENT_WB;
- }
- } else {
- if trailing_ws {
- tok = COMMENT_BW;
- } else {
- tok = COMMENT_BB;
- }
- }
-
- return tok, comment;
+ return comment;
}
func (S *Scanner) Scan() (pos, tok int, val string) {
- sawnl := S.SkipWhitespace();
+L: S.SkipWhitespace();
pos, tok = S.chpos, ILLEGAL;
S.Next(); // always make progress
switch ch {
case -1: tok = EOF;
+ case '\n': tok, val = COMMENT, "\n";
case '"': tok, val = STRING, S.ScanString();
case '\'': tok, val = INT, S.ScanChar();
case '`': tok, val = STRING, S.ScanRawString();
case '*': tok = S.Select2(MUL, MUL_ASSIGN);
case '/':
if S.ch == '/' || S.ch == '*' {
- tok, val = S.ScanComment(sawnl);
+ tok, val = COMMENT, S.ScanComment();
+ if !S.scan_comments {
+ goto L;
+ }
} else {
tok = S.Select2(QUO, QUO_ASSIGN);
}