gopkg.in/alecthomas/gometalinter.v3@v3.0.0/_linters/src/golang.org/x/text/internal/gen/code.go (about)

     1  // Copyright 2015 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package gen
     6  
     7  import (
     8  	"bytes"
     9  	"encoding/gob"
    10  	"fmt"
    11  	"hash"
    12  	"hash/fnv"
    13  	"io"
    14  	"log"
    15  	"os"
    16  	"reflect"
    17  	"strings"
    18  	"unicode"
    19  	"unicode/utf8"
    20  )
    21  
    22  // This file contains utilities for generating code.
    23  
    24  // TODO: other write methods like:
    25  // - slices, maps, types, etc.
    26  
    27  // CodeWriter is a utility for writing structured code. It computes the content
    28  // hash and size of written content. It ensures there are newlines between
    29  // written code blocks.
    30  type CodeWriter struct {
    31  	buf  bytes.Buffer
    32  	Size int
    33  	Hash hash.Hash32 // content hash
    34  	gob  *gob.Encoder
    35  	// For comments we skip the usual one-line separator if they are followed by
    36  	// a code block.
    37  	skipSep bool
    38  }
    39  
    40  func (w *CodeWriter) Write(p []byte) (n int, err error) {
    41  	return w.buf.Write(p)
    42  }
    43  
    44  // NewCodeWriter returns a new CodeWriter.
    45  func NewCodeWriter() *CodeWriter {
    46  	h := fnv.New32()
    47  	return &CodeWriter{Hash: h, gob: gob.NewEncoder(h)}
    48  }
    49  
    50  // WriteGoFile appends the buffer with the total size of all created structures
    51  // and writes it as a Go file to the the given file with the given package name.
    52  func (w *CodeWriter) WriteGoFile(filename, pkg string) {
    53  	f, err := os.Create(filename)
    54  	if err != nil {
    55  		log.Fatalf("Could not create file %s: %v", filename, err)
    56  	}
    57  	defer f.Close()
    58  	if _, err = w.WriteGo(f, pkg); err != nil {
    59  		log.Fatalf("Error writing file %s: %v", filename, err)
    60  	}
    61  }
    62  
    63  // WriteGo appends the buffer with the total size of all created structures and
    64  // writes it as a Go file to the the given writer with the given package name.
    65  func (w *CodeWriter) WriteGo(out io.Writer, pkg string) (n int, err error) {
    66  	sz := w.Size
    67  	w.WriteComment("Total table size %d bytes (%dKiB); checksum: %X\n", sz, sz/1024, w.Hash.Sum32())
    68  	defer w.buf.Reset()
    69  	return WriteGo(out, pkg, w.buf.Bytes())
    70  }
    71  
    72  func (w *CodeWriter) printf(f string, x ...interface{}) {
    73  	fmt.Fprintf(w, f, x...)
    74  }
    75  
    76  func (w *CodeWriter) insertSep() {
    77  	if w.skipSep {
    78  		w.skipSep = false
    79  		return
    80  	}
    81  	// Use at least two newlines to ensure a blank space between the previous
    82  	// block. WriteGoFile will remove extraneous newlines.
    83  	w.printf("\n\n")
    84  }
    85  
    86  // WriteComment writes a comment block. All line starts are prefixed with "//".
    87  // Initial empty lines are gobbled. The indentation for the first line is
    88  // stripped from consecutive lines.
    89  func (w *CodeWriter) WriteComment(comment string, args ...interface{}) {
    90  	s := fmt.Sprintf(comment, args...)
    91  	s = strings.Trim(s, "\n")
    92  
    93  	// Use at least two newlines to ensure a blank space between the previous
    94  	// block. WriteGoFile will remove extraneous newlines.
    95  	w.printf("\n\n// ")
    96  	w.skipSep = true
    97  
    98  	// strip first indent level.
    99  	sep := "\n"
   100  	for ; len(s) > 0 && (s[0] == '\t' || s[0] == ' '); s = s[1:] {
   101  		sep += s[:1]
   102  	}
   103  
   104  	strings.NewReplacer(sep, "\n// ", "\n", "\n// ").WriteString(w, s)
   105  
   106  	w.printf("\n")
   107  }
   108  
   109  func (w *CodeWriter) writeSizeInfo(size int) {
   110  	w.printf("// Size: %d bytes\n", size)
   111  }
   112  
   113  // WriteConst writes a constant of the given name and value.
   114  func (w *CodeWriter) WriteConst(name string, x interface{}) {
   115  	w.insertSep()
   116  	v := reflect.ValueOf(x)
   117  
   118  	switch v.Type().Kind() {
   119  	case reflect.String:
   120  		w.printf("const %s %s = ", name, typeName(x))
   121  		w.WriteString(v.String())
   122  		w.printf("\n")
   123  	default:
   124  		w.printf("const %s = %#v\n", name, x)
   125  	}
   126  }
   127  
   128  // WriteVar writes a variable of the given name and value.
   129  func (w *CodeWriter) WriteVar(name string, x interface{}) {
   130  	w.insertSep()
   131  	v := reflect.ValueOf(x)
   132  	oldSize := w.Size
   133  	sz := int(v.Type().Size())
   134  	w.Size += sz
   135  
   136  	switch v.Type().Kind() {
   137  	case reflect.String:
   138  		w.printf("var %s %s = ", name, typeName(x))
   139  		w.WriteString(v.String())
   140  	case reflect.Struct:
   141  		w.gob.Encode(x)
   142  		fallthrough
   143  	case reflect.Slice, reflect.Array:
   144  		w.printf("var %s = ", name)
   145  		w.writeValue(v)
   146  		w.writeSizeInfo(w.Size - oldSize)
   147  	default:
   148  		w.printf("var %s %s = ", name, typeName(x))
   149  		w.gob.Encode(x)
   150  		w.writeValue(v)
   151  		w.writeSizeInfo(w.Size - oldSize)
   152  	}
   153  	w.printf("\n")
   154  }
   155  
   156  func (w *CodeWriter) writeValue(v reflect.Value) {
   157  	x := v.Interface()
   158  	switch v.Kind() {
   159  	case reflect.String:
   160  		w.WriteString(v.String())
   161  	case reflect.Array:
   162  		// Don't double count: callers of WriteArray count on the size being
   163  		// added, so we need to discount it here.
   164  		w.Size -= int(v.Type().Size())
   165  		w.writeSlice(x, true)
   166  	case reflect.Slice:
   167  		w.writeSlice(x, false)
   168  	case reflect.Struct:
   169  		w.printf("%s{\n", typeName(v.Interface()))
   170  		t := v.Type()
   171  		for i := 0; i < v.NumField(); i++ {
   172  			w.printf("%s: ", t.Field(i).Name)
   173  			w.writeValue(v.Field(i))
   174  			w.printf(",\n")
   175  		}
   176  		w.printf("}")
   177  	default:
   178  		w.printf("%#v", x)
   179  	}
   180  }
   181  
   182  // WriteString writes a string literal.
   183  func (w *CodeWriter) WriteString(s string) {
   184  	s = strings.Replace(s, `\`, `\\`, -1)
   185  	io.WriteString(w.Hash, s) // content hash
   186  	w.Size += len(s)
   187  
   188  	const maxInline = 40
   189  	if len(s) <= maxInline {
   190  		w.printf("%q", s)
   191  		return
   192  	}
   193  
   194  	// We will render the string as a multi-line string.
   195  	const maxWidth = 80 - 4 - len(`"`) - len(`" +`)
   196  
   197  	// When starting on its own line, go fmt indents line 2+ an extra level.
   198  	n, max := maxWidth, maxWidth-4
   199  
   200  	// As per https://golang.org/issue/18078, the compiler has trouble
   201  	// compiling the concatenation of many strings, s0 + s1 + s2 + ... + sN,
   202  	// for large N. We insert redundant, explicit parentheses to work around
   203  	// that, lowering the N at any given step: (s0 + s1 + ... + s63) + (s64 +
   204  	// ... + s127) + etc + (etc + ... + sN).
   205  	explicitParens, extraComment := len(s) > 128*1024, ""
   206  	if explicitParens {
   207  		w.printf(`(`)
   208  		extraComment = "; the redundant, explicit parens are for https://golang.org/issue/18078"
   209  	}
   210  
   211  	// Print "" +\n, if a string does not start on its own line.
   212  	b := w.buf.Bytes()
   213  	if p := len(bytes.TrimRight(b, " \t")); p > 0 && b[p-1] != '\n' {
   214  		w.printf("\"\" + // Size: %d bytes%s\n", len(s), extraComment)
   215  		n, max = maxWidth, maxWidth
   216  	}
   217  
   218  	w.printf(`"`)
   219  
   220  	for sz, p, nLines := 0, 0, 0; p < len(s); {
   221  		var r rune
   222  		r, sz = utf8.DecodeRuneInString(s[p:])
   223  		out := s[p : p+sz]
   224  		chars := 1
   225  		if !unicode.IsPrint(r) || r == utf8.RuneError || r == '"' {
   226  			switch sz {
   227  			case 1:
   228  				out = fmt.Sprintf("\\x%02x", s[p])
   229  			case 2, 3:
   230  				out = fmt.Sprintf("\\u%04x", r)
   231  			case 4:
   232  				out = fmt.Sprintf("\\U%08x", r)
   233  			}
   234  			chars = len(out)
   235  		}
   236  		if n -= chars; n < 0 {
   237  			nLines++
   238  			if explicitParens && nLines&63 == 63 {
   239  				w.printf("\") + (\"")
   240  			}
   241  			w.printf("\" +\n\"")
   242  			n = max - len(out)
   243  		}
   244  		w.printf("%s", out)
   245  		p += sz
   246  	}
   247  	w.printf(`"`)
   248  	if explicitParens {
   249  		w.printf(`)`)
   250  	}
   251  }
   252  
   253  // WriteSlice writes a slice value.
   254  func (w *CodeWriter) WriteSlice(x interface{}) {
   255  	w.writeSlice(x, false)
   256  }
   257  
   258  // WriteArray writes an array value.
   259  func (w *CodeWriter) WriteArray(x interface{}) {
   260  	w.writeSlice(x, true)
   261  }
   262  
   263  func (w *CodeWriter) writeSlice(x interface{}, isArray bool) {
   264  	v := reflect.ValueOf(x)
   265  	w.gob.Encode(v.Len())
   266  	w.Size += v.Len() * int(v.Type().Elem().Size())
   267  	name := typeName(x)
   268  	if isArray {
   269  		name = fmt.Sprintf("[%d]%s", v.Len(), name[strings.Index(name, "]")+1:])
   270  	}
   271  	if isArray {
   272  		w.printf("%s{\n", name)
   273  	} else {
   274  		w.printf("%s{ // %d elements\n", name, v.Len())
   275  	}
   276  
   277  	switch kind := v.Type().Elem().Kind(); kind {
   278  	case reflect.String:
   279  		for _, s := range x.([]string) {
   280  			w.WriteString(s)
   281  			w.printf(",\n")
   282  		}
   283  	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
   284  		reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
   285  		// nLine and nBlock are the number of elements per line and block.
   286  		nLine, nBlock, format := 8, 64, "%d,"
   287  		switch kind {
   288  		case reflect.Uint8:
   289  			format = "%#02x,"
   290  		case reflect.Uint16:
   291  			format = "%#04x,"
   292  		case reflect.Uint32:
   293  			nLine, nBlock, format = 4, 32, "%#08x,"
   294  		case reflect.Uint, reflect.Uint64:
   295  			nLine, nBlock, format = 4, 32, "%#016x,"
   296  		case reflect.Int8:
   297  			nLine = 16
   298  		}
   299  		n := nLine
   300  		for i := 0; i < v.Len(); i++ {
   301  			if i%nBlock == 0 && v.Len() > nBlock {
   302  				w.printf("// Entry %X - %X\n", i, i+nBlock-1)
   303  			}
   304  			x := v.Index(i).Interface()
   305  			w.gob.Encode(x)
   306  			w.printf(format, x)
   307  			if n--; n == 0 {
   308  				n = nLine
   309  				w.printf("\n")
   310  			}
   311  		}
   312  		w.printf("\n")
   313  	case reflect.Struct:
   314  		zero := reflect.Zero(v.Type().Elem()).Interface()
   315  		for i := 0; i < v.Len(); i++ {
   316  			x := v.Index(i).Interface()
   317  			w.gob.EncodeValue(v)
   318  			if !reflect.DeepEqual(zero, x) {
   319  				line := fmt.Sprintf("%#v,\n", x)
   320  				line = line[strings.IndexByte(line, '{'):]
   321  				w.printf("%d: ", i)
   322  				w.printf(line)
   323  			}
   324  		}
   325  	case reflect.Array:
   326  		for i := 0; i < v.Len(); i++ {
   327  			w.printf("%d: %#v,\n", i, v.Index(i).Interface())
   328  		}
   329  	default:
   330  		panic("gen: slice elem type not supported")
   331  	}
   332  	w.printf("}")
   333  }
   334  
   335  // WriteType writes a definition of the type of the given value and returns the
   336  // type name.
   337  func (w *CodeWriter) WriteType(x interface{}) string {
   338  	t := reflect.TypeOf(x)
   339  	w.printf("type %s struct {\n", t.Name())
   340  	for i := 0; i < t.NumField(); i++ {
   341  		w.printf("\t%s %s\n", t.Field(i).Name, t.Field(i).Type)
   342  	}
   343  	w.printf("}\n")
   344  	return t.Name()
   345  }
   346  
   347  // typeName returns the name of the go type of x.
   348  func typeName(x interface{}) string {
   349  	t := reflect.ValueOf(x).Type()
   350  	return strings.Replace(fmt.Sprint(t), "main.", "", 1)
   351  }