github.com/jhump/protocompile@v0.0.0-20221021153901-4f6f732835e8/sourceinfo/source_code_info.go (about)

     1  // Package sourceinfo contains the logic for computing source code info for a
     2  // file descriptor.
     3  //
     4  // The inputs to the computation are an AST for a file as well as the index of
     5  // interpreted options for that file.
     6  package sourceinfo
     7  
     8  import (
     9  	"bytes"
    10  	"strings"
    11  
    12  	"google.golang.org/protobuf/proto"
    13  	"google.golang.org/protobuf/types/descriptorpb"
    14  
    15  	"github.com/jhump/protocompile/ast"
    16  	"github.com/jhump/protocompile/internal"
    17  	"github.com/jhump/protocompile/options"
    18  )
    19  
    20  // GenerateSourceInfo generates source code info for the given AST. If the given
    21  // opts is present, it can generate source code info for interpreted options.
    22  // Otherwise, any options in the AST will get source code info as uninterpreted
    23  // options.
    24  func GenerateSourceInfo(file *ast.FileNode, opts options.Index) *descriptorpb.SourceCodeInfo {
    25  	if file == nil {
    26  		return nil
    27  	}
    28  
    29  	sci := sourceCodeInfo{file: file, commentsUsed: map[ast.SourcePos]struct{}{}}
    30  	path := make([]int32, 0, 10)
    31  
    32  	sci.newLocWithoutComments(file, nil)
    33  
    34  	if file.Syntax != nil {
    35  		sci.newLoc(file.Syntax, append(path, internal.File_syntaxTag))
    36  	}
    37  
    38  	var depIndex, optIndex, msgIndex, enumIndex, extendIndex, svcIndex int32
    39  
    40  	for _, child := range file.Decls {
    41  		switch child := child.(type) {
    42  		case *ast.ImportNode:
    43  			sci.newLoc(child, append(path, internal.File_dependencyTag, depIndex))
    44  			depIndex++
    45  		case *ast.PackageNode:
    46  			sci.newLoc(child, append(path, internal.File_packageTag))
    47  		case *ast.OptionNode:
    48  			generateSourceCodeInfoForOption(opts, &sci, child, false, &optIndex, append(path, internal.File_optionsTag))
    49  		case *ast.MessageNode:
    50  			generateSourceCodeInfoForMessage(opts, &sci, child, nil, append(path, internal.File_messagesTag, msgIndex))
    51  			msgIndex++
    52  		case *ast.EnumNode:
    53  			generateSourceCodeInfoForEnum(opts, &sci, child, append(path, internal.File_enumsTag, enumIndex))
    54  			enumIndex++
    55  		case *ast.ExtendNode:
    56  			generateSourceCodeInfoForExtensions(opts, &sci, child, &extendIndex, &msgIndex, append(path, internal.File_extensionsTag), append(dup(path), internal.File_messagesTag))
    57  		case *ast.ServiceNode:
    58  			generateSourceCodeInfoForService(opts, &sci, child, append(path, internal.File_servicesTag, svcIndex))
    59  			svcIndex++
    60  		}
    61  	}
    62  
    63  	return &descriptorpb.SourceCodeInfo{Location: sci.locs}
    64  }
    65  
    66  func generateSourceCodeInfoForOption(opts options.Index, sci *sourceCodeInfo, n *ast.OptionNode, compact bool, uninterpIndex *int32, path []int32) {
    67  	if !compact {
    68  		sci.newLocWithoutComments(n, path)
    69  	}
    70  	subPath := opts[n]
    71  	if len(subPath) > 0 {
    72  		p := path
    73  		if subPath[0] == -1 {
    74  			// used by "default" and "json_name" field pseudo-options
    75  			// to attribute path to parent element (since those are
    76  			// stored directly on the descriptor, not its options)
    77  			p = make([]int32, len(path)-1)
    78  			copy(p, path)
    79  			subPath = subPath[1:]
    80  		}
    81  		sci.newLoc(n, append(p, subPath...))
    82  		return
    83  	}
    84  
    85  	// it's an uninterpreted option
    86  	optPath := append(path, internal.UninterpretedOptionsTag, *uninterpIndex)
    87  	*uninterpIndex++
    88  	sci.newLoc(n, optPath)
    89  	var valTag int32
    90  	switch n.Val.(type) {
    91  	case ast.IdentValueNode:
    92  		valTag = internal.Uninterpreted_identTag
    93  	case *ast.NegativeIntLiteralNode:
    94  		valTag = internal.Uninterpreted_negIntTag
    95  	case ast.IntValueNode:
    96  		valTag = internal.Uninterpreted_posIntTag
    97  	case ast.FloatValueNode:
    98  		valTag = internal.Uninterpreted_doubleTag
    99  	case ast.StringValueNode:
   100  		valTag = internal.Uninterpreted_stringTag
   101  	case *ast.MessageLiteralNode:
   102  		valTag = internal.Uninterpreted_aggregateTag
   103  	}
   104  	if valTag != 0 {
   105  		sci.newLoc(n.Val, append(optPath, valTag))
   106  	}
   107  	for j, nn := range n.Name.Parts {
   108  		optNmPath := append(optPath, internal.Uninterpreted_nameTag, int32(j))
   109  		sci.newLoc(nn, optNmPath)
   110  		sci.newLoc(nn.Name, append(optNmPath, internal.UninterpretedName_nameTag))
   111  	}
   112  }
   113  
   114  func generateSourceCodeInfoForMessage(opts options.Index, sci *sourceCodeInfo, n ast.MessageDeclNode, fieldPath []int32, path []int32) {
   115  	sci.newLoc(n, path)
   116  
   117  	var decls []ast.MessageElement
   118  	switch n := n.(type) {
   119  	case *ast.MessageNode:
   120  		decls = n.Decls
   121  	case *ast.GroupNode:
   122  		decls = n.Decls
   123  	case *ast.MapFieldNode:
   124  		// map entry so nothing else to do
   125  		return
   126  	}
   127  
   128  	sci.newLoc(n.MessageName(), append(path, internal.Message_nameTag))
   129  	// matching protoc, which emits the corresponding field type name (for group fields)
   130  	// right after the source location for the group message name
   131  	if fieldPath != nil {
   132  		sci.newLoc(n.MessageName(), append(fieldPath, internal.Field_typeNameTag))
   133  	}
   134  
   135  	var optIndex, fieldIndex, oneOfIndex, extendIndex, nestedMsgIndex int32
   136  	var nestedEnumIndex, extRangeIndex, reservedRangeIndex, reservedNameIndex int32
   137  	for _, child := range decls {
   138  		switch child := child.(type) {
   139  		case *ast.OptionNode:
   140  			generateSourceCodeInfoForOption(opts, sci, child, false, &optIndex, append(path, internal.Message_optionsTag))
   141  		case *ast.FieldNode:
   142  			generateSourceCodeInfoForField(opts, sci, child, append(path, internal.Message_fieldsTag, fieldIndex))
   143  			fieldIndex++
   144  		case *ast.GroupNode:
   145  			fldPath := append(path, internal.Message_fieldsTag, fieldIndex)
   146  			generateSourceCodeInfoForField(opts, sci, child, fldPath)
   147  			fieldIndex++
   148  			generateSourceCodeInfoForMessage(opts, sci, child, fldPath, append(dup(path), internal.Message_nestedMessagesTag, nestedMsgIndex))
   149  			nestedMsgIndex++
   150  		case *ast.MapFieldNode:
   151  			generateSourceCodeInfoForField(opts, sci, child, append(path, internal.Message_fieldsTag, fieldIndex))
   152  			fieldIndex++
   153  			nestedMsgIndex++
   154  		case *ast.OneOfNode:
   155  			generateSourceCodeInfoForOneOf(opts, sci, child, &fieldIndex, &nestedMsgIndex, append(path, internal.Message_fieldsTag), append(dup(path), internal.Message_nestedMessagesTag), append(dup(path), internal.Message_oneOfsTag, oneOfIndex))
   156  			oneOfIndex++
   157  		case *ast.MessageNode:
   158  			generateSourceCodeInfoForMessage(opts, sci, child, nil, append(path, internal.Message_nestedMessagesTag, nestedMsgIndex))
   159  			nestedMsgIndex++
   160  		case *ast.EnumNode:
   161  			generateSourceCodeInfoForEnum(opts, sci, child, append(path, internal.Message_enumsTag, nestedEnumIndex))
   162  			nestedEnumIndex++
   163  		case *ast.ExtendNode:
   164  			generateSourceCodeInfoForExtensions(opts, sci, child, &extendIndex, &nestedMsgIndex, append(path, internal.Message_extensionsTag), append(dup(path), internal.Message_nestedMessagesTag))
   165  		case *ast.ExtensionRangeNode:
   166  			generateSourceCodeInfoForExtensionRanges(opts, sci, child, &extRangeIndex, append(path, internal.Message_extensionRangeTag))
   167  		case *ast.ReservedNode:
   168  			if len(child.Names) > 0 {
   169  				resPath := append(path, internal.Message_reservedNameTag)
   170  				sci.newLoc(child, resPath)
   171  				for _, rn := range child.Names {
   172  					sci.newLoc(rn, append(resPath, reservedNameIndex))
   173  					reservedNameIndex++
   174  				}
   175  			}
   176  			if len(child.Ranges) > 0 {
   177  				resPath := append(path, internal.Message_reservedRangeTag)
   178  				sci.newLoc(child, resPath)
   179  				for _, rr := range child.Ranges {
   180  					generateSourceCodeInfoForReservedRange(sci, rr, append(resPath, reservedRangeIndex))
   181  					reservedRangeIndex++
   182  				}
   183  			}
   184  		}
   185  	}
   186  }
   187  
   188  func generateSourceCodeInfoForEnum(opts options.Index, sci *sourceCodeInfo, n *ast.EnumNode, path []int32) {
   189  	sci.newLoc(n, path)
   190  	sci.newLoc(n.Name, append(path, internal.Enum_nameTag))
   191  
   192  	var optIndex, valIndex, reservedNameIndex, reservedRangeIndex int32
   193  	for _, child := range n.Decls {
   194  		switch child := child.(type) {
   195  		case *ast.OptionNode:
   196  			generateSourceCodeInfoForOption(opts, sci, child, false, &optIndex, append(path, internal.Enum_optionsTag))
   197  		case *ast.EnumValueNode:
   198  			generateSourceCodeInfoForEnumValue(opts, sci, child, append(path, internal.Enum_valuesTag, valIndex))
   199  			valIndex++
   200  		case *ast.ReservedNode:
   201  			if len(child.Names) > 0 {
   202  				resPath := append(path, internal.Enum_reservedNameTag)
   203  				sci.newLoc(child, resPath)
   204  				for _, rn := range child.Names {
   205  					sci.newLoc(rn, append(resPath, reservedNameIndex))
   206  					reservedNameIndex++
   207  				}
   208  			}
   209  			if len(child.Ranges) > 0 {
   210  				resPath := append(path, internal.Enum_reservedRangeTag)
   211  				sci.newLoc(child, resPath)
   212  				for _, rr := range child.Ranges {
   213  					generateSourceCodeInfoForReservedRange(sci, rr, append(resPath, reservedRangeIndex))
   214  					reservedRangeIndex++
   215  				}
   216  			}
   217  		}
   218  	}
   219  }
   220  
   221  func generateSourceCodeInfoForEnumValue(opts options.Index, sci *sourceCodeInfo, n *ast.EnumValueNode, path []int32) {
   222  	sci.newLoc(n, path)
   223  	sci.newLoc(n.Name, append(path, internal.EnumVal_nameTag))
   224  	sci.newLoc(n.Number, append(path, internal.EnumVal_numberTag))
   225  
   226  	// enum value options
   227  	if n.Options != nil {
   228  		optsPath := append(path, internal.EnumVal_optionsTag)
   229  		sci.newLoc(n.Options, optsPath)
   230  		var optIndex int32
   231  		for _, opt := range n.Options.GetElements() {
   232  			generateSourceCodeInfoForOption(opts, sci, opt, true, &optIndex, optsPath)
   233  		}
   234  	}
   235  }
   236  
   237  func generateSourceCodeInfoForReservedRange(sci *sourceCodeInfo, n *ast.RangeNode, path []int32) {
   238  	sci.newLoc(n, path)
   239  	sci.newLoc(n.StartVal, append(path, internal.ReservedRange_startTag))
   240  	if n.EndVal != nil {
   241  		sci.newLoc(n.EndVal, append(path, internal.ReservedRange_endTag))
   242  	} else if n.Max != nil {
   243  		sci.newLoc(n.Max, append(path, internal.ReservedRange_endTag))
   244  	}
   245  }
   246  
   247  func generateSourceCodeInfoForExtensions(opts options.Index, sci *sourceCodeInfo, n *ast.ExtendNode, extendIndex, msgIndex *int32, extendPath, msgPath []int32) {
   248  	sci.newLoc(n, extendPath)
   249  	for _, decl := range n.Decls {
   250  		switch decl := decl.(type) {
   251  		case *ast.FieldNode:
   252  			generateSourceCodeInfoForField(opts, sci, decl, append(extendPath, *extendIndex))
   253  			*extendIndex++
   254  		case *ast.GroupNode:
   255  			fldPath := append(extendPath, *extendIndex)
   256  			generateSourceCodeInfoForField(opts, sci, decl, fldPath)
   257  			*extendIndex++
   258  			generateSourceCodeInfoForMessage(opts, sci, decl, fldPath, append(msgPath, *msgIndex))
   259  			*msgIndex++
   260  		}
   261  	}
   262  }
   263  
   264  func generateSourceCodeInfoForOneOf(opts options.Index, sci *sourceCodeInfo, n *ast.OneOfNode, fieldIndex, nestedMsgIndex *int32, fieldPath, nestedMsgPath, oneOfPath []int32) {
   265  	sci.newLoc(n, oneOfPath)
   266  	sci.newLoc(n.Name, append(oneOfPath, internal.OneOf_nameTag))
   267  
   268  	var optIndex int32
   269  	for _, child := range n.Decls {
   270  		switch child := child.(type) {
   271  		case *ast.OptionNode:
   272  			generateSourceCodeInfoForOption(opts, sci, child, false, &optIndex, append(oneOfPath, internal.OneOf_optionsTag))
   273  		case *ast.FieldNode:
   274  			generateSourceCodeInfoForField(opts, sci, child, append(fieldPath, *fieldIndex))
   275  			*fieldIndex++
   276  		case *ast.GroupNode:
   277  			fldPath := append(fieldPath, *fieldIndex)
   278  			generateSourceCodeInfoForField(opts, sci, child, fldPath)
   279  			*fieldIndex++
   280  			generateSourceCodeInfoForMessage(opts, sci, child, fldPath, append(nestedMsgPath, *nestedMsgIndex))
   281  			*nestedMsgIndex++
   282  		}
   283  	}
   284  }
   285  
   286  func generateSourceCodeInfoForField(opts options.Index, sci *sourceCodeInfo, n ast.FieldDeclNode, path []int32) {
   287  	var fieldType string
   288  	if f, ok := n.(*ast.FieldNode); ok {
   289  		fieldType = string(f.FldType.AsIdentifier())
   290  	}
   291  
   292  	if n.GetGroupKeyword() != nil {
   293  		// comments will appear on group message
   294  		sci.newLocWithoutComments(n, path)
   295  		if n.FieldExtendee() != nil {
   296  			sci.newLoc(n.FieldExtendee(), append(path, internal.Field_extendeeTag))
   297  		}
   298  		if n.FieldLabel() != nil {
   299  			// no comments here either (label is first token for group, so we want
   300  			// to leave the comments to be associated with the group message instead)
   301  			sci.newLocWithoutComments(n.FieldLabel(), append(path, internal.Field_labelTag))
   302  		}
   303  		sci.newLoc(n.FieldType(), append(path, internal.Field_typeTag))
   304  		// let the name comments be attributed to the group name
   305  		sci.newLocWithoutComments(n.FieldName(), append(path, internal.Field_nameTag))
   306  	} else {
   307  		sci.newLoc(n, path)
   308  		if n.FieldExtendee() != nil {
   309  			sci.newLoc(n.FieldExtendee(), append(path, internal.Field_extendeeTag))
   310  		}
   311  		if n.FieldLabel() != nil {
   312  			sci.newLoc(n.FieldLabel(), append(path, internal.Field_labelTag))
   313  		}
   314  		var tag int32
   315  		if _, isScalar := internal.FieldTypes[fieldType]; isScalar {
   316  			tag = internal.Field_typeTag
   317  		} else {
   318  			// this is a message or an enum, so attribute type location
   319  			// to the type name field
   320  			tag = internal.Field_typeNameTag
   321  		}
   322  		sci.newLoc(n.FieldType(), append(path, tag))
   323  		sci.newLoc(n.FieldName(), append(path, internal.Field_nameTag))
   324  	}
   325  	sci.newLoc(n.FieldTag(), append(path, internal.Field_numberTag))
   326  
   327  	if n.GetOptions() != nil {
   328  		optsPath := append(path, internal.Field_optionsTag)
   329  		sci.newLoc(n.GetOptions(), optsPath)
   330  		var optIndex int32
   331  		for _, opt := range n.GetOptions().GetElements() {
   332  			generateSourceCodeInfoForOption(opts, sci, opt, true, &optIndex, optsPath)
   333  		}
   334  	}
   335  }
   336  
   337  func generateSourceCodeInfoForExtensionRanges(opts options.Index, sci *sourceCodeInfo, n *ast.ExtensionRangeNode, extRangeIndex *int32, path []int32) {
   338  	sci.newLoc(n, path)
   339  	for _, child := range n.Ranges {
   340  		path := append(path, *extRangeIndex)
   341  		*extRangeIndex++
   342  		sci.newLoc(child, path)
   343  		sci.newLoc(child.StartVal, append(path, internal.ExtensionRange_startTag))
   344  		if child.EndVal != nil {
   345  			sci.newLoc(child.EndVal, append(path, internal.ExtensionRange_endTag))
   346  		} else if child.Max != nil {
   347  			sci.newLoc(child.Max, append(path, internal.ExtensionRange_endTag))
   348  		}
   349  		if n.Options != nil {
   350  			optsPath := append(path, internal.ExtensionRange_optionsTag)
   351  			sci.newLoc(n.Options, optsPath)
   352  			var optIndex int32
   353  			for _, opt := range n.Options.GetElements() {
   354  				generateSourceCodeInfoForOption(opts, sci, opt, true, &optIndex, optsPath)
   355  			}
   356  		}
   357  	}
   358  }
   359  
   360  func generateSourceCodeInfoForService(opts options.Index, sci *sourceCodeInfo, n *ast.ServiceNode, path []int32) {
   361  	sci.newLoc(n, path)
   362  	sci.newLoc(n.Name, append(path, internal.Service_nameTag))
   363  	var optIndex, rpcIndex int32
   364  	for _, child := range n.Decls {
   365  		switch child := child.(type) {
   366  		case *ast.OptionNode:
   367  			generateSourceCodeInfoForOption(opts, sci, child, false, &optIndex, append(path, internal.Service_optionsTag))
   368  		case *ast.RPCNode:
   369  			generateSourceCodeInfoForMethod(opts, sci, child, append(path, internal.Service_methodsTag, rpcIndex))
   370  			rpcIndex++
   371  		}
   372  	}
   373  }
   374  
   375  func generateSourceCodeInfoForMethod(opts options.Index, sci *sourceCodeInfo, n *ast.RPCNode, path []int32) {
   376  	sci.newLoc(n, path)
   377  	sci.newLoc(n.Name, append(path, internal.Method_nameTag))
   378  	if n.Input.Stream != nil {
   379  		sci.newLoc(n.Input.Stream, append(path, internal.Method_inputStreamTag))
   380  	}
   381  	sci.newLoc(n.Input.MessageType, append(path, internal.Method_inputTag))
   382  	if n.Output.Stream != nil {
   383  		sci.newLoc(n.Output.Stream, append(path, internal.Method_outputStreamTag))
   384  	}
   385  	sci.newLoc(n.Output.MessageType, append(path, internal.Method_outputTag))
   386  
   387  	optsPath := append(path, internal.Method_optionsTag)
   388  	var optIndex int32
   389  	for _, decl := range n.Decls {
   390  		if opt, ok := decl.(*ast.OptionNode); ok {
   391  			generateSourceCodeInfoForOption(opts, sci, opt, false, &optIndex, optsPath)
   392  		}
   393  	}
   394  }
   395  
   396  type sourceCodeInfo struct {
   397  	file         *ast.FileNode
   398  	locs         []*descriptorpb.SourceCodeInfo_Location
   399  	commentsUsed map[ast.SourcePos]struct{}
   400  }
   401  
   402  func (sci *sourceCodeInfo) newLocWithoutComments(n ast.Node, path []int32) {
   403  	dup := make([]int32, len(path))
   404  	copy(dup, path)
   405  	info := sci.file.NodeInfo(n)
   406  	sci.locs = append(sci.locs, &descriptorpb.SourceCodeInfo_Location{
   407  		Path: dup,
   408  		Span: makeSpan(info.Start(), info.End()),
   409  	})
   410  }
   411  
   412  func (sci *sourceCodeInfo) newLoc(n ast.Node, path []int32) {
   413  	nodeInfo := sci.file.NodeInfo(n)
   414  	leadingComments := nodeInfo.LeadingComments()
   415  	trailingComments := nodeInfo.TrailingComments()
   416  	if sci.commentUsed(leadingComments) {
   417  		leadingComments = ast.Comments{}
   418  	}
   419  	if sci.commentUsed(trailingComments) {
   420  		trailingComments = ast.Comments{}
   421  	}
   422  	detached := groupComments(leadingComments)
   423  	var trail *string
   424  	if str, ok := combineComments(trailingComments, 0, trailingComments.Len()); ok {
   425  		trail = proto.String(str)
   426  	}
   427  	var lead *string
   428  	if leadingComments.Len() > 0 && leadingComments.Index(leadingComments.Len()-1).End().Line >= nodeInfo.Start().Line-1 {
   429  		lead = proto.String(detached[len(detached)-1])
   430  		detached = detached[:len(detached)-1]
   431  	}
   432  	dup := make([]int32, len(path))
   433  	copy(dup, path)
   434  	sci.locs = append(sci.locs, &descriptorpb.SourceCodeInfo_Location{
   435  		LeadingDetachedComments: detached,
   436  		LeadingComments:         lead,
   437  		TrailingComments:        trail,
   438  		Path:                    dup,
   439  		Span:                    makeSpan(nodeInfo.Start(), nodeInfo.End()),
   440  	})
   441  }
   442  
   443  func makeSpan(start, end ast.SourcePos) []int32 {
   444  	if start.Line == end.Line {
   445  		return []int32{int32(start.Line) - 1, int32(start.Col) - 1, int32(end.Col) - 1}
   446  	}
   447  	return []int32{int32(start.Line) - 1, int32(start.Col) - 1, int32(end.Line) - 1, int32(end.Col) - 1}
   448  }
   449  
   450  func (sci *sourceCodeInfo) commentUsed(c ast.Comments) bool {
   451  	if c.Len() == 0 {
   452  		return false
   453  	}
   454  	pos := c.Index(0).Start()
   455  	if _, ok := sci.commentsUsed[pos]; ok {
   456  		return true
   457  	}
   458  
   459  	sci.commentsUsed[pos] = struct{}{}
   460  	return false
   461  }
   462  
   463  func groupComments(comments ast.Comments) []string {
   464  	if comments.Len() == 0 {
   465  		return nil
   466  	}
   467  
   468  	var groups []string
   469  	singleLineStyle := comments.Index(0).RawText()[:2] == "//"
   470  	line := comments.Index(0).End().Line
   471  	start := 0
   472  	for i := 1; i < comments.Len(); i++ {
   473  		c := comments.Index(i)
   474  		prevSingleLine := singleLineStyle
   475  		singleLineStyle = strings.HasPrefix(c.RawText(), "//")
   476  		if !singleLineStyle || prevSingleLine != singleLineStyle || c.Start().Line > line+1 {
   477  			// new group!
   478  			if str, ok := combineComments(comments, start, i); ok {
   479  				groups = append(groups, str)
   480  			}
   481  			start = i
   482  		}
   483  		line = c.End().Line
   484  	}
   485  	// don't forget last group
   486  	if str, ok := combineComments(comments, start, comments.Len()); ok {
   487  		groups = append(groups, str)
   488  	}
   489  	return groups
   490  }
   491  
   492  func combineComments(comments ast.Comments, start, end int) (string, bool) {
   493  	if start >= end {
   494  		return "", false
   495  	}
   496  	var buf bytes.Buffer
   497  	for i := start; i < end; i++ {
   498  		c := comments.Index(i)
   499  		txt := c.RawText()
   500  		if txt[:2] == "//" {
   501  			buf.WriteString(txt[2:])
   502  		} else {
   503  			lines := strings.Split(txt[2:len(txt)-2], "\n")
   504  			first := true
   505  			for _, l := range lines {
   506  				if first {
   507  					first = false
   508  				} else {
   509  					buf.WriteByte('\n')
   510  				}
   511  
   512  				// strip a prefix of whitespace followed by '*'
   513  				j := 0
   514  				for j < len(l) {
   515  					if l[j] != ' ' && l[j] != '\t' {
   516  						break
   517  					}
   518  					j++
   519  				}
   520  				if j == len(l) {
   521  					l = ""
   522  				} else if l[j] == '*' {
   523  					l = l[j+1:]
   524  				} else if j > 0 {
   525  					l = " " + l[j:]
   526  				}
   527  
   528  				buf.WriteString(l)
   529  			}
   530  		}
   531  	}
   532  	return buf.String(), true
   533  }
   534  
   535  func dup(p []int32) []int32 {
   536  	return append(([]int32)(nil), p...)
   537  }