summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobert Griesemer <gri@golang.org>2009-05-01 19:32:41 -0700
committerRobert Griesemer <gri@golang.org>2009-05-01 19:32:41 -0700
commitdc963a40da2b617dc4a76200769f319e0e4b59c6 (patch)
tree21f40d4173beaebaa7799d2a5a6d8681b0ff9eeb
parent33fefb72910f33a1cbd1e36dd2065c350659d18b (diff)
downloadgolang-dc963a40da2b617dc4a76200769f319e0e4b59c6.tar.gz
weekly snapshot:
- template-driven ast printing now can successfully reproduce entire Go programs next steps: - fine-tuning of output - print interspersed comments - cleanup and testing against all Go programs - replace astprinter R=r OCL=28181 CL=28181
-rw-r--r--usr/gri/pretty/ast.txt300
-rw-r--r--usr/gri/pretty/format.go475
-rw-r--r--usr/gri/pretty/format_test.go59
-rw-r--r--usr/gri/pretty/pretty.go4
4 files changed, 573 insertions, 265 deletions
diff --git a/usr/gri/pretty/ast.txt b/usr/gri/pretty/ast.txt
index fbf1fdc9e..d25a5a3bf 100644
--- a/usr/gri/pretty/ast.txt
+++ b/usr/gri/pretty/ast.txt
@@ -2,216 +2,314 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+// Format file for printing AST nodes (package "ast").
+
// ----------------------------------------------------------------------------
// Debugging
token.Token =
- ^ : "%s";
+ ^:string;
+array =
+ *;
+
+pointer =
+ *;
+
+string =
+ "%s";
+
+char =
+ "%c";
+
+bytes =
+ {*};
+
+nil =
+ ; // TODO we see a lot of nil's - why?
+
+not_empty =
+ *:nil;
-// Format file for printing AST nodes (package "ast").
-ast;
// ----------------------------------------------------------------------------
-// TODO should these be automatic?
+// TODO these are implicit - only here for debugging
-Expr =
- "expr ";
- //*;
+ast.Expr =
+ *;
-Stmt =
+ast.Stmt =
*;
-Decl =
+ast.Decl =
*;
+
// ----------------------------------------------------------------------------
// Comments
-Comment =
- Text : "%s\n";
+ast.Comment =
+ Text:string "\n";
-Comments =
+ast.Comments =
{*};
// ----------------------------------------------------------------------------
// Expressions & Types
-Field =
- {Names ", "} Type;
+ast.Field =
+ [Names:not_empty {Names / ", "} " "] Type;
-BadExpr =
+ast.BadExpr =
"BAD EXPR";
-Ident =
+ast.Ident =
Value;
-Elipsis =
+ast.Ellipsis =
"...";
-IntLit =
- Value : "%s";
+ast.IntLit =
+ Value:string;
-FloatLit =
- Value : "%s";
+ast.FloatLit =
+ Value:string;
-CharLit =
- Value : "%s";
+ast.CharLit =
+ Value:string;
-StringLit =
- Value : "%s";
+ast.StringLit =
+ Value:string;
-StringList =
- { Strings };
+ast.StringList =
+ {Strings / "\n"};
-FuncLit =
+ast.FuncLit =
"func ";
-CompositeLit =
- Type "{}";
+ast.CompositeLit =
+ Type "{" {Elts / ", "} "}";
-ParenExpr =
+ast.ParenExpr =
"(" X ")";
-SelectorExpr =
+ast.SelectorExpr =
X "." Sel;
-IndexExpr =
+ast.IndexExpr =
X "[" Index "]";
-SliceExpr =
- X "[" Begin " : " End "]";
+ast.SliceExpr =
+ X "[" Begin ":" End "]";
-TypeAssertExpr =
+ast.TypeAssertExpr =
X ".(" Type ")";
-CallExpr =
- Fun "(" {Args} ")";
+ast.CallExpr =
+ Fun "(" {Args / ", "} ")";
-StarExpr =
+ast.StarExpr =
"*" X;
-UnaryExpr =
+ast.UnaryExpr =
Op X;
-BinaryExpr =
- X Op Y;
+ast.BinaryExpr =
+ X " " Op " " Y;
-KeyValueExpr =
+ast.KeyValueExpr =
Key ": " Value;
-ArrayType =
+ast.ArrayType =
"[" Len "]" Elt;
-SliceType =
+ast.SliceType =
"[]" Elt;
-StructType =
- "struct {\n"
+ast.StructType =
+ "struct {"
+ [Fields:not_empty
+ >> "\t" "\n"
+ {Fields / ";\n"}
+ << "\n"
+ ]
"}";
-FuncType =
- "(" {Params " "} ")";
+signature =
+ "(" {Params / ", "} ")" [Results:not_empty " (" {Results / ", "} ")"];
+
+funcSignature =
+ *:signature;
+
+ast.FuncType =
+ "func" ^:signature;
-// BUG take this one away and the code crashes
-InterfaceType =
- "interface {}";
+ast.InterfaceType =
+ "interface {"
+ [Methods:not_empty
+ >> "\t" "\n"
+ {Methods / ";\n"} // TODO should not start with "func"
+ << "\n"
+ ]
+ "}";
-MapType =
+ast.MapType =
"map[" Key "]" Value;
-ChanType =
+ast.ChanType =
"chan";
// ----------------------------------------------------------------------------
// Statements
-BadStmt =
+ast.BadStmt =
"BAD STMT";
-DeclStmt =
+ast.DeclStmt =
Decl;
-EmptyStmt =
+ast.EmptyStmt =
;
-LabeledStmt =
+ast.LabeledStmt =
Label ":\t" Stmt;
-ExprStmt =
+ast.ExprStmt =
X;
-IncDecStmt =
+ast.IncDecStmt =
X Tok;
-AssignStmt =
- "assignment " {Lhs ", "};
- //{Lhs ", "} Tok {Rhs ", "};
+ast.AssignStmt =
+ {Lhs / ", "} " " Tok " " {Rhs / ", "};
-GoStmt =
+ast.GoStmt =
"go " Call;
-ReturnStmt =
- "return" {" " Results};
+ast.ReturnStmt =
+ "return" {" " Results / ","};
-BranchStmt =
+ast.BranchStmt =
Tok [" " Label];
-BlockStmt =
- "{\n" {List ";\n"} "}\n";
-
-IfStmt =
- "if " "{" [Body] "}" [Else];
-
-SwitchStmt =
- "switch {}";
-
-TypeSwitchStmt =
- "switch {}";
+blockStmt = // like ast.BlockStmt but w/o indentation
+ "{"
+ [List:not_empty
+ "\n"
+ {List / ";\n"}
+ "\n"
+ ]
+ "}";
-SelectStmt =
- "select {}";
+blockStmtPtr =
+ *:blockStmt;
-ForStmt =
- "for {}";
+ast.BlockStmt =
+ "{"
+ [List:not_empty
+ >> "\t" "\n"
+ {List / ";\n"}
+ << "\n"
+ ]
+ "}";
-RangeStmt =
- "range";
+ast.IfStmt =
+ "if " [Init "; "] [Cond " "] Body [" else " Else];
+
+ast.CaseClause =
+ ( Values:not_empty "case " {Values / ", "}
+ | "default"
+ )
+ ":"
+ [Body:not_empty
+ >> "\t" "\n"
+ {Body / ";\n"}
+ <<
+ ];
+
+ast.SwitchStmt =
+ "switch " [Init "; "] [Tag " "]
+ Body:blockStmtPtr;
+
+ast.TypeCaseClause =
+ ( Type:not_empty "case " Type
+ | "default"
+ )
+ ":"
+ [Body:not_empty
+ >> "\t" "\n"
+ {Body / ";\n"}
+ <<
+ ];
+
+ast.TypeSwitchStmt =
+ "switch " Assign " "
+ Body:blockStmtPtr;
+
+ast.CommClause =
+ "CommClause";
+
+ast.SelectStmt =
+ "select "
+ Body:blockStmtPtr;
+
+ast.ForStmt =
+ "for "
+ [ Init:not_empty
+ [Init] "; " [Cond] "; " [Post " "]
+ | Post:not_empty
+ [Init] "; " [Cond] "; " [Post " "]
+ | Cond " "
+ ]
+ Body;
+
+ast.RangeStmt =
+ "for " Key [", " Value] " " Tok " range " X
+ " "
+ Body;
// ----------------------------------------------------------------------------
// Declarations
-Spec =
+ast.Spec =
*;
-ImportSpec =
- "import";
+ast.ImportSpec =
+ Doc
+ [Name] "\t" {Path};
-ValueSpec =
- "value";
+ast.ValueSpec =
+ {Names / ", "} [" " Type] [Values:not_empty " = " {Values / ", "}];
-TypeSpec =
- "type";
+ast.TypeSpec =
+ Name " " // TODO using "\t" instead of " " screws up struct field alignment
+ Type;
-BadDecl =
+ast.BadDecl =
"BAD DECL";
-GenDecl =
+ast.GenDecl =
Doc
- Tok " (\n"
- ")\n";
-
-FuncDecl =
- "func " ["(" Recv ") "] Name Type [" " Body];
-
+ Tok " ("
+ >> "\t" "\n"
+ {Specs / ";\n"}
+ <<
+ "\n"
+ ")";
+
+ast.FuncDecl =
+ "func " ["(" Recv ") "] Name Type:funcSignature
+ [" " Body]
+ "\n";
+
// ----------------------------------------------------------------------------
// Program
-Program =
+ast.Program =
Doc
"package " Name "\n\n"
- {Decls "\n\n"};
+ {Decls / "\n\n"};
diff --git a/usr/gri/pretty/format.go b/usr/gri/pretty/format.go
index 62009d2c8..eff983633 100644
--- a/usr/gri/pretty/format.go
+++ b/usr/gri/pretty/format.go
@@ -32,13 +32,22 @@ import (
)
-// TODO remove once the code works
-var debug = flag.Bool("d", false, "debug mode");
+// TODO should probably do this in a different way
+var (
+ debug = flag.Bool("d", false, "debug mode");
+ trace = flag.Bool("t", false, "trace mode");
+)
// ----------------------------------------------------------------------------
// Format representation
+type (
+ Formatter func(w io.Write, value interface{}, name string) bool;
+ FormatterMap map[string]Formatter;
+)
+
+
// A production expression is built from the following nodes.
//
type (
@@ -54,28 +63,36 @@ type (
x, y expr;
};
- field struct {
- name string; // including "^", "*"
- fexpr expr; // nil if no fexpr specified
- };
-
literal struct {
// TODO should there be other types or should it all be string literals?
value []byte;
};
+ indentation struct {
+ iexpr expr; // outdent if nil
+ };
+
+ field struct {
+ fname string; // including "^", "*"
+ tname string; // "" if no tname specified
+ };
+
+ negation struct {
+ neg expr;
+ };
+
option struct {
- x expr
+ opt expr;
};
repetition struct {
- x expr
+ rep expr;
+ div expr;
};
- // TODO custom formats are not yet used
custom struct {
name string;
- f func(w io.Write, value interface{}, name string) bool
+ form Formatter
};
)
@@ -90,31 +107,47 @@ func (x *sequence) String() string {
}
+func (x *literal) String() string {
+ return strconv.Quote(string(x.value));
+}
+
+
+func (x *indentation) String() string {
+ if x.iexpr != nil {
+ fmt.Sprintf(">> %s", x.iexpr);
+ }
+ return "<<";
+}
+
+
func (x *field) String() string {
- if x.fexpr == nil {
- return x.name;
+ if x.tname == "" {
+ return x.fname;
}
- return fmt.Sprintf("%s: (%v)", x.name, x.fexpr);
+ return x.fname + " : " + x.tname;
}
-func (x *literal) String() string {
- return strconv.Quote(string(x.value));
+func (x *negation) String() string {
+ return fmt.Sprintf("!%v", x.neg);
}
func (x *option) String() string {
- return fmt.Sprintf("[%v]", x.x);
+ return fmt.Sprintf("[%v]", x.opt);
}
func (x *repetition) String() string {
- return fmt.Sprintf("{%v}", x.x);
+ if x.div == nil {
+ return fmt.Sprintf("{%v}", x.rep);
+ }
+ return fmt.Sprintf("{%v / %v}", x.rep, x.div);
}
func (x *custom) String() string {
- return fmt.Sprintf("<custom %s>", x.name);
+ return "<" + x.name + ">";
}
@@ -124,13 +157,15 @@ func (x *custom) String() string {
Formatting rules are specified in the following syntax:
- Format = { Production } .
- Production = Name [ "=" [ Expression ] ] ";" .
+ Format = Production { ";" Production } [ ";" ] .
+ Production = Name "=" Expression .
Name = identifier { "." identifier } .
- Expression = Term { "|" Term } .
+ Expression = [ Term ] { "|" [ Term ] } .
Term = Factor { Factor } .
- Factor = string_literal | Field | Group | Option | Repetition .
- Field = ( "^" | "*" | Name ) [ ":" Expression ] .
+ Factor = string_literal | Indent | Field | Negation | Group | Option | Repetition .
+ Indent = ">>" Factor | "<<" .
+ Field = ( "^" | "*" | Name ) [ ":" Name ] .
+ Negation = "!" Factor .
Group = "(" Expression ")" .
Option = "[" Expression "]" .
Repetition = "{" Expression "}" .
@@ -182,9 +217,6 @@ type Format map [string] expr;
- installable custom formatters (like for template.go)
- have a format to select type name, field tag, field offset?
- use field tag as default format for that field
- - field format override (":") is not working as it should
- (cannot refer to another production - syntactially not possible
- at the moment)
*/
type parser struct {
@@ -257,12 +289,6 @@ func (p *parser) parseName() string {
}
-// TODO WriteByte should be a ByteBuffer method
-func writeByte(buf *io.ByteBuffer, b byte) {
- buf.Write([]byte{b});
-}
-
-
func (p *parser) parseValue() []byte {
if p.tok != token.STRING {
p.expect(token.STRING);
@@ -281,30 +307,33 @@ func (p *parser) parseValue() []byte {
}
+func (p *parser) parseFactor() (x expr)
func (p *parser) parseExpr() expr
func (p *parser) parseField() expr {
- var name string;
+ var fname string;
switch p.tok {
case token.XOR:
- name = "^";
+ fname = "^";
p.next();
case token.MUL:
- name = "*";
+ fname = "*";
p.next();
case token.IDENT:
- name = p.parseName();
+ // TODO use reflect.ExpandType() to lookup a field
+ // during parse-time if posssible
+ fname = p.parseName();
default:
return nil;
}
- var fexpr expr;
+ var tname string;
if p.tok == token.COLON {
p.next();
- fexpr = p.parseExpr();
+ tname = p.parseName();
}
- return &field{name, fexpr};
+ return &field{fname, tname};
}
@@ -313,6 +342,18 @@ func (p *parser) parseFactor() (x expr) {
case token.STRING:
x = &literal{p.parseValue()};
+ case token.SHR:
+ p.next();
+ x = &indentation{p.parseFactor()};
+
+ case token.SHL:
+ p.next();
+ x = &indentation{nil};
+
+ case token.NOT:
+ p.next();
+ x = &negation{p.parseFactor()};
+
case token.LPAREN:
p.next();
x = p.parseExpr();
@@ -325,7 +366,13 @@ func (p *parser) parseFactor() (x expr) {
case token.LBRACE:
p.next();
- x = &repetition{p.parseExpr()};
+ x = p.parseExpr();
+ var div expr;
+ if p.tok == token.QUO {
+ p.next();
+ div = p.parseExpr();
+ }
+ x = &repetition{x, div};
p.expect(token.RBRACE);
default:
@@ -338,18 +385,15 @@ func (p *parser) parseFactor() (x expr) {
func (p *parser) parseTerm() expr {
x := p.parseFactor();
- if x == nil {
- p.error_expected(p.pos, "factor");
- p.next(); // make progress
- return nil;
- }
- for {
- y := p.parseFactor();
- if y == nil {
- break;
+ if x != nil {
+ for {
+ y := p.parseFactor();
+ if y == nil {
+ break;
+ }
+ x = &sequence{x, y};
}
- x = &sequence{x, y};
}
return x;
@@ -369,36 +413,35 @@ func (p *parser) parseExpr() expr {
}
+func (p *parser) parseProd() (string, expr) {
+ name := p.parseName();
+ p.expect(token.ASSIGN);
+ x := p.parseExpr();
+ return name, x;
+}
+
+
func (p *parser) parseFormat() Format {
format := make(Format);
- prefix := "";
for p.tok != token.EOF {
pos := p.pos;
- name := p.parseName();
-
- if p.tok == token.ASSIGN {
- // production
+ name, x := p.parseProd();
+
+ // add production to format
+ if t, found := format[name]; !found {
+ format[name] = x;
+ } else {
+ p.Error(pos, "production already declared: " + name);
+ }
+
+ if p.tok == token.SEMICOLON {
p.next();
- var x expr;
- if p.tok != token.SEMICOLON {
- x = p.parseExpr();
- }
- // add production to format
- name = prefix + name;
- if t, found := format[name]; !found {
- format[name] = x;
- } else {
- p.Error(pos, "production already declared: " + name);
- }
-
} else {
- // prefix only
- prefix = name + ".";
+ break;
}
-
- p.expect(token.SEMICOLON);
}
+ p.expect(token.EOF);
return format;
}
@@ -441,18 +484,36 @@ func readSource(src interface{}, err scanner.ErrorHandler) []byte {
// a string, a []byte, or implement io.Read. The result is a Format
// if no errors occured; otherwise Parse returns nil.
//
-func Parse(src interface{}) Format {
+func Parse(src interface{}, fmap FormatterMap) Format {
// initialize parser
var p parser;
p.scanner.Init(readSource(src, &p), &p, false);
p.next();
- f := p.parseFormat();
-
+ format := p.parseFormat();
if p.lastline > 0 {
return nil; // src contains errors
}
- return f;
+
+ // add custom formatters if any
+ if fmap != nil {
+ for name, form := range fmap {
+ if t, found := format[name]; !found {
+ format[name] = &custom{name, form};
+ } else {
+ p.Error(token.Position{0, 0, 0}, "formatter already declared: " + name);
+ }
+ }
+ }
+
+ return format;
+}
+
+
+func (f Format) Dump() {
+ for name, form := range f {
+ fmt.Printf("%s = %v;\n", name, form);
+ }
}
@@ -473,10 +534,12 @@ func fieldIndex(v reflect.StructValue, fieldname string) int {
func getField(v reflect.StructValue, i int) reflect.Value {
fld := v.Field(i);
+ /*
if tmp, is_interface := fld.(reflect.InterfaceValue); is_interface {
// TODO do I have to check something for nil here?
fld = reflect.NewValue(tmp.Get());
}
+ */
return fld;
}
@@ -484,7 +547,7 @@ func getField(v reflect.StructValue, i int) reflect.Value {
func getFieldByName(v reflect.StructValue, fieldname string) reflect.Value {
i := fieldIndex(v, fieldname);
if i < 0 {
- panicln("field not found:", fieldname);
+ panicln(fmt.Sprintf("no field %s int %s", fieldname, v.Type().Name()));
}
return getField(v, i);
@@ -502,7 +565,7 @@ func typename(value reflect.Value) string {
case reflect.ArrayKind: name = "array";
case reflect.BoolKind: name = "bool";
case reflect.ChanKind: name = "chan";
- case reflect.DotDotDotKind: name = "...";
+ case reflect.DotDotDotKind: name = "ellipsis";
case reflect.FloatKind: name = "float";
case reflect.Float32Kind: name = "float32";
case reflect.Float64Kind: name = "float64";
@@ -530,91 +593,177 @@ func typename(value reflect.Value) string {
var defaults = map [int] expr {
- reflect.ArrayKind: &field{"*", nil},
- reflect.DotDotDotKind: &field{"*", nil},
- reflect.InterfaceKind: &field{"*", nil},
- reflect.MapKind: &field{"*", nil},
- reflect.PtrKind: &field{"*", nil},
+ reflect.ArrayKind: &field{"*", ""},
+ reflect.DotDotDotKind: &field{"*", ""},
+ reflect.InterfaceKind: &field{"*", ""},
+ reflect.MapKind: &field{"*", ""},
+ reflect.PtrKind: &field{"*", ""},
+ reflect.StringKind: &literal{io.StringBytes("%s")},
}
var catchAll = &literal{io.StringBytes("%v")};
-func (f Format) getFormat(value reflect.Value) expr {
- if fexpr, found := f[typename(value)]; found {
+func (f Format) getFormat(name string, value reflect.Value) expr {
+ /*
+ if name == "nil" {
+ fmt.Printf("value = %T %v, kind = %d\n", value, value, value.Kind());
+ panic();
+ }
+ */
+
+ if fexpr, found := f[name]; found {
return fexpr;
}
+
+ if *debug {
+ fmt.Printf("no production for type: %s\n", name);
+ }
// no fexpr found - return kind-specific default value, if any
if fexpr, found := defaults[value.Kind()]; found {
return fexpr;
}
+ if *debug {
+ fmt.Printf("no default for type: %s\n", name);
+ }
+
return catchAll;
}
// Count the number of printf-style '%' formatters in s.
-// The result is 0, 1, or 2 (where 2 stands for 2 or more).
//
func percentCount(s []byte) int {
n := 0;
- for i := 0; n < 2 && i < len(s); i++ {
- // TODO should not count "%%"'s
+ for i := 0; i < len(s); i++ {
if s[i] == '%' {
- n++;
+ i++;
+ if i >= len(s) || s[i] != '%' { // don't count "%%"
+ n++;
+ }
}
}
return n;
}
-func printf(w io.Write, format []byte, value reflect.Value) {
- // TODO this seems a bit of a hack
- if percentCount(format) == 1 {
- // exactly one '%' format specifier - try to use it
- fmt.Fprintf(w, string(format), value.Interface());
- } else {
- // 0 or more then 1 '%' format specifier - ignore them
- w.Write(format);
+func rawPrintf(w io.Write, format []byte, value reflect.Value) {
+ // TODO find a better way to do this
+ x := value.Interface();
+ switch percentCount(format) {
+ case 0: w.Write(format);
+ case 1: fmt.Fprintf(w, string(format), x);
+ case 2: fmt.Fprintf(w, string(format), x, x);
+ case 3: fmt.Fprintf(w, string(format), x, x, x);
+ case 4: fmt.Fprintf(w, string(format), x, x, x, x);
+ default: panic("no support for more than 4 '%'-format chars yet");
}
}
-// TODO once 6g bug found
-func print(s string, a ...) {
- /*
- f0 := reflect.NewValue(a).(reflect.StructValue).Field(0);
- if t, is_iface := f0.(reflect.InterfaceValue); is_iface {
- f0 = reflect.NewValue(t.Get());
+// TODO this should become a Go built-in
+func push(dst []int, x int) []int {
+ n := len(dst);
+ if n > cap(dst) {
+ panic("dst too small");
}
- */
- fmt.Printf(s, a)
+ dst = dst[0 : n+1];
+ dst[n] = x;
+ return dst;
+}
+
+
+func append(dst, src []byte) []byte {
+ n, m := len(dst), len(src);
+ if n+m > cap(dst) {
+ panic("dst too small");
+ }
+ dst = dst[0 : n+m];
+ for i := 0; i < m; i++ {
+ dst[n+i] = src[i];
+ }
+ return dst;
+}
+
+
+type state struct {
+ f Format;
+
+ // indentation
+ indent_text []byte;
+ indent_widths []int;
+}
+
+
+func (ps *state) init(f Format) {
+ ps.f = f;
+ ps.indent_text = make([]byte, 0, 1000); // TODO don't use fixed cap
+ ps.indent_widths = make([]int, 0, 100); // TODO don't use fixed cap
+}
+
+
+func (ps *state) indent(text []byte) {
+ ps.indent_widths = push(ps.indent_widths, len(ps.indent_text));
+ ps.indent_text = append(ps.indent_text, text);
+}
+
+
+func (ps *state) outdent() {
+ i := len(ps.indent_widths);
+ if i > 0 {
+ ps.indent_text = ps.indent_text[0 : ps.indent_widths[i-1]];
+ ps.indent_widths = ps.indent_widths[0 : i-1];
+ }
+}
+
+
+func (ps *state) printIndented(w io.Write, s []byte) {
+ // replace each '\n' with the indent + '\n'
+ i0 := 0;
+ for i := 0; i < len(s); i++ {
+ if s[i] == '\n' {
+ w.Write(s[i0 : i+1]);
+ w.Write(ps.indent_text);
+ i0 = i+1;
+ }
+ }
+ w.Write(s[i0 : len(s)]);
}
-func (f Format) print(w io.Write, fexpr expr, value reflect.Value, index, level int) bool
+func (ps *state) printf(w io.Write, format []byte, value reflect.Value) {
+ if len(ps.indent_widths) == 0 {
+ // no indentation
+ rawPrintf(w, format, value);
+ } else {
+ // print into temporary buffer
+ var buf io.ByteBuffer;
+ rawPrintf(&buf, format, value);
+ ps.printIndented(w, buf.Data());
+ }
+}
+
+
+func (ps *state) print(w io.Write, fexpr expr, value reflect.Value, index, level int) bool
// Returns true if a non-empty field value was found.
-func (f Format) print0(w io.Write, fexpr expr, value reflect.Value, index, level int) bool {
+func (ps *state) print0(w io.Write, fexpr expr, value reflect.Value, index, level int) bool {
if fexpr == nil {
return true;
}
- if value == nil {
- panic("should not be possible");
- }
-
switch t := fexpr.(type) {
case *alternative:
// - print the contents of the first alternative with a non-empty field
// - result is true if there is at least one non-empty field
var buf io.ByteBuffer;
- if f.print(&buf, t.x, value, index, level) {
+ if ps.print(&buf, t.x, value, 0, level) {
w.Write(buf.Data());
return true;
} else {
- buf.Reset();
- if f.print(&buf, t.y, value, 0, level) {
+ var buf io.ByteBuffer;
+ if ps.print(&buf, t.y, value, 0, level) {
w.Write(buf.Data());
return true;
}
@@ -625,27 +774,43 @@ func (f Format) print0(w io.Write, fexpr expr, value reflect.Value, index, level
// - print the contents of the sequence
// - result is true if there is no empty field
// TODO do we need to buffer here? why not?
- b1 := f.print(w, t.x, value, index, level);
- b2 := f.print(w, t.y, value, index, level);
+ b1 := ps.print(w, t.x, value, index, level);
+ b2 := ps.print(w, t.y, value, index, level);
return b1 && b2;
+ case *literal:
+ // - print the literal
+ // - result is always true (literal is never empty)
+ ps.printf(w, t.value, value);
+ return true;
+
+ case *indentation:
+ if t.iexpr != nil {
+ // indent
+ var buf io.ByteBuffer;
+ ps.print(&buf, t.iexpr, value, index, level);
+ ps.indent(buf.Data());
+
+ } else {
+ // outdent
+ ps.outdent();
+ }
+ return true;
+
case *field:
// - print the contents of the field
// - format is either the field format or the type-specific format
// - TODO look at field tag for default format
// - result is true if the field is not empty
- switch t.name {
+ switch t.fname {
case "^":
// identity - value doesn't change
case "*":
// indirect
- if value.Addr() == nil { // TODO is this right?
- return false;
- }
switch v := value.(type) {
case reflect.ArrayValue:
- if index < 0 || v.Len() <= index {
+ if v.Len() <= index {
return false;
}
value = v.Elem(index);
@@ -654,54 +819,54 @@ func (f Format) print0(w io.Write, fexpr expr, value reflect.Value, index, level
panic("reflection support for maps incomplete");
case reflect.PtrValue:
- if v.Get() == nil { // TODO is this right?
+ if v.Get() == nil {
return false;
}
value = v.Sub();
case reflect.InterfaceValue:
- if v.Get() == nil { // TODO is this right?
+ if v.Get() == nil {
return false;
}
value = v.Value();
default:
- panic("not a ptr, array, map, or interface"); // TODO fix this
- }
-
- if value == nil {
- fmt.Fprint(w, "NIL"); // TODO debugging
- return false;
+ // TODO fix this
+ panic(fmt.Sprintf("error: * does not apply to `%s`\n", value.Type().Name()));
}
default:
// field
if s, is_struct := value.(reflect.StructValue); is_struct {
- value = getFieldByName(s, t.name);
+ value = getFieldByName(s, t.fname);
} else {
- panic ("not a struct"); // TODO fix this
+ // TODO fix this
+ panic(fmt.Sprintf("error: %s has no field `%s`\n", value.Type().Name(), t.fname));
}
}
// determine format
- fexpr = t.fexpr;
- if fexpr == nil {
- // no field format - use type-specific format
- fexpr = f.getFormat(value);
+ tname := t.tname;
+ if tname == "" {
+ tname = typename(value)
}
+ fexpr = ps.f.getFormat(tname, value);
- return f.print(w, fexpr, value, index, level);
+ return ps.print(w, fexpr, value, index, level);
- case *literal:
- // - print the literal
- // - result is always true (literal is never empty)
- printf(w, t.value, value);
+ case *negation:
+ // TODO is this operation useful at all?
+ // print the contents of the option if is contains an empty field
+ var buf io.ByteBuffer;
+ if !ps.print(&buf, t.neg, value, 0, level) {
+ w.Write(buf.Data());
+ }
return true;
-
+
case *option:
// print the contents of the option if it contains a non-empty field
var buf io.ByteBuffer;
- if f.print(&buf, t.x, value, 0, level) {
+ if ps.print(&buf, t.opt, value, 0, level) {
w.Write(buf.Data());
}
return true;
@@ -709,14 +874,22 @@ func (f Format) print0(w io.Write, fexpr expr, value reflect.Value, index, level
case *repetition:
// print the contents of the repetition while there is a non-empty field
var buf io.ByteBuffer;
- for i := 0; f.print(&buf, t.x, value, i, level); i++ {
+ for i := 0; ps.print(&buf, t.rep, value, i, level); i++ {
+ if i > 0 {
+ ps.print(w, t.div, value, i, level);
+ }
w.Write(buf.Data());
buf.Reset();
}
return true;
case *custom:
- return t.f(w, value.Interface(), t.name);
+ var buf io.ByteBuffer;
+ if t.form(&buf, value.Interface(), t.name) {
+ ps.printIndented(w, buf.Data());
+ return true;
+ }
+ return false;
}
panic("unreachable");
@@ -738,14 +911,14 @@ func printTrace(indent int, format string, a ...) {
}
-func (f Format) print(w io.Write, fexpr expr, value reflect.Value, index, level int) bool {
- if *debug {
+func (ps *state) print(w io.Write, fexpr expr, value reflect.Value, index, level int) bool {
+ if *trace {
printTrace(level, "%v, %d {\n", fexpr, /*value.Interface(), */index);
}
- result := f.print0(w, fexpr, value, index, level+1);
+ result := ps.print0(w, fexpr, value, index, level+1);
- if *debug {
+ if *trace {
printTrace(level, "} %v\n", result);
}
return result;
@@ -761,7 +934,9 @@ func (f Format) Fprint(w io.Write, args ...) {
value := reflect.NewValue(args).(reflect.StructValue);
for i := 0; i < value.Len(); i++ {
fld := getField(value, i);
- f.print(w, f.getFormat(fld), fld, -1, 0);
+ var ps state;
+ ps.init(f);
+ ps.print(w, f.getFormat(typename(fld), fld), fld, 0, 0);
}
}
@@ -770,7 +945,7 @@ func (f Format) Fprint(w io.Write, args ...) {
// and writes to standard output.
//
func (f Format) Print(args ...) {
- f.Print(os.Stdout, args);
+ f.Fprint(os.Stdout, args);
}
diff --git a/usr/gri/pretty/format_test.go b/usr/gri/pretty/format_test.go
index e2948cc34..80401ba83 100644
--- a/usr/gri/pretty/format_test.go
+++ b/usr/gri/pretty/format_test.go
@@ -11,10 +11,10 @@ import (
func check(t *testing.T, form, expected string, args ...) {
- result := format.Parse(form).Sprint(args);
+ result := format.Parse(form, nil).Sprint(args);
if result != expected {
t.Errorf(
- "format : %s\nresult : %s\nexpected: %s\n\n",
+ "format : %s\nresult : `%s`\nexpected: `%s`\n\n",
form, result, expected
)
}
@@ -22,13 +22,19 @@ func check(t *testing.T, form, expected string, args ...) {
// ----------------------------------------------------------------------------
-// - formatting of basic type int
+// Syntax
-const F0 =
- `int = "0x%x";`
+func TestA(t *testing.T) {
+ // TODO fill this in
+}
+
+
+// ----------------------------------------------------------------------------
+// - formatting of basic types
func Test0(t *testing.T) {
- check(t, F0, "0x2a", 42);
+ check(t, `bool = "%v"`, "false", false);
+ check(t, `int = "%b %d %o 0x%x"`, "101010 42 52 0x2a", 42);
}
@@ -60,7 +66,7 @@ type T2 struct {
const F2a =
F1 +
`pointer = *;`
- `format.T2 = s ["-" p "-"];`;
+ `format.T2 = s ["-" p "-"];`
const F2b =
F1 +
@@ -82,14 +88,43 @@ type T3 struct {
}
const F3a =
- `format.T3 = s { " " a a "," };`;
+ `format.T3 = s {" " a a / ","};`
const F3b =
- `format.T3 = [a:""] s | "nothing";`; // use 'a' to select alternative w/o printing a
+ `nil = ;`
+ `empty = *:nil;`
+ `format.T3 = s [a:empty ": " {a / "-"}]`
func Test3(t *testing.T) {
check(t, F3a, "foo", T3{"foo", nil});
- check(t, F3a, "foo 00, 11, 22,", T3{"foo", []int{0, 1, 2}});
- //check(t, F3b, "nothing", T3{"bar", nil}); // TODO fix this
- check(t, F3b, "bar", T3{"bar", []int{0}});
+ check(t, F3a, "foo 00, 11, 22", T3{"foo", []int{0, 1, 2}});
+ check(t, F3b, "bar", T3{"bar", nil});
+ check(t, F3b, "bal: 2-3-5", T3{"bal", []int{2, 3, 5}});
+}
+
+
+// ----------------------------------------------------------------------------
+// - formatting of a struct with alternative field
+
+type T4 struct {
+ x *int;
+ a []int;
+}
+
+const F4a =
+ `nil = ;`
+ `empty = *:nil;`
+ `format.T4 = "<" (x:empty x | "-") ">" `
+
+const F4b =
+ `nil = ;`
+ `empty = *:nil;`
+ `format.T4 = "<" (a:empty {a / ", "} | "-") ">" `
+
+func Test4(t *testing.T) {
+ x := 7;
+ check(t, F4a, "<->", T4{nil, nil});
+ check(t, F4a, "<7>", T4{&x, nil});
+ check(t, F4b, "<->", T4{nil, nil});
+ check(t, F4b, "<2, 3, 7>", T4{nil, []int{2, 3, 7}});
}
diff --git a/usr/gri/pretty/pretty.go b/usr/gri/pretty/pretty.go
index 81bb4d45e..0f4bafa25 100644
--- a/usr/gri/pretty/pretty.go
+++ b/usr/gri/pretty/pretty.go
@@ -66,7 +66,7 @@ func makeTabwriter(writer io.Write) *tabwriter.Writer {
if *usetabs {
padchar = '\t';
}
- return tabwriter.NewWriter(writer, *tabwidth, 1, padchar, tabwriter.FilterHTML);
+ return tabwriter.NewWriter(writer, *tabwidth, 1, padchar, 0);
}
@@ -114,7 +114,7 @@ func main() {
fmt.Fprintf(os.Stderr, "%s: %v\n", ast_txt, err);
sys.Exit(1);
}
- ast_format := format.Parse(src);
+ ast_format := format.Parse(src, nil);
if ast_format == nil {
fmt.Fprintf(os.Stderr, "%s: format errors\n", ast_txt);
sys.Exit(1);