github.com/westcoastroms/westcoastroms-build@v0.0.0-20190928114312-2350e5a73030/build/blueprint/parser/printer.go (about)

     1  // Copyright 2014 Google Inc. All rights reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package parser
    16  
    17  import (
    18  	"fmt"
    19  	"strconv"
    20  	"strings"
    21  	"text/scanner"
    22  	"unicode"
    23  )
    24  
    25  var noPos scanner.Position
    26  
    27  type printer struct {
    28  	defs     []Definition
    29  	comments []*CommentGroup
    30  
    31  	curComment int
    32  
    33  	pos scanner.Position
    34  
    35  	pendingSpace   bool
    36  	pendingNewline int
    37  
    38  	output []byte
    39  
    40  	indentList []int
    41  	wsBuf      []byte
    42  
    43  	skippedComments []*CommentGroup
    44  }
    45  
    46  func newPrinter(file *File) *printer {
    47  	return &printer{
    48  		defs:       file.Defs,
    49  		comments:   file.Comments,
    50  		indentList: []int{0},
    51  
    52  		// pendingNewLine is initialized to -1 to eat initial spaces if the first token is a comment
    53  		pendingNewline: -1,
    54  
    55  		pos: scanner.Position{
    56  			Line: 1,
    57  		},
    58  	}
    59  }
    60  
    61  func Print(file *File) ([]byte, error) {
    62  	p := newPrinter(file)
    63  
    64  	for _, def := range p.defs {
    65  		p.printDef(def)
    66  	}
    67  	p.flush()
    68  	return p.output, nil
    69  }
    70  
    71  func PrintExpression(expression Expression) ([]byte, error) {
    72  	dummyFile := &File{}
    73  	p := newPrinter(dummyFile)
    74  	p.printExpression(expression)
    75  	p.flush()
    76  	return p.output, nil
    77  }
    78  
    79  func (p *printer) Print() ([]byte, error) {
    80  	for _, def := range p.defs {
    81  		p.printDef(def)
    82  	}
    83  	p.flush()
    84  	return p.output, nil
    85  }
    86  
    87  func (p *printer) printDef(def Definition) {
    88  	if assignment, ok := def.(*Assignment); ok {
    89  		p.printAssignment(assignment)
    90  	} else if module, ok := def.(*Module); ok {
    91  		p.printModule(module)
    92  	} else {
    93  		panic("Unknown definition")
    94  	}
    95  }
    96  
    97  func (p *printer) printAssignment(assignment *Assignment) {
    98  	p.printToken(assignment.Name, assignment.NamePos)
    99  	p.requestSpace()
   100  	p.printToken(assignment.Assigner, assignment.EqualsPos)
   101  	p.requestSpace()
   102  	p.printExpression(assignment.OrigValue)
   103  	p.requestNewline()
   104  }
   105  
   106  func (p *printer) printModule(module *Module) {
   107  	p.printToken(module.Type, module.TypePos)
   108  	p.printMap(&module.Map)
   109  	p.requestDoubleNewline()
   110  }
   111  
   112  func (p *printer) printExpression(value Expression) {
   113  	switch v := value.(type) {
   114  	case *Variable:
   115  		p.printToken(v.Name, v.NamePos)
   116  	case *Operator:
   117  		p.printOperator(v)
   118  	case *Bool:
   119  		var s string
   120  		if v.Value {
   121  			s = "true"
   122  		} else {
   123  			s = "false"
   124  		}
   125  		p.printToken(s, v.LiteralPos)
   126  	case *Int64:
   127  		p.printToken(strconv.FormatInt(v.Value, 10), v.LiteralPos)
   128  	case *String:
   129  		p.printToken(strconv.Quote(v.Value), v.LiteralPos)
   130  	case *List:
   131  		p.printList(v.Values, v.LBracePos, v.RBracePos)
   132  	case *Map:
   133  		p.printMap(v)
   134  	default:
   135  		panic(fmt.Errorf("bad property type: %s", value.Type()))
   136  	}
   137  }
   138  
   139  func (p *printer) printList(list []Expression, pos, endPos scanner.Position) {
   140  	p.requestSpace()
   141  	p.printToken("[", pos)
   142  	if len(list) > 1 || pos.Line != endPos.Line {
   143  		p.requestNewline()
   144  		p.indent(p.curIndent() + 4)
   145  		for _, value := range list {
   146  			p.printExpression(value)
   147  			p.printToken(",", noPos)
   148  			p.requestNewline()
   149  		}
   150  		p.unindent(endPos)
   151  	} else {
   152  		for _, value := range list {
   153  			p.printExpression(value)
   154  		}
   155  	}
   156  	p.printToken("]", endPos)
   157  }
   158  
   159  func (p *printer) printMap(m *Map) {
   160  	p.requestSpace()
   161  	p.printToken("{", m.LBracePos)
   162  	if len(m.Properties) > 0 || m.LBracePos.Line != m.RBracePos.Line {
   163  		p.requestNewline()
   164  		p.indent(p.curIndent() + 4)
   165  		for _, prop := range m.Properties {
   166  			p.printProperty(prop)
   167  			p.printToken(",", noPos)
   168  			p.requestNewline()
   169  		}
   170  		p.unindent(m.RBracePos)
   171  	}
   172  	p.printToken("}", m.RBracePos)
   173  }
   174  
   175  func (p *printer) printOperator(operator *Operator) {
   176  	p.printExpression(operator.Args[0])
   177  	p.requestSpace()
   178  	p.printToken(string(operator.Operator), operator.OperatorPos)
   179  	if operator.Args[0].End().Line == operator.Args[1].Pos().Line {
   180  		p.requestSpace()
   181  	} else {
   182  		p.requestNewline()
   183  	}
   184  	p.printExpression(operator.Args[1])
   185  }
   186  
   187  func (p *printer) printProperty(property *Property) {
   188  	p.printToken(property.Name, property.NamePos)
   189  	p.printToken(":", property.ColonPos)
   190  	p.requestSpace()
   191  	p.printExpression(property.Value)
   192  }
   193  
   194  // Print a single token, including any necessary comments or whitespace between
   195  // this token and the previously printed token
   196  func (p *printer) printToken(s string, pos scanner.Position) {
   197  	newline := p.pendingNewline != 0
   198  
   199  	if pos == noPos {
   200  		pos = p.pos
   201  	}
   202  
   203  	if newline {
   204  		p.printEndOfLineCommentsBefore(pos)
   205  		p.requestNewlinesForPos(pos)
   206  	}
   207  
   208  	p.printInLineCommentsBefore(pos)
   209  
   210  	p.flushSpace()
   211  
   212  	p.output = append(p.output, s...)
   213  
   214  	p.pos = pos
   215  }
   216  
   217  // Print any in-line (single line /* */) comments that appear _before_ pos
   218  func (p *printer) printInLineCommentsBefore(pos scanner.Position) {
   219  	for p.curComment < len(p.comments) && p.comments[p.curComment].Pos().Offset < pos.Offset {
   220  		c := p.comments[p.curComment]
   221  		if c.Comments[0].Comment[0][0:2] == "//" || len(c.Comments[0].Comment) > 1 {
   222  			p.skippedComments = append(p.skippedComments, c)
   223  		} else {
   224  			p.printComment(c)
   225  			p.requestSpace()
   226  		}
   227  		p.curComment++
   228  	}
   229  }
   230  
   231  // Print any comments, including end of line comments, that appear _before_ the line specified
   232  // by pos
   233  func (p *printer) printEndOfLineCommentsBefore(pos scanner.Position) {
   234  	if len(p.skippedComments) > 0 {
   235  		for _, c := range p.skippedComments {
   236  			p.printComment(c)
   237  		}
   238  		p._requestNewline()
   239  		p.skippedComments = nil
   240  	}
   241  	for p.curComment < len(p.comments) && p.comments[p.curComment].Pos().Line < pos.Line {
   242  		c := p.comments[p.curComment]
   243  		p.printComment(c)
   244  		p._requestNewline()
   245  		p.curComment++
   246  	}
   247  }
   248  
   249  // Compare the line numbers of the previous and current positions to determine whether extra
   250  // newlines should be inserted.  A second newline is allowed anywhere requestNewline() is called.
   251  func (p *printer) requestNewlinesForPos(pos scanner.Position) bool {
   252  	if pos.Line > p.pos.Line {
   253  		p._requestNewline()
   254  		if pos.Line > p.pos.Line+1 {
   255  			p.pendingNewline = 2
   256  		}
   257  		return true
   258  	}
   259  
   260  	return false
   261  }
   262  
   263  func (p *printer) requestSpace() {
   264  	p.pendingSpace = true
   265  }
   266  
   267  // Ask for a newline to be inserted before the next token, but do not insert any comments.  Used
   268  // by the comment printers.
   269  func (p *printer) _requestNewline() {
   270  	if p.pendingNewline == 0 {
   271  		p.pendingNewline = 1
   272  	}
   273  }
   274  
   275  // Ask for a newline to be inserted before the next token.  Also inserts any end-of line comments
   276  // for the current line
   277  func (p *printer) requestNewline() {
   278  	pos := p.pos
   279  	pos.Line++
   280  	p.printEndOfLineCommentsBefore(pos)
   281  	p._requestNewline()
   282  }
   283  
   284  // Ask for two newlines to be inserted before the next token.  Also inserts any end-of line comments
   285  // for the current line
   286  func (p *printer) requestDoubleNewline() {
   287  	p.requestNewline()
   288  	p.pendingNewline = 2
   289  }
   290  
   291  // Flush any pending whitespace, ignoring pending spaces if there is a pending newline
   292  func (p *printer) flushSpace() {
   293  	if p.pendingNewline == 1 {
   294  		p.output = append(p.output, '\n')
   295  		p.pad(p.curIndent())
   296  	} else if p.pendingNewline == 2 {
   297  		p.output = append(p.output, "\n\n"...)
   298  		p.pad(p.curIndent())
   299  	} else if p.pendingSpace == true && p.pendingNewline != -1 {
   300  		p.output = append(p.output, ' ')
   301  	}
   302  
   303  	p.pendingSpace = false
   304  	p.pendingNewline = 0
   305  }
   306  
   307  // Print a single comment, which may be a multi-line comment
   308  func (p *printer) printComment(cg *CommentGroup) {
   309  	for _, comment := range cg.Comments {
   310  		if !p.requestNewlinesForPos(comment.Pos()) {
   311  			p.requestSpace()
   312  		}
   313  		for i, line := range comment.Comment {
   314  			line = strings.TrimRightFunc(line, unicode.IsSpace)
   315  			p.flushSpace()
   316  			if i != 0 {
   317  				lineIndent := strings.IndexFunc(line, func(r rune) bool { return !unicode.IsSpace(r) })
   318  				lineIndent = max(lineIndent, p.curIndent())
   319  				p.pad(lineIndent - p.curIndent())
   320  			}
   321  			p.output = append(p.output, strings.TrimSpace(line)...)
   322  			if i < len(comment.Comment)-1 {
   323  				p._requestNewline()
   324  			}
   325  		}
   326  		p.pos = comment.End()
   327  	}
   328  }
   329  
   330  // Print any comments that occur after the last token, and a trailing newline
   331  func (p *printer) flush() {
   332  	for _, c := range p.skippedComments {
   333  		if !p.requestNewlinesForPos(c.Pos()) {
   334  			p.requestSpace()
   335  		}
   336  		p.printComment(c)
   337  	}
   338  	for p.curComment < len(p.comments) {
   339  		p.printComment(p.comments[p.curComment])
   340  		p.curComment++
   341  	}
   342  	p.output = append(p.output, '\n')
   343  }
   344  
   345  // Print whitespace to pad from column l to column max
   346  func (p *printer) pad(l int) {
   347  	if l > len(p.wsBuf) {
   348  		p.wsBuf = make([]byte, l)
   349  		for i := range p.wsBuf {
   350  			p.wsBuf[i] = ' '
   351  		}
   352  	}
   353  	p.output = append(p.output, p.wsBuf[0:l]...)
   354  }
   355  
   356  func (p *printer) indent(i int) {
   357  	p.indentList = append(p.indentList, i)
   358  }
   359  
   360  func (p *printer) unindent(pos scanner.Position) {
   361  	p.printEndOfLineCommentsBefore(pos)
   362  	p.indentList = p.indentList[0 : len(p.indentList)-1]
   363  }
   364  
   365  func (p *printer) curIndent() int {
   366  	return p.indentList[len(p.indentList)-1]
   367  }
   368  
   369  func max(a, b int) int {
   370  	if a > b {
   371  		return a
   372  	} else {
   373  		return b
   374  	}
   375  }