diff options
author | Robert Griesemer <gri@golang.org> | 2009-05-01 19:32:41 -0700 |
---|---|---|
committer | Robert Griesemer <gri@golang.org> | 2009-05-01 19:32:41 -0700 |
commit | dc963a40da2b617dc4a76200769f319e0e4b59c6 (patch) | |
tree | 21f40d4173beaebaa7799d2a5a6d8681b0ff9eeb | |
parent | 33fefb72910f33a1cbd1e36dd2065c350659d18b (diff) | |
download | golang-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.txt | 300 | ||||
-rw-r--r-- | usr/gri/pretty/format.go | 475 | ||||
-rw-r--r-- | usr/gri/pretty/format_test.go | 59 | ||||
-rw-r--r-- | usr/gri/pretty/pretty.go | 4 |
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); |