...

Source file src/github.com/emicklei/proto/comment.go

Documentation: github.com/emicklei/proto

     1  // Copyright (c) 2017 Ernest Micklei
     2  //
     3  // MIT License
     4  //
     5  // Permission is hereby granted, free of charge, to any person obtaining
     6  // a copy of this software and associated documentation files (the
     7  // "Software"), to deal in the Software without restriction, including
     8  // without limitation the rights to use, copy, modify, merge, publish,
     9  // distribute, sublicense, and/or sell copies of the Software, and to
    10  // permit persons to whom the Software is furnished to do so, subject to
    11  // the following conditions:
    12  //
    13  // The above copyright notice and this permission notice shall be
    14  // included in all copies or substantial portions of the Software.
    15  //
    16  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    17  // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    18  // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
    19  // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
    20  // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    21  // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
    22  // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    23  
    24  package proto
    25  
    26  import (
    27  	"strings"
    28  	"text/scanner"
    29  )
    30  
    31  // Comment one or more comment text lines, either in c- or c++ style.
    32  type Comment struct {
    33  	Position scanner.Position
    34  	// Lines are comment text lines without prefixes //, ///, /* or suffix */
    35  	Lines      []string
    36  	Cstyle     bool // refers to /* ... */,  C++ style is using //
    37  	ExtraSlash bool // is true if the comment starts with 3 slashes
    38  }
    39  
    40  // newComment returns a comment.
    41  func newComment(pos scanner.Position, lit string) *Comment {
    42  	extraSlash := strings.HasPrefix(lit, "///")
    43  	isCstyle := strings.HasPrefix(lit, "/*") && strings.HasSuffix(lit, "*/")
    44  	var lines []string
    45  	if isCstyle {
    46  		withoutMarkers := strings.TrimRight(strings.TrimLeft(lit, "/*"), "*/")
    47  		lines = strings.Split(withoutMarkers, "\n")
    48  	} else {
    49  		lines = strings.Split(strings.TrimLeft(lit, "/"), "\n")
    50  	}
    51  	return &Comment{Position: pos, Lines: lines, Cstyle: isCstyle, ExtraSlash: extraSlash}
    52  }
    53  
    54  type inlineComment struct {
    55  	line       string
    56  	extraSlash bool
    57  }
    58  
    59  // Accept dispatches the call to the visitor.
    60  func (c *Comment) Accept(v Visitor) {
    61  	v.VisitComment(c)
    62  }
    63  
    64  // Merge appends all lines from the argument comment.
    65  func (c *Comment) Merge(other *Comment) {
    66  	c.Lines = append(c.Lines, other.Lines...)
    67  	c.Cstyle = c.Cstyle || other.Cstyle
    68  }
    69  
    70  func (c Comment) hasTextOnLine(line int) bool {
    71  	if len(c.Lines) == 0 {
    72  		return false
    73  	}
    74  	return c.Position.Line <= line && line <= c.Position.Line+len(c.Lines)-1
    75  }
    76  
    77  // Message returns the first line or empty if no lines.
    78  func (c Comment) Message() string {
    79  	if len(c.Lines) == 0 {
    80  		return ""
    81  	}
    82  	return c.Lines[0]
    83  }
    84  
    85  // commentInliner is for types that can have an inline comment.
    86  type commentInliner interface {
    87  	inlineComment(c *Comment)
    88  }
    89  
    90  // maybeScanInlineComment tries to scan comment on the current line ; if present then set it for the last element added.
    91  func maybeScanInlineComment(p *Parser, c elementContainer) {
    92  	currentPos := p.scanner.Position
    93  	// see if there is an inline Comment
    94  	pos, tok, lit := p.next()
    95  	esize := len(c.elements())
    96  	// seen comment and on same line and elements have been added
    97  	if tCOMMENT == tok && pos.Line == currentPos.Line && esize > 0 {
    98  		// if the last added element can have an inline comment then set it
    99  		last := c.elements()[esize-1]
   100  		if inliner, ok := last.(commentInliner); ok {
   101  			// TODO skip multiline?
   102  			inliner.inlineComment(newComment(pos, lit))
   103  		}
   104  	} else {
   105  		p.nextPut(pos, tok, lit)
   106  	}
   107  }
   108  
   109  // takeLastCommentIfEndsOnLine removes and returns the last element of the list if it is a Comment
   110  func takeLastCommentIfEndsOnLine(list []Visitee, line int) (*Comment, []Visitee) {
   111  	if len(list) == 0 {
   112  		return nil, list
   113  	}
   114  	if last, ok := list[len(list)-1].(*Comment); ok && last.hasTextOnLine(line) {
   115  		return last, list[:len(list)-1]
   116  	}
   117  	return nil, list
   118  }
   119  
   120  // mergeOrReturnComment creates a new comment and tries to merge it with the last element (if is a comment and is on the next line).
   121  func mergeOrReturnComment(elements []Visitee, lit string, pos scanner.Position) *Comment {
   122  	com := newComment(pos, lit)
   123  	esize := len(elements)
   124  	if esize == 0 {
   125  		return com
   126  	}
   127  	// last element must be a comment to merge
   128  	last, ok := elements[esize-1].(*Comment)
   129  	if !ok {
   130  		return com
   131  	}
   132  	// do not merge c-style comments
   133  	if last.Cstyle {
   134  		return com
   135  	}
   136  	// last comment has text on previous line
   137  	// TODO handle last line of file could be inline comment
   138  	if !last.hasTextOnLine(pos.Line - 1) {
   139  		return com
   140  	}
   141  	last.Merge(com)
   142  	return nil
   143  }
   144  
   145  // parent is part of elementContainer
   146  func (c *Comment) parent(Visitee) {}
   147  
   148  // consumeCommentFor is for reading and taking all comment lines before the body of an element (starting at {)
   149  func consumeCommentFor(p *Parser, e elementContainer) {
   150  	pos, tok, lit := p.next()
   151  	if tok == tCOMMENT {
   152  		if com := mergeOrReturnComment(e.elements(), lit, pos); com != nil { // not merged?
   153  			e.addElement(com)
   154  		}
   155  		consumeCommentFor(p, e) // bit of recursion is fine
   156  	} else {
   157  		p.nextPut(pos, tok, lit)
   158  	}
   159  }
   160  

View as plain text