]> Cypherpunks repositories - gostls13.git/commitdiff
weekly snapshot:
authorRobert Griesemer <gri@golang.org>
Sat, 2 May 2009 02:32:41 +0000 (19:32 -0700)
committerRobert Griesemer <gri@golang.org>
Sat, 2 May 2009 02:32:41 +0000 (19:32 -0700)
- 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

usr/gri/pretty/ast.txt
usr/gri/pretty/format.go
usr/gri/pretty/format_test.go
usr/gri/pretty/pretty.go

index fbf1fdc9e9e024ff8bc24d6f211822878f60c397..d25a5a3bf3c0e3730749b59577c4c04149e346c1 100644 (file)
 // 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"};
index 62009d2c895d28cb63c7530af8654e3ee8ff01f6..eff9836330273d3a5905ab3df6b6744152115b9d 100644 (file)
@@ -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);
 }
 
 
index e2948cc3404c9a0eaf1dc326fdee53a9c6832f4a..80401ba83b87d82a167ad01db1136554742e8f14 100644 (file)
@@ -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}});
 }
index 81bb4d45e4c3ce827e67abb36b6273ac36f5a7e4..0f4bafa259b07dd62d403ebc8175f1c564a12579 100644 (file)
@@ -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);