]> Cypherpunks repositories - gostls13.git/commitdiff
format package
authorRobert Griesemer <gri@golang.org>
Tue, 2 Jun 2009 02:13:44 +0000 (19:13 -0700)
committerRobert Griesemer <gri@golang.org>
Tue, 2 Jun 2009 02:13:44 +0000 (19:13 -0700)
R=r,rsc
DELTA=2871  (1712 added, 1118 deleted, 41 changed)
OCL=29222
CL=29704

src/lib/Make.deps
src/lib/Makefile
src/lib/format/Makefile [new file with mode: 0644]
src/lib/format/format.go [new file with mode: 0644]
src/lib/format/format_test.go [new file with mode: 0644]
src/lib/format/parser.go [new file with mode: 0644]
usr/gri/pretty/Makefile
usr/gri/pretty/ast.txt
usr/gri/pretty/format.go [deleted file]
usr/gri/pretty/format_test.go [deleted file]
usr/gri/pretty/pretty.go

index 6a965e32713762900a9b5102bc02de43bf404825..4a1805f4cdbd209151d75f0930547c70988e5655 100644 (file)
@@ -10,6 +10,7 @@ exec.install: os.install strings.install
 exvar.install: fmt.install http.install io.install log.install strconv.install sync.install
 flag.install: fmt.install os.install strconv.install
 fmt.install: io.install os.install reflect.install strconv.install utf8.install
+format.install: container/vector.install flag.install fmt.install go/scanner.install go/token.install io.install os.install reflect.install runtime.install strconv.install strings.install
 go/ast.install: go/token.install unicode.install utf8.install
 go/doc.install: container/vector.install fmt.install go/ast.install go/token.install io.install once.install regexp.install sort.install strings.install template.install
 go/parser.install: container/vector.install fmt.install go/ast.install go/scanner.install go/token.install io.install os.install
index 8aa70cd476c3898798acab22766f166753c9abd5..d0658605e35fc648642b85c605af22f20d1e775f 100644 (file)
@@ -26,6 +26,7 @@ DIRS=\
        exvar\
        flag\
        fmt\
+       format\
        go/ast\
        go/doc\
        go/parser\
@@ -73,6 +74,7 @@ TEST=\
        exvar\
        flag\
        fmt\
+       format\
        go/parser\
        go/scanner\
        hash/adler32\
diff --git a/src/lib/format/Makefile b/src/lib/format/Makefile
new file mode 100644 (file)
index 0000000..5979332
--- /dev/null
@@ -0,0 +1,76 @@
+# Copyright 2009 The Go Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file.
+
+# DO NOT EDIT.  Automatically generated by gobuild.
+# gobuild -m >Makefile
+
+D=
+
+O_arm=5
+O_amd64=6
+O_386=8
+OS=568vq
+
+O=$(O_$(GOARCH))
+GC=$(O)g -I_obj
+CC=$(O)c -FVw
+AS=$(O)a
+AR=6ar
+
+default: packages
+
+clean:
+       rm -rf *.[$(OS)] *.a [$(OS)].out _obj
+
+test: packages
+       gotest
+
+coverage: packages
+       gotest
+       6cov -g `pwd` | grep -v '_test\.go:'
+
+%.$O: %.go
+       $(GC) $*.go
+
+%.$O: %.c
+       $(CC) $*.c
+
+%.$O: %.s
+       $(AS) $*.s
+
+O1=\
+       format.$O\
+
+O2=\
+       parser.$O\
+
+
+phases: a1 a2
+_obj$D/format.a: phases
+
+a1: $(O1)
+       $(AR) grc _obj$D/format.a format.$O
+       rm -f $(O1)
+
+a2: $(O2)
+       $(AR) grc _obj$D/format.a parser.$O
+       rm -f $(O2)
+
+
+newpkg: clean
+       mkdir -p _obj$D
+       $(AR) grc _obj$D/format.a
+
+$(O1): newpkg
+$(O2): a1
+$(O3): a2
+
+nuke: clean
+       rm -f $(GOROOT)/pkg$D/format.a
+
+packages: _obj$D/format.a
+
+install: packages
+       test -d $(GOROOT)/pkg && mkdir -p $(GOROOT)/pkg$D
+       cp _obj$D/format.a $(GOROOT)/pkg$D/format.a
diff --git a/src/lib/format/format.go b/src/lib/format/format.go
new file mode 100644 (file)
index 0000000..392a9d0
--- /dev/null
@@ -0,0 +1,786 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+/*     The format package implements syntax-directed, type-driven formatting
+       of arbitrary data structures. Formatting a data structure consists of
+       two phases: first, a parser reads a format specification and builds a
+       "compiled" format. Then, the format can be applied repeatedly to
+       arbitrary values. Applying a format to a value evaluates to a []byte
+       containing the formatted value bytes, or nil.
+
+       A format specification is a set of package declarations and format rules:
+
+               Format      = [ Entry { ";" Entry } [ ";" ] ] .
+               Entry       = PackageDecl | FormatRule . 
+
+       (The syntax of a format specification is presented in the same EBNF
+       notation as used in the Go language specification. The syntax of white
+       space, comments, identifiers, and string literals is the same as in Go.)
+
+       A package declaration binds a package name (such as 'ast') to a
+       package import path (such as '"go/ast"'). Each package used (in
+       a type name, see below) must be declared once before use.
+
+               PackageDecl = PackageName ImportPath .
+               PackageName = identifier .
+               ImportPath  = string .
+
+       A format rule binds a rule name to a format expression. A rule name
+       may be a type name or one of the special names 'default' or '/'.
+       A type name may be the name of a predeclared type (for example, 'int',
+       'float32', etc.), the package-qualified name of a user-defined type
+       (for example, 'ast.MapType'), or an identifier indicating the structure
+       of unnamed composite types ('array', 'chan', 'func', 'interface', 'map',
+       or 'ptr'). Each rule must have a unique name; rules can be declared in
+       any order.
+
+               FormatRule  = RuleName "=" Expression .
+               RuleName    = TypeName | "default" | "/" .
+               TypeName    = [ PackageName "." ] identifier .
+
+       To format a value, the value's type name is used to select the format rule
+       (there is an override mechanism, see below). The format expression of the
+       selected rule specifies how the value is formatted. Each format expression,
+       when applied to a value, evaluates to a byte sequence or nil.
+
+       In its most general form, a format expression is a list of alternatives,
+       each of which is a sequence of operands:
+
+               Expression  = [ Sequence ] { "|" [ Sequence ] } .
+               Sequence    = Operand { Operand } .
+
+       The formatted result produced by an expression is the result of the first
+       alternative sequence that evaluates to a non-nil result; if there is no
+       such alternative, the expression evaluates to nil. The result produced by
+       an operand sequence is the concatenation of the results of its operands.
+       If any operand in the sequence evaluates to nil, the entire sequence
+       evaluates to nil.
+
+       There are five kinds of operands:
+
+               Operand     = Literal | Field | Group | Option | Repetition .
+
+       Literals evaluate to themselves, with two substitutions. First,
+       %-formats expand in the manner of fmt.Printf, with the current value
+       passed as the parameter. Second, the current indentation (see below)
+       is inserted after every newline character.
+
+               Literal     = string .
+
+       This table shows string literals applied to the value 42 and the
+       corresponding formatted result:
+
+               "foo"       foo
+               "%x"        2a
+               "x = %d"    x = 42
+               "%#x = %d"  0x2a = 42
+
+       A field operand is a field name optionally followed by an alternate
+       rule name. The field name may be an identifier or one of the special
+       names ^ or *.
+
+               Field       = FieldName [ ":" RuleName ] .
+               FieldName   = identifier | "^" | "*" .
+
+       If the field name is an identifier, the current value must be a struct,
+       and there must be a field with that name in the struct. The same lookup
+       rules apply as in the Go language (for instance, the name of an anonymous
+       field is the unqualified type name). The field name denotes the field
+       value in the struct. If the field is not found, formatting is aborted
+       and an error message is returned. (TODO consider changing the semantics
+       such that if a field is not found, it evaluates to nil).
+
+       The special name '^' denotes the current value. (TODO see if ^ can
+       change to @ or be eliminated).
+
+       The meaning of the special name '*' depends on the type of the current
+       value:
+
+               array, slice types   array, slice element (inside {} only, see below)
+               interfaces           value stored in interface
+               pointers             value pointed to by pointer
+
+       (Implementation restriction: channel, function and map types are not
+       supported due to missing reflection support).
+
+       Fields are evaluated as follows: If the field value is nil, or an array
+       or slice element does not exist, the result is nil (see below for details
+       on array/slice elements). If the value is not nil the field value is
+       formatted (recursively) using the rule corresponding to its type name,
+       or the alternate rule name, if given.
+
+       The following example shows a complete format specification for a
+       struct 'myPackage.Point'. Assume the package
+       
+               package myPackage  // in directory myDir/myPackage
+               type Point struct {
+                       name string;
+                       x, y int;
+               }
+
+       Applying the format specification
+
+               myPackage "myDir/myPackage";
+               int = "%d";
+               hexInt = "0x%x";
+               string = "---%s---";
+               myPackage.Point = name "{" x ", " y:hexInt "}";
+
+       to the value myPackage.Point{"foo", 3, 15} results in
+
+               ---foo---{3, 0xf}
+
+       Finally, an operand may be a grouped, optional, or repeated expression.
+       A grouped expression ("group") groups a more complex expression (body)
+       so that it can be used in place of a single operand:
+
+               Group       = "(" [ Indentation ">>" ] Body ")" .
+               Indentation = Expression .
+               Body        = Expression .
+
+       A group body may be prefixed by an indentation expression followed by '>>'.
+       The indentation expression is applied to the current value like any other
+       expression and the result, if not nil, is appended to the current indentation
+       during the evaluation of the body (see also formatting state, below).
+
+       An optional expression ("option") is enclosed in '[]' brackets.
+
+               Option      = "[" Body "]" .
+
+       An option evaluates to its body, except that if the body evaluates to nil,
+       the option expression evaluates to an empty []byte. Thus an option's purpose
+       is to protect the expression containing the option from a nil operand.
+
+       A repeated expression ("repetition") is enclosed in '{}' braces.
+
+               Repetition  = "{" Body [ "/" Separator ] "}" .
+               Separator   = Expression .
+       
+       A repeated expression is evaluated as follows: The body is evaluated
+       repeatedly and its results are concatenated until the body evaluates
+       to nil. The result of the repetition is the (possibly empty) concatenation,
+       but it is never nil. An implicit index is supplied for the evaluation of
+       the body: that index is used to address elements of arrays or slices. If
+       the corresponding elements do not exist, the field denoting the element
+       evaluates to nil (which in turn may terminate the repetition).
+
+       The body of a repetition may be followed by a '/' and a "separator"
+       expression. If the separator is present, it is invoked between repetitions
+       of the body.
+
+       The following example shows a complete format specification for formatting
+       a slice of unnamed type. Applying the specification
+
+               int = "%b";
+               array = { * / ", " };  // array is the type name for an unnamed slice
+
+       to the value '[]int{2, 3, 5, 7}' results in
+
+               10, 11, 101, 111
+
+       Default rule: If a format rule named 'default' is present, it is used for
+       formatting a value if no other rule was found. A common default rule is
+
+               default = "%v"
+
+       to provide default formatting for basic types without having to specify
+       a specific rule for each basic type.
+
+       Global separator rule: If a format rule named '/' is present, it is
+       invoked with the current value between literals. If the separator
+       expression evaluates to nil, it is ignored.
+
+       For instance, a global separator rule may be used to punctuate a sequence
+       of values with commas. The rules:
+
+               default = "%v";
+               / = ", ";
+
+       will format an argument list by printing each one in its default format,
+       separated by a comma and a space.
+*/
+package format
+
+import (
+       "container/vector";
+       "fmt";
+       "go/token";
+       "io";
+       "os";
+       "reflect";
+       "runtime";
+       "strconv";
+       "strings";
+)
+
+
+// ----------------------------------------------------------------------------
+// Format representation
+
+type State struct
+
+// Custom formatters implement the Formatter function type.
+// A formatter is invoked with the current formatting state, the
+// value to format, and the rule name under which the formatter
+// was installed (the same formatter function may be installed
+// under different names). The formatter may access the current state
+// to guide formatting and use State.Write to append to the state's
+// output.
+//
+// A formatter must return a boolean value indicating if it evaluated
+// to a non-nil value (true), or a nil value (false).
+//
+type Formatter func(state *State, value interface{}, ruleName string) bool
+
+
+// A FormatterMap is a set of custom formatters.
+// It maps a rule name to a formatter function.
+//
+type FormatterMap map [string] Formatter;
+
+
+// A parsed format expression is built from the following nodes.
+//
+type (
+       expr interface {};
+
+       alternatives []expr;  // x | y | z
+
+       sequence []expr;  // x y z
+
+       literal [][]byte;  // a list of string segments, possibly starting with '%'
+
+       field struct {
+               fieldName string;  // including "^", "*"
+               ruleName string;  // "" if no rule name specified
+       };
+
+       group struct {
+               indent, body expr;  // (indent >> body)
+       };
+
+       option struct {
+               body expr;  // [body]
+       };
+
+       repetition struct {
+               body, separator expr;  // {body / separator}
+       };
+
+       custom struct {
+               ruleName string;
+               fun Formatter
+       };
+)
+
+
+// A Format is the result of parsing a format specification.
+// The format may be applied repeatedly to format values.
+//
+type Format map [string] expr;
+
+
+// ----------------------------------------------------------------------------
+// Formatting
+
+// An application-specific environment may be provided to Format.Apply;
+// the environment is available inside custom formatters via State.Env().
+// Environments must implement copying; the Copy method must return an
+// complete copy of the receiver. This is necessary so that the formatter
+// can save and restore an environment (in case of an absent expression).
+//
+// If the Environment doesn't change during formatting (this is under
+// control of the custom formatters), the Copy function can simply return
+// the receiver, and thus can be very light-weight.
+//
+type Environment interface {
+       Copy() Environment
+}
+
+
+// State represents the current formatting state.
+// It is provided as argument to custom formatters.
+//
+type State struct {
+       fmt Format;  // format in use
+       env Environment;  // user-supplied environment
+       errors chan os.Error;  // not chan *Error (errors <- nil would be wrong!)
+       hasOutput bool;  // true after the first literal has been written
+       indent io.ByteBuffer;  // current indentation
+       output io.ByteBuffer;  // format output
+       linePos token.Position;  // position of line beginning (Column == 0)
+       default_ expr;  // possibly nil
+       separator expr;  // possibly nil
+}
+
+
+func newState(fmt Format, env Environment, errors chan os.Error) *State {
+       s := new(State);
+       s.fmt = fmt;
+       s.env = env;
+       s.errors = errors;
+       s.linePos = token.Position{Line: 1};
+
+       // if we have a default rule, cache it's expression for fast access
+       if x, found := fmt["default"]; found {
+               s.default_ = x;
+       }
+
+       // if we have a global separator rule, cache it's expression for fast access
+       if x, found := fmt["/"]; found {
+               s.separator = x;
+       }
+
+       return s;
+}
+
+
+// Env returns the environment passed to Format.Apply.
+func (s *State) Env() interface{} {
+       return s.env;
+}
+
+
+// LinePos returns the position of the current line beginning
+// in the state's output buffer. Line numbers start at 1.
+//
+func (s *State) LinePos() token.Position {
+       return s.linePos;
+}
+
+
+// Pos returns the position of the next byte to be written to the
+// output buffer. Line numbers start at 1.
+//
+func (s *State) Pos() token.Position {
+       offs := s.output.Len();
+       return token.Position{Line: s.linePos.Line, Column: offs - s.linePos.Offset, Offset: offs};
+}
+
+
+// Write writes data to the output buffer, inserting the indentation
+// string after each newline. It cannot return an error.
+//
+func (s *State) Write(data []byte) (int, os.Error) {
+       n := 0;
+       i0 := 0;
+       for i, ch := range data {
+               if ch == '\n' {
+                       // write text segment and indentation
+                       n1, _ := s.output.Write(data[i0 : i+1]);
+                       n2, _ := s.output.Write(s.indent.Data());
+                       n += n1 + n2;
+                       i0 = i + 1;
+                       s.linePos.Offset = s.output.Len();
+                       s.linePos.Line++;
+               }
+       }
+       n3, _ := s.output.Write(data[i0 : len(data)]);
+       return n + n3, nil;
+}
+
+
+type checkpoint struct {
+       env Environment;
+       hasOutput bool;
+       outputLen int;
+       linePos token.Position;
+}
+
+
+func (s *State) save() checkpoint {
+       saved := checkpoint{nil, s.hasOutput, s.output.Len(), s.linePos};
+       if s.env != nil {
+               saved.env = s.env.Copy();
+       }
+       return saved;
+}
+
+
+func (s *State) restore(m checkpoint) {
+       s.env = m.env;
+       s.output.Truncate(m.outputLen);
+}
+
+
+func (s *State) error(msg string) {
+       s.errors <- os.NewError(msg);
+       runtime.Goexit();
+}
+
+
+// getField searches in val, which must be a struct, for a field
+// with the given name. It returns the value and the embedded depth
+// where it was found.
+//
+func getField(val reflect.Value, fieldname string) (reflect.Value, int) {
+       // do we have a struct in the first place?
+       if val.Kind() != reflect.StructKind {
+               return nil, 0;
+       }
+       
+       sval, styp := val.(reflect.StructValue), val.Type().(reflect.StructType);
+
+       // look for field at the top level
+       for i := 0; i < styp.Len(); i++ {
+               name, typ, tag, offset := styp.Field(i);
+               if name == fieldname || name == "" && strings.HasSuffix(typ.Name(), "." + fieldname) /* anonymous field */ {
+                       return sval.Field(i), 0;
+               }
+       }
+
+       // look for field in anonymous fields
+       var field reflect.Value;
+       level := 1000;  // infinity (no struct has that many levels)
+       for i := 0; i < styp.Len(); i++ {
+               name, typ, tag, offset := styp.Field(i);
+               if name == "" {
+                       f, l := getField(sval.Field(i), fieldname);
+                       // keep the most shallow field
+                       if f != nil {
+                               switch {
+                               case l < level:
+                                       field, level = f, l;
+                               case l == level:
+                                       // more than one field at the same level,
+                                       // possibly an error unless there is a more
+                                       // shallow field found later
+                                       field = nil;
+                               }
+                       }
+               }
+       }
+       
+       return field, level + 1;
+}
+
+
+// TODO At the moment, unnamed types are simply mapped to the default
+//      names below. For instance, all unnamed arrays are mapped to
+//      'array' which is not really sufficient. Eventually one may want
+//      to be able to specify rules for say an unnamed slice of T.
+//
+var defaultNames = map[int]string {
+       reflect.ArrayKind: "array",
+       reflect.BoolKind: "bool",
+       reflect.ChanKind: "chan",
+       reflect.DotDotDotKind: "ellipsis",
+       reflect.FloatKind: "float",
+       reflect.Float32Kind: "float32",
+       reflect.Float64Kind: "float64",
+       reflect.FuncKind: "func",
+       reflect.IntKind: "int",
+       reflect.Int16Kind: "int16",
+       reflect.Int32Kind: "int32",
+       reflect.Int64Kind: "int64",
+       reflect.Int8Kind: "int8",
+       reflect.InterfaceKind: "interface",
+       reflect.MapKind: "map",
+       reflect.PtrKind: "ptr",
+       reflect.StringKind: "string",
+       reflect.StructKind: "struct",
+       reflect.UintKind: "uint",
+       reflect.Uint16Kind: "uint16",
+       reflect.Uint32Kind: "uint32",
+       reflect.Uint64Kind: "uint64",
+       reflect.Uint8Kind: "uint8",
+       reflect.UintptrKind: "uintptr",
+}
+
+
+func typename(value reflect.Value) string {
+       name := value.Type().Name();
+       if name == "" {
+               if defaultName, found := defaultNames[value.Kind()]; found {
+                       name = defaultName;
+               }
+       }
+       return name;
+}
+
+
+func (s *State) getFormat(name string) expr {
+       if fexpr, found := s.fmt[name]; found {
+               return fexpr;
+       }
+
+       if s.default_ != nil {
+               return s.default_;
+       }
+
+       s.error(fmt.Sprintf("no format rule for type: '%s'", name));
+       return nil;
+}
+
+
+// eval applies a format expression fexpr to a value. If the expression
+// evaluates internally to a non-nil []byte, that slice is appended to
+// the state's output buffer and eval returns true. Otherwise, eval
+// returns false and the state remains unchanged.
+//
+func (s *State) eval(fexpr expr, value reflect.Value, index int) bool {
+       // an empty format expression always evaluates
+       // to a non-nil (but empty) []byte
+       if fexpr == nil {
+               return true;
+       }
+
+       switch t := fexpr.(type) {
+       case alternatives:
+               // append the result of the first alternative that evaluates to
+               // a non-nil []byte to the state's output
+               mark := s.save();
+               for _, x := range t {
+                       if s.eval(x, value, index) {
+                               return true;
+                       }
+                       s.restore(mark);
+               }
+               return false;
+
+       case sequence:
+               // append the result of all operands to the state's output
+               // unless a nil result is encountered
+               mark := s.save();
+               for _, x := range t {
+                       if !s.eval(x, value, index) {
+                               s.restore(mark);
+                               return false;
+                       }
+               }
+               return true;
+
+       case literal:
+               // write separator, if any
+               if s.hasOutput {
+                       // not the first literal
+                       if s.separator != nil {
+                               sep := s.separator;  // save current separator
+                               s.separator = nil;  // and disable it (avoid recursion)
+                               mark := s.save();
+                               if !s.eval(sep, value, index) {
+                                       s.restore(mark);
+                               }
+                               s.separator = sep;  // enable it again
+                       }
+               }
+               s.hasOutput = true;
+               // write literal segments
+               for _, lit := range t {
+                       if lit[0] == '%' && len(lit) > 1 {
+                               // segment contains a %-format at the beginning
+                               if lit[1] == '%' {
+                                       // "%%" is printed as a single "%"
+                                       s.Write(lit[1 : len(lit)]);
+                               } else {
+                                       // use s instead of s.output to get indentation right
+                                       fmt.Fprintf(s, string(lit), value.Interface());
+                               }
+                       } else {
+                               // segment contains no %-formats
+                               s.Write(lit);
+                       }
+               }
+               return true;  // a literal never evaluates to nil
+
+       case *field:
+               // determine field value
+               switch t.fieldName {
+               case "^":
+                       // field value is current value
+
+               case "*":
+                       // indirection: operation is type-specific
+                       switch v := value.(type) {
+                       case reflect.ArrayValue:
+                               if v.IsNil() || v.Len() <= index {
+                                       return false;
+                               }
+                               value = v.Elem(index);
+
+                       case reflect.MapValue:
+                               s.error("reflection support for maps incomplete");
+
+                       case reflect.PtrValue:
+                               if v.IsNil() {
+                                       return false;
+                               }
+                               value = v.Sub();
+
+                       case reflect.InterfaceValue:
+                               if v.IsNil() {
+                                       return false;
+                               }
+                               value = v.Value();
+
+                       case reflect.ChanValue:
+                               s.error("reflection support for chans incomplete");
+
+                       case reflect.FuncValue:
+                               s.error("reflection support for funcs incomplete");
+
+                       default:
+                               s.error(fmt.Sprintf("error: * does not apply to `%s`", value.Type().Name()));
+                       }
+
+               default:
+                       // value is value of named field
+                       field, _ := getField(value, t.fieldName);
+                       if field == nil {
+                               // TODO consider just returning false in this case
+                               s.error(fmt.Sprintf("error: no field `%s` in `%s`", t.fieldName, value.Type().Name()));
+                       }
+                       value = field;
+               }
+
+               // determine rule
+               ruleName := t.ruleName;
+               if ruleName == "" {
+                       // no alternate rule name, value type determines rule
+                       ruleName = typename(value)
+               }
+               fexpr = s.getFormat(ruleName);
+
+               mark := s.save();
+               if !s.eval(fexpr, value, index) {
+                       s.restore(mark);
+                       return false;
+               }
+               return true;
+
+       case *group:
+               // remember current indentation
+               indentLen := s.indent.Len();
+
+               // update current indentation
+               mark := s.save();
+               s.eval(t.indent, value, index);
+               // if the indentation evaluates to nil, the state's output buffer
+               // didn't change - either way it's ok to append the difference to
+               // the current identation
+               s.indent.Write(s.output.Data()[mark.outputLen : s.output.Len()]);
+               s.restore(mark);
+
+               // format group body
+               mark = s.save();
+               b := true;
+               if !s.eval(t.body, value, index) {
+                       s.restore(mark);
+                       b = false;
+               }
+               
+               // reset indentation
+               s.indent.Truncate(indentLen);
+               return b;
+
+       case *option:
+               // evaluate the body and append the result to the state's output
+               // buffer unless the result is nil
+               mark := s.save();
+               if !s.eval(t.body, value, 0) {  // TODO is 0 index correct?
+                       s.restore(mark);
+               }
+               return true;  // an option never evaluates to nil
+
+       case *repetition:
+               // evaluate the body and append the result to the state's output
+               // buffer until a result is nil
+               for i := 0; ; i++ {
+                       mark := s.save();
+                       // write separator, if any
+                       if i > 0 && t.separator != nil {
+                               // nil result from separator is ignored
+                               mark := s.save();
+                               if !s.eval(t.separator, value, i) {
+                                       s.restore(mark);
+                               }
+                       }
+                       if !s.eval(t.body, value, i) {
+                               s.restore(mark);
+                               break;
+                       }
+               }
+               return true;  // a repetition never evaluates to nil
+
+       case *custom:
+               // invoke the custom formatter to obtain the result
+               mark := s.save();
+               if !t.fun(s, value.Interface(), t.ruleName) {
+                       s.restore(mark);
+                       return false;
+               }
+               return true;
+       }
+
+       panic("unreachable");
+       return false;
+}
+
+
+// Eval formats each argument according to the format
+// f and returns the resulting []byte and os.Error. If
+// an error occured, the []byte contains the partially
+// formatted result. An environment env may be passed
+// in which is available in custom formatters through
+// the state parameter.
+//
+func (f Format) Eval(env Environment, args ...) ([]byte, os.Error) {
+       errors := make(chan os.Error);
+       s := newState(f, env, errors);
+
+       go func() {
+               value := reflect.NewValue(args).(reflect.StructValue);
+               for i := 0; i < value.Len(); i++ {
+                       fld := value.Field(i);
+                       mark := s.save();
+                       if !s.eval(s.getFormat(typename(fld)), fld, 0) {  // TODO is 0 index correct?
+                               s.restore(mark);
+                       }
+               }
+               errors <- nil;  // no errors
+       }();
+
+       return s.output.Data(), <- errors;
+}
+
+
+// ----------------------------------------------------------------------------
+// Convenience functions
+
+// Fprint formats each argument according to the format f
+// and writes to w. The result is the total number of bytes
+// written and an os.Error, if any.
+//
+func (f Format) Fprint(w io.Writer, env Environment, args ...) (int, os.Error) {
+       data, err := f.Eval(env, args);
+       if err != nil {
+               // TODO should we print partial result in case of error?
+               return 0, err;
+       }
+       return w.Write(data);
+}
+
+
+// Print formats each argument according to the format f
+// and writes to standard output. The result is the total
+// number of bytes written and an os.Error, if any.
+//
+func (f Format) Print(args ...) (int, os.Error) {
+       return f.Fprint(os.Stdout, nil, args);
+}
+
+
+// Sprint formats each argument according to the format f
+// and returns the resulting string. If an error occurs
+// during formatting, the result string contains the
+// partially formatted result followed by an error message.
+//
+func (f Format) Sprint(args ...) string {
+       var buf io.ByteBuffer;
+       n, err := f.Fprint(&buf, nil, args);
+       if err != nil {
+               fmt.Fprintf(&buf, "--- Sprint(%s) failed: %v", fmt.Sprint(args), err);
+       }
+       return string(buf.Data());
+}
diff --git a/src/lib/format/format_test.go b/src/lib/format/format_test.go
new file mode 100644 (file)
index 0000000..92e0d0e
--- /dev/null
@@ -0,0 +1,365 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package format
+
+import (
+       "fmt";
+       "format";
+       "io";
+       "os";
+       "testing";
+)
+
+
+func parse(t *testing.T, form string, fmap format.FormatterMap) format.Format {
+       f, err := format.Parse(io.StringBytes(form), fmap);
+       if err != nil {
+               t.Errorf("Parse(%s): %v", err);
+               return nil;
+       }
+       return f;
+}
+
+
+func verify(t *testing.T, f format.Format, expected string, args ...) {
+       if f == nil {
+               return;  // allow other tests to run
+       }
+       result := f.Sprint(args);
+       if result != expected {
+               t.Errorf(
+                       "result  : `%s`\nexpected: `%s`\n\n",
+                       result, expected
+               )
+       }
+}
+
+
+func formatter(s *format.State, value interface{}, rule_name string) bool {
+       switch rule_name {
+       case "/":
+               fmt.Fprintf(s, "%d %d %d", s.Pos().Line, s.LinePos().Column, s.Pos().Column);
+               return true;
+       case "blank":
+               s.Write([]byte{' '});
+               return true;
+       case "int":
+               if value.(int) & 1 == 0 {
+                       fmt.Fprint(s, "even ");
+               } else {
+                       fmt.Fprint(s, "odd ");
+               }
+               return true;
+       case "nil":
+               return false;
+       }
+       panic("unreachable");
+       return false;
+}
+
+
+func TestCustomFormatters(t *testing.T) {
+       fmap0 := format.FormatterMap{ "/": formatter };
+       fmap1 := format.FormatterMap{ "int": formatter, "blank": formatter, "nil": formatter };
+
+       f := parse(t, `int=`, fmap0);
+       verify(t, f, ``, 1, 2, 3);
+
+       f = parse(t, `int="#"`, nil);
+       verify(t, f, `###`, 1, 2, 3);
+
+       f = parse(t, `int="#";string="%s"`, fmap0);
+       verify(t, f, "#1 0 1#1 0 7#1 0 13\n2 0 0foo2 0 8\n", 1, 2, 3, "\n", "foo", "\n");
+
+       f = parse(t, ``, fmap1);
+       verify(t, f, `even odd even odd `, 0, 1, 2, 3);
+
+       f = parse(t, `/ =^:blank; float="#"`, fmap1);
+       verify(t, f, `# # #`, 0.0, 1.0, 2.0);
+
+       f = parse(t, `float=^:nil`, fmap1);
+       verify(t, f, ``, 0.0, 1.0, 2.0);
+
+       // TODO needs more tests
+}
+
+
+// ----------------------------------------------------------------------------
+// Formatting of basic and simple composite types
+
+func check(t *testing.T, form, expected string, args ...) {
+       f := parse(t, form, nil);
+       result := f.Sprint(args);
+       if result != expected {
+               t.Errorf(
+                       "format  : %s\nresult  : `%s`\nexpected: `%s`\n\n",
+                       form, result, expected
+               )
+       }
+}
+
+
+func TestBasicTypes(t *testing.T) {
+       check(t, ``, ``);
+       check(t, `bool=":%v"`, `:true:false`, true, false);
+       check(t, `int="%b %d %o 0x%x"`, `101010 42 52 0x2a`, 42);
+       
+       check(t, `int="%"`, `%`, 42);
+       check(t, `int="%%"`, `%`, 42);
+       check(t, `int="**%%**"`, `**%**`, 42);
+       check(t, `int="%%%%%%"`, `%%%`, 42);
+       check(t, `int="%%%d%%"`, `%42%`, 42);
+
+       const i = -42;
+       const is = `-42`;
+       check(t, `int  ="%d"`, is, i);
+       check(t, `int8 ="%d"`, is, int8(i));
+       check(t, `int16="%d"`, is, int16(i));
+       check(t, `int32="%d"`, is, int32(i));
+       check(t, `int64="%d"`, is, int64(i));
+
+       const u = 42;
+       const us = `42`;
+       check(t, `uint  ="%d"`, us, uint(u));
+       check(t, `uint8 ="%d"`, us, uint8(u));
+       check(t, `uint16="%d"`, us, uint16(u));
+       check(t, `uint32="%d"`, us, uint32(u));
+       check(t, `uint64="%d"`, us, uint64(u));
+
+       const f = 3.141592;
+       const fs = `3.141592`;
+       check(t, `float  ="%g"`, fs, f);
+       check(t, `float32="%g"`, fs, float32(f));
+       check(t, `float64="%g"`, fs, float64(f));
+}
+
+
+func TestArrayTypes(t *testing.T) {
+       var a0 [10]int;
+       check(t, `array="array";`, `array`, a0);
+
+       a1 := [...]int{1, 2, 3};
+       check(t, `array="array";`, `array`, a1);
+       check(t, `array={*}; int="%d";`, `123`, a1);
+       check(t, `array={* / ", "}; int="%d";`, `1, 2, 3`, a1);
+       check(t, `array={* / *}; int="%d";`, `12233`, a1);
+
+       a2 := []interface{}{42, "foo", 3.14};
+       check(t, `array={* / ", "}; interface=*; string="bar"; default="%v";`, `42, bar, 3.14`, a2);
+}
+
+
+func TestChanTypes(t *testing.T) {
+       var c0 chan int;
+       check(t, `chan="chan"`, `chan`, c0);
+
+       c1 := make(chan int);
+       go func(){ c1 <- 42 }();
+       check(t, `chan="chan"`, `chan`, c1);
+       // check(t, `chan=*`, `42`, c1);  // reflection support for chans incomplete
+}
+
+
+func TestFuncTypes(t *testing.T) {
+       var f0 func() int;
+       check(t, `func="func"`, `func`, f0);
+
+       f1 := func() int { return 42; };
+       check(t, `func="func"`, `func`, f1);
+       // check(t, `func=*`, `42`, f1);  // reflection support for funcs incomplete
+}
+
+
+func TestInterfaceTypes(t *testing.T) {
+       var i0 interface{};
+       check(t, `interface="interface"`, `interface`, i0);
+
+       i0 = "foo";
+       check(t, `interface="interface"`, `interface`, i0);
+       check(t, `interface=*; string="%s"`, `foo`, i0);
+}
+
+
+func TestMapTypes(t *testing.T) {
+       var m0 map[string]int;
+       check(t, `map="map"`, `map`, m0);
+
+       m1 := map[string]int{};
+       check(t, `map="map"`, `map`, m1);
+       // check(t, `map=*`, ``, m1);  // reflection support for maps incomplete
+}
+
+
+func TestPointerTypes(t *testing.T) {
+       var p0 *int;
+       check(t, `ptr="ptr"`, `ptr`, p0);
+       check(t, `ptr=*`, ``, p0);
+       check(t, `ptr=*|"nil"`, `nil`, p0);
+
+       x := 99991;
+       p1 := &x;
+       check(t, `ptr="ptr"`, `ptr`, p1);
+       check(t, `ptr=*; int="%d"`, `99991`, p1);
+}
+
+
+func TestDefaultRule(t *testing.T) {
+       check(t, `default="%v"`, `42foo3.14`, 42, "foo", 3.14);
+       check(t, `default="%v"; int="%x"`, `abcdef`, 10, 11, 12, 13, 14, 15);
+       check(t, `default="%v"; int="%x"`, `ab**ef`, 10, 11, "**", 14, 15);
+       check(t, `default="%x"; int=^:default`, `abcdef`, 10, 11, 12, 13, 14, 15);
+}
+
+
+func TestGlobalSeparatorRule(t *testing.T) {
+       check(t, `int="%d"; / ="-"`, `1-2-3-4`, 1, 2, 3, 4);
+       check(t, `int="%x%x"; / ="*"`, `aa*aa`, 10, 10);
+}
+
+
+// ----------------------------------------------------------------------------
+// Formatting of a struct
+
+type T1 struct {
+       a int;
+}
+
+const F1 =
+       `format "format";`
+       `int = "%d";`
+       `format.T1 = "<" a ">";`
+
+func TestStruct1(t *testing.T) {
+       check(t, F1, "<42>", T1{42});
+}
+
+
+// ----------------------------------------------------------------------------
+// Formatting of a struct with an optional field (ptr)
+
+type T2 struct {
+       s string;
+       p *T1;
+}
+
+const F2a =
+       F1 +
+       `string = "%s";`
+       `ptr = *;`
+       `format.T2 = s ["-" p "-"];`
+       
+const F2b =
+       F1 +
+       `string = "%s";`
+       `ptr = *;`
+       `format.T2 = s ("-" p "-" | "empty");`;
+       
+func TestStruct2(t *testing.T) {
+       check(t, F2a, "foo", T2{"foo", nil});
+       check(t, F2a, "bar-<17>-", T2{"bar", &T1{17}});
+       check(t, F2b, "fooempty", T2{"foo", nil});
+}
+
+
+// ----------------------------------------------------------------------------
+// Formatting of a struct with a repetitive field (slice)
+
+type T3 struct {
+       s string;
+       a []int;
+}
+
+const F3a =
+       `format "format";`
+       `default = "%v";`
+       `array = *;`
+       `format.T3 = s  {" " a a / ","};`
+
+const F3b =
+       `format "format";`
+       `int = "%d";`
+       `string = "%s";`
+       `array = *;`
+       `nil = ;`
+       `empty = *:nil;`
+       `format.T3 = s [a:empty ": " {a / "-"}]`
+
+func TestStruct3(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, "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 =
+       `format "format";`
+       `int = "%d";`
+       `ptr = *;`
+       `array = *;`
+       `nil = ;`
+       `empty = *:nil;`
+       `format.T4 = "<" (x:empty x | "-") ">" `
+
+const F4b =
+       `format "format";`
+       `int = "%d";`
+       `ptr = *;`
+       `array = *;`
+       `nil = ;`
+       `empty = *:nil;`
+       `format.T4 = "<" (a:empty {a / ", "} | "-") ">" `
+
+func TestStruct4(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}});
+}
+
+
+// ----------------------------------------------------------------------------
+// Formatting a struct (documentation example)
+
+type Point struct {
+       name string;
+       x, y int;
+}
+
+const FPoint =
+       `format "format";`
+       `int = "%d";`
+       `hexInt = "0x%x";`
+       `string = "---%s---";`
+       `format.Point = name "{" x ", " y:hexInt "}";`
+
+func TestStructPoint(t *testing.T) {
+       p := Point{"foo", 3, 15};
+       check(t, FPoint, "---foo---{3, 0xf}", p);
+}
+
+
+// ----------------------------------------------------------------------------
+// Formatting a slice (documentation example)
+
+const FSlice =
+       `int = "%b";`
+       `array = { * / ", " }`
+       
+func TestSlice(t *testing.T) {
+       check(t, FSlice, "10, 11, 101, 111", []int{2, 3, 5, 7});
+}
+
+
+// TODO add more tests
diff --git a/src/lib/format/parser.go b/src/lib/format/parser.go
new file mode 100644 (file)
index 0000000..a6e6e5e
--- /dev/null
@@ -0,0 +1,445 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package format
+
+import (
+       "container/vector";
+       "fmt";
+       "format";
+       "go/scanner";
+       "go/token";
+       "io";
+       "os";
+       "strconv";
+       "strings";
+)
+
+// ----------------------------------------------------------------------------
+// Error handling
+
+// Error describes an individual error. The position Pos, if valid,
+// indicates the format source position the error relates to. The
+// error is specified with the Msg string.
+//
+type Error struct {
+       Pos token.Position;
+       Msg string;
+}
+
+
+func (e *Error) String() string {
+       pos := "";
+       if e.Pos.IsValid() {
+               pos = fmt.Sprintf("%d:%d: ", e.Pos.Line, e.Pos.Column);
+       }
+       return pos + e.Msg;
+}
+
+
+// An ErrorList is a list of errors encountered during parsing.
+type ErrorList []*Error
+
+
+// ErrorList implements SortInterface and the os.Error interface.
+
+func (p ErrorList) Len() int  { return len(p); }
+func (p ErrorList) Swap(i, j int)  { p[i], p[j] = p[j], p[i]; }
+func (p ErrorList) Less(i, j int) bool  { return p[i].Pos.Offset < p[j].Pos.Offset; }
+
+
+func (p ErrorList) String() string {
+       switch len(p) {
+       case 0: return "unspecified error";
+       case 1: return p[0].String();
+       }
+       return fmt.Sprintf("%s (and %d more errors)", p[0].String(), len(p) - 1);
+}
+
+
+// ----------------------------------------------------------------------------
+// Parsing
+
+type parser struct {
+       errors vector.Vector;
+       scanner scanner.Scanner;
+       pos token.Position;  // token position
+       tok token.Token;  // one token look-ahead
+       lit []byte;  // token literal
+
+       packs map [string] string;  // PackageName -> ImportPath
+       rules map [string] expr;  // RuleName -> Expression
+}
+
+
+func (p *parser) next() {
+       p.pos, p.tok, p.lit = p.scanner.Scan();
+       switch p.tok {
+       case token.CHAN, token.FUNC, token.INTERFACE, token.MAP, token.STRUCT:
+               // Go keywords for composite types are type names
+               // returned by reflect. Accept them as identifiers.
+               p.tok = token.IDENT;  // p.lit is already set correctly
+       }
+}
+
+
+func (p *parser) init(src []byte) {
+       p.errors.Init(0);
+       p.scanner.Init(src, p, 0);
+       p.next();  // initializes pos, tok, lit
+       p.packs = make(map [string] string);
+       p.rules = make(map [string] expr);
+}
+
+
+// The parser implements scanner.Error.
+func (p *parser) Error(pos token.Position, msg string) {
+       // Don't collect errors that are on the same line as the previous error
+       // in the hope to reduce the number of spurious errors due to incorrect
+       // parser synchronization.
+       if p.errors.Len() == 0 || p.errors.Last().(*Error).Pos.Line != pos.Line {
+               p.errors.Push(&Error{pos, msg});
+       }
+}
+
+
+func (p *parser) errorExpected(pos token.Position, msg string) {
+       msg = "expected " + msg;
+       if pos.Offset == p.pos.Offset {
+               // the error happened at the current position;
+               // make the error message more specific
+               msg += ", found '" + p.tok.String() + "'";
+               if p.tok.IsLiteral() {
+                       msg += " " + string(p.lit);
+               }
+       }
+       p.Error(pos, msg);
+}
+
+
+func (p *parser) expect(tok token.Token) token.Position {
+       pos := p.pos;
+       if p.tok != tok {
+               p.errorExpected(pos, "'" + tok.String() + "'");
+       }
+       p.next();  // make progress in any case
+       return pos;
+}
+
+
+func (p *parser) parseIdentifier() string {
+       name := string(p.lit);
+       p.expect(token.IDENT);
+       return name;
+}
+
+
+func (p *parser) parseTypeName() (string, bool) {
+       pos := p.pos;
+       name, isIdent := p.parseIdentifier(), true;
+       if p.tok == token.PERIOD {
+               // got a package name, lookup package
+               if importPath, found := p.packs[name]; found {
+                       name = importPath;
+               } else {
+                       p.Error(pos, "package not declared: " + name);
+               }
+               p.next();
+               name, isIdent = name + "." + p.parseIdentifier(), false;
+       }
+       return name, isIdent;
+}
+
+
+// Parses a rule name and returns it. If the rule name is
+// a package-qualified type name, the package name is resolved.
+// The 2nd result value is true iff the rule name consists of a
+// single identifier only (and thus could be a package name).
+//
+func (p *parser) parseRuleName() (string, bool) {
+       name, isIdent := "", false;
+       switch p.tok {
+       case token.IDENT:
+               name, isIdent = p.parseTypeName();
+       case token.DEFAULT:
+               name = "default";
+               p.next();
+       case token.QUO:
+               name = "/";
+               p.next();
+       default:
+               p.errorExpected(p.pos, "rule name");
+               p.next();  // make progress in any case
+       }
+       return name, isIdent;
+}
+
+
+func (p *parser) parseString() string {
+       s := "";
+       if p.tok == token.STRING {
+               var err os.Error;
+               s, err = strconv.Unquote(string(p.lit));
+               // Unquote may fail with an error, but only if the scanner found
+               // an illegal string in the first place. In this case the error
+               // has already been reported.
+               p.next();
+               return s;
+       } else {
+               p.expect(token.STRING);
+       }
+       return s;
+}
+
+
+func (p *parser) parseLiteral() literal {
+       s := io.StringBytes(p.parseString());
+
+       // A string literal may contain %-format specifiers. To simplify
+       // and speed up printing of the literal, split it into segments
+       // that start with "%" possibly followed by a last segment that
+       // starts with some other character.
+       var list vector.Vector;
+       list.Init(0);
+       i0 := 0;
+       for i := 0; i < len(s); i++ {
+               if s[i] == '%' && i+1 < len(s) {
+                       // the next segment starts with a % format
+                       if i0 < i {
+                               // the current segment is not empty, split it off
+                               list.Push(s[i0 : i]);
+                               i0 = i;
+                       }
+                       i++;  // skip %; let loop skip over char after %
+               }
+       }
+       // the final segment may start with any character
+       // (it is empty iff the string is empty)
+       list.Push(s[i0 : len(s)]);
+
+       // convert list into a literal
+       lit := make(literal, list.Len());
+       for i := 0; i < list.Len(); i++ {
+               lit[i] = list.At(i).([]byte);
+       }
+
+       return lit;
+}
+
+
+func (p *parser) parseField() expr {
+       var fname string;
+       switch p.tok {
+       case token.XOR:
+               fname = "^";
+               p.next();
+       case token.MUL:
+               fname = "*";
+               p.next();
+       case token.IDENT:
+               fname = p.parseIdentifier();
+       default:
+               return nil;
+       }
+
+       var ruleName string;
+       if p.tok == token.COLON {
+               p.next();
+               var _ bool;
+               ruleName, _ = p.parseRuleName();
+       }
+
+       return &field{fname, ruleName};
+}
+
+
+func (p *parser) parseExpression() expr
+
+func (p *parser) parseOperand() (x expr) {
+       switch p.tok {
+       case token.STRING:
+               x = p.parseLiteral();
+
+       case token.LPAREN:
+               p.next();
+               x = p.parseExpression();
+               if p.tok == token.SHR {
+                       p.next();
+                       x = &group{x, p.parseExpression()};
+               }
+               p.expect(token.RPAREN);
+
+       case token.LBRACK:
+               p.next();
+               x = &option{p.parseExpression()};
+               p.expect(token.RBRACK);
+
+       case token.LBRACE:
+               p.next();
+               x = p.parseExpression();
+               var div expr;
+               if p.tok == token.QUO {
+                       p.next();
+                       div = p.parseExpression();
+               }
+               x = &repetition{x, div};
+               p.expect(token.RBRACE);
+
+       default:
+               x = p.parseField();  // may be nil
+       }
+
+       return x;
+}
+
+
+func (p *parser) parseSequence() expr {
+       var list vector.Vector;
+       list.Init(0);
+
+       for x := p.parseOperand(); x != nil; x = p.parseOperand() {
+               list.Push(x);
+       }
+
+       // no need for a sequence if list.Len() < 2
+       switch list.Len() {
+       case 0: return nil;
+       case 1: return list.At(0).(expr);
+       }
+
+       // convert list into a sequence
+       seq := make(sequence, list.Len());
+       for i := 0; i < list.Len(); i++ {
+               seq[i] = list.At(i).(expr);
+       }
+       return seq;
+}
+
+
+func (p *parser) parseExpression() expr {
+       var list vector.Vector;
+       list.Init(0);
+
+       for {
+               x := p.parseSequence();
+               if x != nil {
+                       list.Push(x);
+               }
+               if p.tok != token.OR {
+                       break;
+               }
+               p.next();
+       }
+
+       // no need for an alternatives if list.Len() < 2
+       switch list.Len() {
+       case 0: return nil;
+       case 1: return list.At(0).(expr);
+       }
+
+       // convert list into a alternatives
+       alt := make(alternatives, list.Len());
+       for i := 0; i < list.Len(); i++ {
+               alt[i] = list.At(i).(expr);
+       }
+       return alt;
+}
+
+
+func (p *parser) parseFormat() {
+       for p.tok != token.EOF {
+               pos := p.pos;
+
+               name, isIdent := p.parseRuleName();
+               switch p.tok {
+               case token.STRING:
+                       // package declaration
+                       importPath := p.parseString();
+
+                       // add package declaration
+                       if !isIdent {
+                               p.Error(pos, "illegal package name: " + name);
+                       } else if _, found := p.packs[name]; !found {
+                               p.packs[name] = importPath;
+                       } else {
+                               p.Error(pos, "package already declared: " + name);
+                       }
+
+               case token.ASSIGN:
+                       // format rule
+                       p.next();
+                       x := p.parseExpression();
+
+                       // add rule
+                       if _, found := p.rules[name]; !found {
+                               p.rules[name] = x;
+                       } else {
+                               p.Error(pos, "format rule already declared: " + name);
+                       }
+
+               default:
+                       p.errorExpected(p.pos, "package declaration or format rule");
+                       p.next();  // make progress in any case
+               }
+
+               if p.tok == token.SEMICOLON {
+                       p.next();
+               } else {
+                       break;
+               }
+       }
+       p.expect(token.EOF);
+}
+
+
+func remap(p *parser, name string) string {
+       i := strings.Index(name, ".");
+       if i >= 0 {
+               packageName := name[0 : i];
+               typeName := name[i : len(name)];
+               // lookup package
+               if importPath, found := p.packs[packageName]; found {
+                       name = importPath + "." + typeName;
+               } else {
+                       var invalidPos token.Position;
+                       p.Error(invalidPos, "package not declared: " + packageName);
+               }
+       }
+       return name;
+}
+
+
+// Parse parses a set of format productions from source src. Custom
+// formatters may be provided via a map of formatter functions. If
+// there are no errors, the result is a Format and the error is nil.
+// Otherwise the format is nil and a non-empty ErrorList is returned.
+//
+func Parse(src []byte, fmap FormatterMap) (Format, os.Error) {
+       // parse source
+       var p parser;
+       p.init(src);
+       p.parseFormat();
+
+       // add custom formatters, if any
+       for name, form := range fmap {
+               name = remap(&p, name);
+               if t, found := p.rules[name]; !found {
+                       p.rules[name] = &custom{name, form};
+               } else {
+                       var invalidPos token.Position;
+                       p.Error(invalidPos, "formatter already declared: " + name);
+               }
+       }
+
+       // convert errors list, if any
+       if p.errors.Len() > 0 {
+               errors := make(ErrorList, p.errors.Len());
+               for i := 0; i < p.errors.Len(); i++ {
+                       errors[i] = p.errors.At(i).(*Error);
+               }
+               return nil, errors;
+       }
+
+       return p.rules, nil;
+}
index b0662bc74b566deadbd020ef6bc0b0a10cfb5e6b..0ede5d470a8810117fc17f429945ff5026e107b4 100644 (file)
@@ -32,7 +32,7 @@ clean:
 
 godoc.6:       astprinter.6
 
-pretty.6:       astprinter.6 format.6
+pretty.6:       astprinter.6
 
 %.6:   %.go
        $(G) $(F) $<
index 85373d6b346c8b0620c43652862e2fdd3ce70435..b51146c5c1a447c2a93a96dfb62b207e43acf8cf 100644 (file)
@@ -13,7 +13,7 @@ token "token";
 array =
        *;
 
-pointer =
+ptr =
        *;
 
 string =
@@ -47,6 +47,9 @@ token.Token =
        ^:string;
 
 ast.Comment =
+       // TODO this doesn't indent properly after //-style comments because
+       //      the '\n'-char is printed as part of the comment - need to
+       //      address this
        Text:string [Text:isMultiLineComment "\n"];
 
 ast.Comments =
@@ -84,7 +87,7 @@ ast.StringList =
        {Strings / "\n"};
 
 ast.FuncLit =
-       Type " " Body;
+       Type " " Body ^:clearOptSemi;  // no optional ; after a func literal body
 
 ast.CompositeLit =
        Type "{" {Elts / ", "} "}";
@@ -123,9 +126,9 @@ ast.StructType =
        "struct"
        [Lbrace:isValidPos " {"]
        [       Fields:exists
-               >> "\t" "\n"
+               ( "\t" >> "\n"
                        {Fields / ";\n"}
-               << "\n"
+               ) "\n"
        ]
        [Rbrace:isValidPos "}"];
 
@@ -142,9 +145,9 @@ ast.InterfaceType =
        "interface"
        [Lbrace:isValidPos " {"]
        [       Methods:exists
-               >> "\t" "\n"
+               ( "\t" >> "\n"
                        {Methods / ";\n"}
-               << "\n"
+               ) "\n"
        ]
        [Rbrace:isValidPos "}"];
 
@@ -197,14 +200,17 @@ ast.ReturnStmt =
 ast.BranchStmt =
        Tok [" " Label];
 
+stmtList =
+       {^ / ^:optSemi "\n"};
+
 blockStmt =  // like ast.BlockStmt but w/o indentation
        "{"
        [List:exists
        "\n"
-               {List / ";\n"}
+               List:stmtList
        "\n"
        ]
-       "}";
+       "}" ^:setOptSemi;
 
 blockStmtPtr =
        *:blockStmt;
@@ -212,11 +218,11 @@ blockStmtPtr =
 ast.BlockStmt =
        "{"
        [List:exists
-       >> "\t" "\n"
-               {List / ";\n"}
-       << "\n"
+       ( "\t" >> "\n"
+               List:stmtList
+       ) "\n"
        ]
-       "}";
+       "}" ^:setOptSemi;
 
 ast.IfStmt =
        "if " [Init "; "] [Cond " "] Body [" else " Else];
@@ -227,9 +233,9 @@ ast.CaseClause =
        )
        ":"
        [Body:exists
-       >> "\t" "\n"
-               {Body / ";\n"}
-       <<
+       ( "\t" >> "\n"
+               Body:stmtList
+       )
        ];
 
 ast.SwitchStmt =
@@ -242,9 +248,9 @@ ast.TypeCaseClause =
        )
        ":"
        [Body:exists
-       >> "\t" "\n"
-               {Body / ";\n"}
-       <<
+       ( "\t" >> "\n"
+               Body:stmtList
+       )
        ];
 
 ast.TypeSwitchStmt =
@@ -257,9 +263,9 @@ ast.CommClause =
        )
        ":"
        [Body:exists
-       >> "\t" "\n"
-               {Body / ";\n"}
-       <<
+       ( "\t" >> "\n"
+               Body:stmtList
+       )
        ];
 
 ast.SelectStmt =
@@ -303,11 +309,13 @@ ast.BadDecl =
 ast.GenDecl =
        Doc
        Tok " "
-       (       Lparen:isValidPos
-               >> "\t" "(\n"
-               {Specs / ";\n"}
-               <<
-               "\n)"
+       (       Lparen:isValidPos "("
+               [Specs:exists
+               ( "\t" >> "\n"
+                       {Specs / ";\n"}
+               ) "\n"
+               ]
+               ")" ^:setOptSemi
        |       {Specs / ";\n"}
        );
 
diff --git a/usr/gri/pretty/format.go b/usr/gri/pretty/format.go
deleted file mode 100644 (file)
index a671beb..0000000
+++ /dev/null
@@ -1,967 +0,0 @@
-// Copyright 2009 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-/*     The format package implements syntax-directed, type-driven formatting
-       of arbitrary data structures. Formatting a data structure consists of
-       two phases: first, a format specification is parsed (once per format)
-       which results in a "compiled" format. The format can then be used
-       repeatedly to print arbitrary values to a io.Writer.
-
-       A format specification consists of a set of named format rules in EBNF.
-       The rule names correspond to the type names of the data structure to be
-       formatted. Each format rule consists of literal values and struct field
-       names which are combined into sequences, alternatives, grouped, optional,
-       repeated, or indented sub-expressions. Additionally, format rules may be
-       specified via Go formatter functions.
-
-       When formatting a value, its type name determines the format rule. The
-       syntax of the rule or the corresponding formatter function determines
-       if and how the value is formatted. A format rule may refer to a struct
-       field of the current value. In this case the same mechanism is applied
-       recursively to that field.
-*/
-package format
-
-import (
-       "container/vector";
-       "flag";
-       "fmt";
-       "go/scanner";
-       "go/token";
-       "io";
-       "os";
-       "reflect";
-       "runtime";
-       "strconv";
-       "strings";
-)
-
-
-// ----------------------------------------------------------------------------
-// Format representation
-
-// Custom formatters implement the Formatter function type.
-// A formatter is invoked with a writer w, an environment env
-// (provided to format.Fprint and simply passed through), the
-// value to format, and the rule name under which the formatter
-// was installed (the same formatter function may be installed
-// under different names).
-//
-type Formatter func(w io.Writer, env, value interface{}, rule_name string) bool
-
-
-// A FormatterMap is a set of custom formatters.
-// It maps a rule name to a formatter.
-//
-type FormatterMap map [string] Formatter;
-
-
-// A production expression is built from the following nodes.
-//
-type (
-       expr interface {};
-
-       alternatives []expr;  // x | y | z
-
-       sequence []expr;  // x y z
-
-       // a literal is represented as string or []byte
-
-       field struct {
-               field_name string;  // including "^", "*"
-               rule_name string;  // "" if no rule name specified
-       };
-
-       indentation struct {
-               indent, body expr;  // >> indent body <<
-       };
-
-       option struct {
-               body expr;  // [body]
-       };
-
-       repetition struct {
-               body, div expr;  // {body / div}
-       };
-
-       custom struct {
-               rule_name string;
-               form Formatter
-       };
-)
-
-
-/*     The syntax of a format specification is presented in the same EBNF
-       notation as used in the Go language spec. The syntax of white space,
-       comments, identifiers, and string literals is the same as in Go.
-
-       A format specification consists of a possibly empty set of package
-       declarations and format rules:
-
-               Format      = [ Entry { ";" Entry } ] [ ";" ] .
-               Entry       = PackageDecl | FormatRule . 
-
-       A package declaration binds a package name (such as 'ast') to a
-       package import path (such as '"go/ast"'). A package name must be
-       declared at most once.
-
-               PackageDecl = PackageName ImportPath .
-               PackageName = identifier .
-               ImportPath  = string .
-
-       A format rule binds a rule name to a format expression. A rule name
-       may be a type name or one of the special names 'default' (denoting
-       the default rule) or '/' (denoting the global "divider" rule - see
-       below). A type name may be the name of a predeclared type ('int',
-       'float32', etc.), the name of an anonymous composite type ('array',
-       'pointer', etc.), or the name of a user-defined type qualified by
-       the corresponding package name (for instance 'ast.MapType'). The
-       package name must have been declared already. A rule name must be
-       declared at most once.
-
-               FormatRule  = RuleName "=" Expression .
-               RuleName    = TypeName | "default" | "/" .
-               TypeName    = [ PackageName "." ] identifier .
-
-       A format expression specifies how a value is to be formatted. In its
-       most general form, a format expression is a set of alternatives separated
-       by "|". Each alternative and the entire expression may be empty.
-
-               Expression  = [ Sequence ] { "|" [ Sequence ] } .
-               Sequence    = Operand { Operand } .
-               Operand     = Literal | Field | Indentation | Group | Option | Repetition .
-
-               Literal     = string .
-               Field       = FieldName [ ":" RuleName ] .
-               FieldName   = identifier | "^" | "*" .
-
-               Indent      = ">>" Operand Expression "<<" .
-               Group       = "(" Expression ")" .
-               Option      = "[" Expression "]" .
-               Repetition  = "{" Expression [ "/" Expression ] "}" .
-
-       TODO complete this comment
-*/
-type Format map [string] expr;
-
-
-// ----------------------------------------------------------------------------
-// Error handling
-
-// Error describes an individual error. The position Pos, if valid,
-// indicates the format source position the error relates to. The
-// error is specified with the Msg string.
-// 
-type Error struct {
-       Pos token.Position;
-       Msg string;
-}
-
-
-// Error implements the os.Error interface.
-func (e *Error) String() string {
-       pos := "";
-       if e.Pos.IsValid() {
-               pos = fmt.Sprintf("%d:%d: ", e.Pos.Line, e.Pos.Column);
-       }
-       return pos + e.Msg;
-}
-
-
-// Multiple parser errors are returned as an ErrorList.
-type ErrorList []*Error
-
-
-// ErrorList implements the SortInterface.
-func (p ErrorList) Len() int  { return len(p); }
-func (p ErrorList) Swap(i, j int)  { p[i], p[j] = p[j], p[i]; }
-func (p ErrorList) Less(i, j int) bool  { return p[i].Pos.Offset < p[j].Pos.Offset; }
-
-
-// ErrorList implements the os.Error interface.
-func (p ErrorList) String() string {
-       switch len(p) {
-       case 0: return "unspecified error";
-       case 1: return p[0].String();
-       }
-       return fmt.Sprintf("%s (and %d more errors)", p[0].String(), len(p) - 1);
-}
-
-
-// ----------------------------------------------------------------------------
-// Parsing
-
-type parser struct {
-       errors vector.Vector;
-       scanner scanner.Scanner;
-       pos token.Position;  // token position
-       tok token.Token;  // one token look-ahead
-       lit []byte;  // token literal
-
-       packs map [string] string;  // PackageName -> ImportPath
-       rules Format;  // RuleName -> Expression
-}
-
-
-// The parser implements scanner.Error.
-func (p *parser) Error(pos token.Position, msg string) {
-       // Don't collect errors that are on the same line as the previous error
-       // in the hope to reduce the number of spurious errors due to incorrect
-       // parser synchronization.
-       if p.errors.Len() == 0 || p.errors.Last().(*Error).Pos.Line != pos.Line {
-               p.errors.Push(&Error{pos, msg});
-       }
-}
-
-
-func (p *parser) next() {
-       p.pos, p.tok, p.lit = p.scanner.Scan();
-}
-
-
-func (p *parser) error_expected(pos token.Position, msg string) {
-       msg = "expected " + msg;
-       if pos.Offset == p.pos.Offset {
-               // the error happened at the current position;
-               // make the error message more specific
-               msg += ", found '" + p.tok.String() + "'";
-               if p.tok.IsLiteral() {
-                       msg += " " + string(p.lit);
-               }
-       }
-       p.Error(pos, msg);
-}
-
-
-func (p *parser) expect(tok token.Token) token.Position {
-       pos := p.pos;
-       if p.tok != tok {
-               p.error_expected(pos, "'" + tok.String() + "'");
-       }
-       p.next();  // make progress in any case
-       return pos;
-}
-
-
-func (p *parser) parseIdentifier() string {
-       name := string(p.lit);
-       p.expect(token.IDENT);
-       return name;
-}
-
-
-func (p *parser) parseTypeName() (string, bool) {
-       pos := p.pos;
-       name, is_ident := p.parseIdentifier(), true;
-       if p.tok == token.PERIOD {
-               // got a package name, lookup package
-               if import_path, found := p.packs[name]; found {
-                       name = import_path;
-               } else {
-                       p.Error(pos, "package not declared: " + name);
-               }
-               p.next();
-               name, is_ident = name + "." + p.parseIdentifier(), false;
-       }
-       return name, is_ident;
-}
-
-
-// Parses a rule name and returns it. If the rule name is
-// a package-qualified type name, the package name is resolved.
-// The 2nd result value is true iff the rule name consists of a
-// single identifier only (and thus could be a package name).
-//
-func (p *parser) parseRuleName() (string, bool) {
-       name, is_ident := "", false;
-       switch p.tok {
-       case token.IDENT:
-               name, is_ident = p.parseTypeName();
-       case token.DEFAULT:
-               name = "default";
-               p.next();
-       case token.QUO:
-               name = "/";
-               p.next();
-       default:
-               p.error_expected(p.pos, "rule name");
-               p.next();  // make progress in any case
-       }
-       return name, is_ident;
-}
-
-
-func asLiteral(x interface{}) expr {
-       s := x.(string);
-       if len(s) > 0 && s[0] == '%' {
-               // literals containing format characters are represented as strings
-               return s;
-       }
-       // all other literals are represented as []byte for faster writing
-       return io.StringBytes(s);
-}
-
-
-func (p *parser) parseLiteral() expr {
-       if p.tok != token.STRING {
-               p.expect(token.STRING);
-               return "";
-       }
-
-       s, err := strconv.Unquote(string(p.lit));
-       if err != nil {
-               panic("scanner error");
-       }
-       p.next();
-
-       // A string literal may contain newline characters and %-format specifiers.
-       // To simplify and speed up printing of the literal, split it into segments
-       // that start with "\n" or "%" (but noy "%%"), possibly followed by a last
-       // segment that starts with some other character. If there is more than one
-       // such segment, return a sequence of "simple" literals, otherwise just
-       // return the string.
-
-       // split string
-       var list vector.Vector;
-       list.Init(0);
-       i0 := 0;
-       for i := 0; i < len(s); i++ {
-               switch s[i] {
-               case '\n':
-                       // next segment starts with '\n'
-               case '%':
-                       if i+1 >= len(s) || s[i+1] == '%' {
-                               i++;
-                               continue;  //  "%%" is not a format-%
-                       }
-                       // next segment starts with '%'
-               default:
-                       // all other cases do not split the string
-                       continue;
-               }
-               // split off the current segment
-               if i0 < i {
-                       list.Push(s[i0 : i]);
-                       i0 = i;
-               }
-       }
-       // the final segment may start with any character
-       // (it is empty iff the string is empty)
-       list.Push(s[i0 : len(s)]);
-
-       // no need for a sequence there is only one segment
-       if list.Len() == 1 {
-               return asLiteral(list.At(0));
-       }
-
-       // convert list into a sequence
-       seq := make(sequence, list.Len());
-       for i := 0; i < list.Len(); i++ {
-               seq[i] = asLiteral(list.At(i));
-       }
-       return seq;
-}
-
-
-func (p *parser) parseField() expr {
-       var fname string;
-       switch p.tok {
-       case token.XOR:
-               fname = "^";
-               p.next();
-       case token.MUL:
-               fname = "*";
-               p.next();
-       case token.IDENT:
-               // TODO(gri) could use reflect.ExpandType() to lookup a field
-               // at parse-time - would provide "compile-time" errors and
-               // faster printing.
-               fname = p.parseIdentifier();
-       default:
-               return nil;
-       }
-
-       var rule_name string;
-       if p.tok == token.COLON {
-               p.next();
-               var _ bool;
-               rule_name, _ = p.parseRuleName();
-       }
-
-       return &field{fname, rule_name};
-}
-
-
-func (p *parser) parseExpression() expr
-
-func (p *parser) parseOperand() (x expr) {
-       switch p.tok {
-       case token.STRING:
-               x = p.parseLiteral();
-
-       case token.SHR:
-               p.next();
-               x = &indentation{p.parseOperand(), p.parseExpression()};
-               p.expect(token.SHL);
-
-       case token.LPAREN:
-               p.next();
-               x = p.parseExpression();
-               p.expect(token.RPAREN);
-
-       case token.LBRACK:
-               p.next();
-               x = &option{p.parseExpression()};
-               p.expect(token.RBRACK);
-
-       case token.LBRACE:
-               p.next();
-               x = p.parseExpression();
-               var div expr;
-               if p.tok == token.QUO {
-                       p.next();
-                       div = p.parseExpression();
-               }
-               x = &repetition{x, div};
-               p.expect(token.RBRACE);
-
-       default:
-               x = p.parseField();  // may be nil
-       }
-
-       return x;
-}
-
-
-func (p *parser) parseSequence() expr {
-       var list vector.Vector;
-       list.Init(0);
-
-       for x := p.parseOperand(); x != nil; x = p.parseOperand() {
-               list.Push(x);
-       }
-
-       // no need for a sequence if list.Len() < 2
-       switch list.Len() {
-       case 0: return nil;
-       case 1: return list.At(0).(expr);
-       }
-
-       // convert list into a sequence
-       seq := make(sequence, list.Len());
-       for i := 0; i < list.Len(); i++ {
-               seq[i] = list.At(i).(expr);
-       }
-       return seq;
-}
-
-
-func (p *parser) parseExpression() expr {
-       var list vector.Vector;
-       list.Init(0);
-
-       for {
-               x := p.parseSequence();
-               if x != nil {
-                       list.Push(x);
-               }
-               if p.tok != token.OR {
-                       break;
-               }
-               p.next();
-       }
-
-       // no need for an alternatives if list.Len() < 2
-       switch list.Len() {
-       case 0: return nil;
-       case 1: return list.At(0).(expr);
-       }
-
-       // convert list into a alternatives
-       alt := make(alternatives, list.Len());
-       for i := 0; i < list.Len(); i++ {
-               alt[i] = list.At(i).(expr);
-       }
-       return alt;
-}
-
-
-func (p *parser) parseFormat() {
-       for p.tok != token.EOF {
-               pos := p.pos;
-
-               name, is_ident := p.parseRuleName();
-               switch p.tok {
-               case token.STRING:
-                       // package declaration
-                       import_path, err := strconv.Unquote(string(p.lit));
-                       if err != nil {
-                               panic("scanner error");
-                       }
-                       p.next();
-
-                       // add package declaration
-                       if !is_ident {
-                               p.Error(pos, "illegal package name: " + name);
-                       } else if _, found := p.packs[name]; !found {
-                               p.packs[name] = import_path;
-                       } else {
-                               p.Error(pos, "package already declared: " + name);
-                       }
-
-               case token.ASSIGN:
-                       // format rule
-                       p.next();
-                       x := p.parseExpression();
-
-                       // add rule
-                       if _, found := p.rules[name]; !found {
-                               p.rules[name] = x;
-                       } else {
-                               p.Error(pos, "format rule already declared: " + name);
-                       }
-
-               default:
-                       p.error_expected(p.pos, "package declaration or format rule");
-                       p.next();  // make progress in any case
-               }
-
-               if p.tok == token.SEMICOLON {
-                       p.next();
-               } else {
-                       break;
-               }
-       }
-       p.expect(token.EOF);
-}
-
-
-func (p *parser) remap(pos token.Position, name string) string {
-       i := strings.Index(name, ".");
-       if i >= 0 {
-               package_name := name[0 : i];
-               type_name := name[i : len(name)];
-               // lookup package
-               if import_path, found := p.packs[package_name]; found {
-                       name = import_path + "." + type_name;
-               } else {
-                       p.Error(pos, "package not declared: " + package_name);
-               }
-       }
-       return name;
-}
-
-
-// Parse parses a set of format productions from source src. If there are no
-// errors, the result is a Format and the error is nil. Otherwise the format
-// is nil and a non-empty ErrorList is returned.
-//
-func Parse(src []byte, fmap FormatterMap) (Format, os.Error) {
-       // parse source
-       var p parser;
-       p.errors.Init(0);
-       p.scanner.Init(src, &p, false);
-       p.next();
-       p.packs = make(map [string] string);
-       p.rules = make(Format);
-       p.parseFormat();
-
-       // add custom formatters, if any
-       var invalidPos token.Position;
-       for name, form := range fmap {
-               name = p.remap(invalidPos, name);
-               if t, found := p.rules[name]; !found {
-                       p.rules[name] = &custom{name, form};
-               } else {
-                       var invalidPos token.Position;
-                       p.Error(invalidPos, "formatter already declared: " + name);
-               }
-       }
-
-       // convert errors list, if any
-       if p.errors.Len() > 0 {
-               errors := make(ErrorList, p.errors.Len());
-               for i := 0; i < p.errors.Len(); i++ {
-                       errors[i] = p.errors.At(i).(*Error);
-               }
-               return nil, errors;
-       }
-
-       return p.rules, nil;
-}
-
-
-// ----------------------------------------------------------------------------
-// Formatting
-
-// The current formatting state.
-type state struct {
-       f Format;  // the format used
-       env interface{};  // the user-supplied environment, simply passed through
-       def expr;  // the default rule, if any
-       div expr;  // the global divider rule, if any
-       writediv bool;  // true if the divider needs to be written
-       errors chan os.Error;  // not chan *Error: errors <- nil would be wrong!
-       indent io.ByteBuffer;  // the current indentation
-}
-
-
-func (ps *state) init(f Format, env interface{}, errors chan os.Error) {
-       ps.f = f;
-       ps.env = env;
-       // if we have a default ("default") rule, cache it for fast access
-       if def, has_def := f["default"]; has_def {
-               ps.def = def;
-       }
-       // if we have a divider ("/") rule, cache it for fast access
-       if div, has_div := f["/"]; has_div {
-               ps.div = div;
-       }
-       ps.errors = errors;
-}
-
-
-func (ps *state) error(msg string) {
-       ps.errors <- os.NewError(msg);
-       runtime.Goexit();
-}
-
-
-// Get a field value given a field name. Returns the field value and
-// the "embedding level" at which it was found. The embedding level
-// is 0 for top-level fields in a struct.
-//
-func getField(val reflect.Value, fieldname string) (reflect.Value, int) {
-       // do we have a struct in the first place?
-       if val.Kind() != reflect.StructKind {
-               return nil, 0;
-       }
-       
-       sval, styp := val.(reflect.StructValue), val.Type().(reflect.StructType);
-
-       // look for field at the top level
-       for i := 0; i < styp.Len(); i++ {
-               name, typ, tag, offset := styp.Field(i);
-               if name == fieldname || name == "" && strings.HasSuffix(typ.Name(), "." + fieldname) /* anonymous field */ {
-                       return sval.Field(i), 0;
-               }
-       }
-
-       // look for field in anonymous fields
-       var field reflect.Value;
-       level := 1000;  // infinity (no struct has that many levels)
-       for i := 0; i < styp.Len(); i++ {
-               name, typ, tag, offset := styp.Field(i);
-               if name == "" {
-                       f, l := getField(sval.Field(i), fieldname);
-                       // keep the most shallow field
-                       if f != nil && l < level {
-                               field, level = f, l;
-                       }
-               }
-       }
-       
-       return field, level + 1;
-}
-
-
-var default_names = map[int]string {
-       reflect.ArrayKind: "array",
-       reflect.BoolKind: "bool",
-       reflect.ChanKind: "chan",
-       reflect.DotDotDotKind: "ellipsis",
-       reflect.FloatKind: "float",
-       reflect.Float32Kind: "float32",
-       reflect.Float64Kind: "float64",
-       reflect.FuncKind: "func",
-       reflect.IntKind: "int",
-       reflect.Int16Kind: "int16",
-       reflect.Int32Kind: "int32",
-       reflect.Int64Kind: "int64",
-       reflect.Int8Kind: "int8",
-       reflect.InterfaceKind: "interface",
-       reflect.MapKind: "map",
-       reflect.PtrKind: "pointer",
-       reflect.StringKind: "string",
-       reflect.StructKind: "struct",
-       reflect.UintKind: "uint",
-       reflect.Uint16Kind: "uint16",
-       reflect.Uint32Kind: "uint32",
-       reflect.Uint64Kind: "uint64",
-       reflect.Uint8Kind: "uint8",
-       reflect.UintptrKind: "uintptr",
-}
-
-
-func typename(value reflect.Value) string {
-       name := value.Type().Name();
-       if name == "" {
-               if default_name, found := default_names[value.Kind()]; found {
-                       name = default_name;
-               }
-       }
-       return name;
-}
-
-
-func (ps *state) getFormat(name string) expr {
-       if fexpr, found := ps.f[name]; found {
-               return fexpr;
-       }
-
-       if ps.def != nil {
-               return ps.def;
-       }
-
-       ps.error(fmt.Sprintf("no production for type: '%s'\n", name));
-       return nil;
-}
-
-
-func (ps *state) printf(w io.Writer, fexpr expr, value reflect.Value, index int) bool
-
-
-func (ps *state) printDiv(w io.Writer, value reflect.Value) {
-       if ps.div != nil && ps.writediv {
-               div := ps.div;
-               ps.div = nil;
-               ps.printf(w, div, value, 0);
-               ps.div = div;
-       }
-       ps.writediv = true;
-}
-
-
-func (ps *state) writeIndented(w io.Writer, s []byte) {
-       // write indent after each '\n'
-       i0 := 0;
-       for i := 0; i < len(s); i++ {
-               if s[i] == '\n' {
-                       w.Write(s[i0 : i+1]);
-                       w.Write(ps.indent.Data());
-                       i0 = i+1;
-               }
-       }
-       w.Write(s[i0 : len(s)]);
-}
-
-
-// TODO complete this comment
-// Returns true if a non-empty field value was found.
-func (ps *state) printf(w io.Writer, fexpr expr, value reflect.Value, index int) bool {
-       if fexpr == nil {
-               return true;
-       }
-
-       switch t := fexpr.(type) {
-       case alternatives:
-               // - write first non-empty alternative
-               // - result is not empty iff there is an non-empty alternative
-               for _, x := range t {
-                       var buf io.ByteBuffer;
-                       if ps.printf(&buf, x, value, 0) {
-                               w.Write(buf.Data());
-                               return true;
-                       }
-               }
-               return false;
-
-       case sequence:
-               // - write every element of the sequence
-               // - result is not empty iff no element was empty
-               b := true;
-               for _, x := range t {
-                       b = ps.printf(w, x, value, index) && b;
-               }
-               return b;
-
-       case []byte:
-               // write literal, may start with "\n"
-               ps.printDiv(w, value);
-               if len(t) > 0 && t[0] == '\n' && ps.indent.Len() > 0 {
-                       // newline must be followed by indentation
-                       w.Write([]byte{'\n'});
-                       w.Write(ps.indent.Data());
-                       t = t[1 : len(t)];
-               }
-               w.Write(t);
-               return true;
-               
-       case string:
-               // write format literal with value, starts with "%" (but not "%%")
-               ps.printDiv(w, value);
-               fmt.Fprintf(w, t, value.Interface());
-               return true;
-
-       case *field:
-               // - write the contents of the field
-               // - format is either the field format or the type-specific format
-               // - result is not empty iff the field is not empty
-               switch t.field_name {
-               case "^":
-                       // identity - value doesn't change
-
-               case "*":
-                       // indirect
-                       switch v := value.(type) {
-                       case reflect.ArrayValue:
-                               if v.Len() <= index {
-                                       return false;
-                               }
-                               value = v.Elem(index);
-
-                       case reflect.MapValue:
-                               ps.error("reflection support for maps incomplete\n");
-
-                       case reflect.PtrValue:
-                               if v.Get() == nil {
-                                       return false;
-                               }
-                               value = v.Sub();
-
-                       case reflect.InterfaceValue:
-                               if v.Get() == nil {
-                                       return false;
-                               }
-                               value = v.Value();
-
-                       default:
-                               ps.error(fmt.Sprintf("error: * does not apply to `%s`\n", value.Type().Name()));
-                       }
-
-               default:
-                       // field
-                       field, _ := getField(value, t.field_name);
-                       if field == nil {
-                               ps.error(fmt.Sprintf("error: no field `%s` in `%s`\n", t.field_name, value.Type().Name()));
-                       }
-                       value = field;
-               }
-
-               // field-specific rule name
-               rule_name := t.rule_name;
-               if rule_name == "" {
-                       rule_name = typename(value)
-               }
-               fexpr = ps.getFormat(rule_name);
-
-               return ps.printf(w, fexpr, value, index);
-
-       case *indentation:
-               // - write the body within the given indentation
-               // - the result is not empty iff the body is not empty
-               saved_len := ps.indent.Len();
-               ps.printf(&ps.indent, t.indent, value, index);  // add additional indentation
-               b := ps.printf(w, t.body, value, index);
-               ps.indent.Truncate(saved_len);  // reset indentation
-               return b;
-
-       case *option:
-               // - write body if it is not empty
-               // - the result is always not empty
-               var buf io.ByteBuffer;
-               if ps.printf(&buf, t.body, value, 0) {
-                       w.Write(buf.Data());
-               }
-               return true;
-
-       case *repetition:
-               // - write body until as long as it is not empty
-               // - the result is always not empty
-               var buf io.ByteBuffer;
-               for i := 0; ps.printf(&buf, t.body, value, i); i++ {
-                       if i > 0 {
-                               ps.printf(w, t.div, value, i);
-                       }
-                       w.Write(buf.Data());
-                       buf.Reset();
-               }
-               return true;
-
-       case *custom:
-               // - invoke custom formatter
-               var buf io.ByteBuffer;
-               if t.form(&buf, ps.env, value.Interface(), t.rule_name) {
-                       ps.writeIndented(w, buf.Data());
-                       return true;
-               }
-               return false;
-       }
-
-       panic("unreachable");
-       return false;
-}
-
-
-// Sandbox to wrap a writer.
-// Counts total number of bytes written and handles write errors.
-//
-type sandbox struct {
-       writer io.Writer;
-       written int;
-       errors chan os.Error;
-}
-
-
-// Write data to the sandboxed writer. If an error occurs, Write
-// doesn't return. Instead it reports the error to the errors
-// channel and exits the current goroutine.
-//
-func (s *sandbox) Write(data []byte) (int, os.Error) {
-       n, err := s.writer.Write(data);
-       s.written += n;
-       if err != nil {
-               s.errors <- err;
-               runtime.Goexit();
-       }
-       return n, nil;
-}
-
-
-// Fprint formats each argument according to the format f
-// and writes to w. The result is the total number of bytes
-// written and an os.Error, if any.
-//
-func (f Format) Fprint(w io.Writer, env interface{}, args ...) (int, os.Error) {
-       errors := make(chan os.Error);
-       sw := sandbox{w, 0, errors};
-
-       var ps state;
-       ps.init(f, env, errors);
-
-       go func() {
-               value := reflect.NewValue(args).(reflect.StructValue);
-               for i := 0; i < value.Len(); i++ {
-                       fld := value.Field(i);
-                       ps.printf(&sw, ps.getFormat(typename(fld)), fld, 0);
-               }
-               errors <- nil;  // no errors
-       }();
-
-       return sw.written, <-errors;
-}
-
-
-// Print formats each argument according to the format f
-// and writes to standard output. The result is the total
-// number of bytes written and an os.Error, if any.
-//
-func (f Format) Print(args ...) (int, os.Error) {
-       return f.Fprint(os.Stdout, nil, args);
-}
-
-
-// Sprint formats each argument according to the format f
-// and returns the resulting string. If an error occurs
-// during formatting, the result contains the respective
-// error message at the end.
-//
-func (f Format) Sprint(args ...) string {
-       var buf io.ByteBuffer;
-       n, err := f.Fprint(&buf, nil, args);
-       if err != nil {
-               fmt.Fprintf(&buf, "--- Sprint(%v) failed: %v", args, err);
-       }
-       return string(buf.Data());
-}
diff --git a/usr/gri/pretty/format_test.go b/usr/gri/pretty/format_test.go
deleted file mode 100644 (file)
index 2add36f..0000000
+++ /dev/null
@@ -1,153 +0,0 @@
-// Copyright 2009 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package format
-
-import (
-       "format";
-       "io";
-       "testing";
-)
-
-
-func check(t *testing.T, form, expected string, args ...) {
-       f, err := format.Parse(io.StringBytes(form), nil);
-       if err != nil {
-               panic(form + ": " + err.String());
-       }
-       result := f.Sprint(args);
-       if result != expected {
-               t.Errorf(
-                       "format  : %s\nresult  : `%s`\nexpected: `%s`\n\n",
-                       form, result, expected
-               )
-       }
-}
-
-
-// ----------------------------------------------------------------------------
-// Syntax
-
-func TestA(t *testing.T) {
-       // TODO fill this in
-}
-
-
-// ----------------------------------------------------------------------------
-// - formatting of basic types
-
-func Test0(t *testing.T) {
-       check(t, `bool = "%v"`, "false", false);
-       check(t, `int = "%b %d %o 0x%x"`, "101010 42 52 0x2a", 42);
-}
-
-
-// ----------------------------------------------------------------------------
-// - formatting of a struct
-
-type T1 struct {
-       a int;
-}
-
-const F1 =
-       `format "format";`
-       `int = "%d";`
-       `format.T1 = "<" a ">";`
-
-func Test1(t *testing.T) {
-       check(t, F1, "<42>", T1{42});
-}
-
-
-// ----------------------------------------------------------------------------
-// - formatting of a struct with an optional field (pointer)
-
-type T2 struct {
-       s string;
-       p *T1;
-}
-
-const F2a =
-       F1 +
-       `string = "%s";`
-       `pointer = *;`
-       `format.T2 = s ["-" p "-"];`
-       
-const F2b =
-       F1 +
-       `string = "%s";`
-       `pointer = *;`
-       `format.T2 = s ("-" p "-" | "empty");`;
-       
-func Test2(t *testing.T) {
-       check(t, F2a, "foo", T2{"foo", nil});
-       check(t, F2a, "bar-<17>-", T2{"bar", &T1{17}});
-       check(t, F2b, "fooempty", T2{"foo", nil});
-}
-
-
-// ----------------------------------------------------------------------------
-// - formatting of a struct with a repetitive field (slice)
-
-type T3 struct {
-       s string;
-       a []int;
-}
-
-const F3a =
-       `format "format";`
-       `default = "%v";`
-       `array = *;`
-       `format.T3 = s  {" " a a / ","};`
-
-const F3b =
-       `format "format";`
-       `int = "%d";`
-       `string = "%s";`
-       `array = *;`
-       `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, "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 =
-       `format "format";`
-       `int = "%d";`
-       `pointer = *;`
-       `array = *;`
-       `nil = ;`
-       `empty = *:nil;`
-       `format.T4 = "<" (x:empty x | "-") ">" `
-
-const F4b =
-       `format "format";`
-       `int = "%d";`
-       `pointer = *;`
-       `array = *;`
-       `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 dfad3005107b8de8187f1a72e9384a60631ec922..9916f5babb648f794b17fb5499dbe3e55bd11380 100644 (file)
@@ -70,31 +70,67 @@ func makeTabwriter(writer io.Writer) *tabwriter.Writer {
 }
 
 
-func isValidPos(w io.Writer, env, value interface{}, name string) bool {
+func isValidPos(state *format.State, value interface{}, rule_name string) bool {
        pos := value.(token.Position);
        return pos.IsValid();
 }
 
 
-func isSend(w io.Writer, env, value interface{}, name string) bool {
+func isSend(state *format.State, value interface{}, rule_name string) bool {
        return value.(ast.ChanDir) & ast.SEND != 0;
 }
 
 
-func isRecv(w io.Writer, env, value interface{}, name string) bool {
+func isRecv(state *format.State, value interface{}, rule_name string) bool {
        return value.(ast.ChanDir) & ast.RECV != 0;
 }
 
-func isMultiLineComment(w io.Writer, env, value interface{}, name string) bool {
-       return value.([]byte)[1] == '*'
+
+func isMultiLineComment(state *format.State, value interface{}, rule_name string) bool {
+       return value.([]byte)[1] == '*';
+}
+
+
+type environment struct {
+       optSemi *bool;
+}
+
+
+func (e environment) Copy() format.Environment {
+       optSemi := *e.optSemi;
+       return environment{&optSemi};
+}
+
+
+func clearOptSemi(state *format.State, value interface{}, rule_name string) bool {
+       *state.Env().(environment).optSemi = false;
+       return true;
 }
 
 
-var fmap = format.FormatterMap{
+func setOptSemi(state *format.State, value interface{}, rule_name string) bool {
+       *state.Env().(environment).optSemi = true;
+       return true;
+}
+
+
+func optSemi(state *format.State, value interface{}, rule_name string) bool {
+       if !*state.Env().(environment).optSemi {
+               state.Write([]byte{';'});
+       }
+       return true;
+}
+
+
+var fmap = format.FormatterMap {
        "isValidPos": isValidPos,
        "isSend": isSend,
        "isRecv": isRecv,
        "isMultiLineComment": isMultiLineComment,
+       "/": clearOptSemi,
+       "clearOptSemi": clearOptSemi,
+       "setOptSemi": setOptSemi,
+       "optSemi": optSemi,
 }
 
 
@@ -120,7 +156,7 @@ func main() {
        }
        ast_format, err := format.Parse(src, fmap);
        if err != nil {
-               fmt.Fprintf(os.Stderr, "%s:%v\n", ast_txt, err);
+               fmt.Fprintf(os.Stderr, "%s: %v\n", ast_txt, err);
                os.Exit(1);
        }
 
@@ -153,10 +189,10 @@ func main() {
                if !*silent {
                        tw := makeTabwriter(os.Stdout);
                        if *formatter {
-                               var optSemi bool;  // formatting environment
-                               _, err := ast_format.Fprint(tw, &optSemi, prog);
+                               env := environment{new(bool)};
+                               _, err := ast_format.Fprint(tw, env, prog);
                                if err != nil {
-                                       fmt.Fprintf(os.Stderr, "format error$$: %s", err);
+                                       fmt.Fprintf(os.Stderr, "format error: %v\n", err);
                                        exitcode = 1;
                                        continue;  // proceed with next file
                                }