...

Source file src/golang.org/x/text/internal/gen/code.go

Documentation: golang.org/x/text/internal/gen

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

View as plain text