// Copyright 2014 Google Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package parser import ( "fmt" "strconv" "strings" "text/scanner" ) var noPos = scanner.Position{} type whitespace int const ( wsDontCare whitespace = iota wsBoth wsAfter wsBefore wsCanBreak // allow extra line breaks or comments wsBothCanBreak // wsBoth plus allow extra line breaks or comments wsForceBreak wsForceDoubleBreak ) type tokenState struct { ws whitespace token string pos scanner.Position indent int } type printer struct { defs []Definition comments []Comment curComment int prev tokenState output []byte indentList []int wsBuf []byte skippedComments []Comment } func newPrinter(file *File) *printer { return &printer{ defs: file.Defs, comments: file.Comments, indentList: []int{0}, prev: tokenState{ ws: wsCanBreak, }, } } func Print(file *File) ([]byte, error) { p := newPrinter(file) for _, def := range p.defs { p.printDef(def) } p.prev.ws = wsDontCare p.flush() return p.output, nil } func (p *printer) Print() ([]byte, error) { for _, def := range p.defs { p.printDef(def) } p.flush() return p.output, nil } func (p *printer) printDef(def Definition) { if assignment, ok := def.(*Assignment); ok { p.printAssignment(assignment) } else if module, ok := def.(*Module); ok { p.printModule(module) } else { panic("Unknown definition") } } func (p *printer) printAssignment(assignment *Assignment) { p.printToken(assignment.Name.Name, assignment.Name.Pos, wsDontCare) p.printToken(assignment.Assigner, assignment.Pos, wsBoth) p.printValue(assignment.OrigValue) p.prev.ws = wsForceBreak } func (p *printer) printModule(module *Module) { p.printToken(module.Type.Name, module.Type.Pos, wsDontCare) p.printMap(module.Properties, module.LbracePos, module.RbracePos) p.prev.ws = wsForceDoubleBreak } func (p *printer) printValue(value Value) { if value.Variable != "" { p.printToken(value.Variable, value.Pos, wsDontCare) } else if value.Expression != nil { p.printExpression(*value.Expression) } else { switch value.Type { case Bool: var s string if value.BoolValue { s = "true" } else { s = "false" } p.printToken(s, value.Pos, wsDontCare) case String: p.printToken(strconv.Quote(value.StringValue), value.Pos, wsDontCare) case List: p.printList(value.ListValue, value.Pos, value.EndPos) case Map: p.printMap(value.MapValue, value.Pos, value.EndPos) default: panic(fmt.Errorf("bad property type: %d", value.Type)) } } } func (p *printer) printList(list []Value, pos, endPos scanner.Position) { p.printToken("[", pos, wsBefore) if len(list) > 1 || pos.Line != endPos.Line { p.prev.ws = wsForceBreak p.indent(p.curIndent() + 4) for _, value := range list { p.printValue(value) p.printToken(",", noPos, wsForceBreak) } p.unindent() } else { for _, value := range list { p.printValue(value) } } p.printToken("]", endPos, wsDontCare) } func (p *printer) printMap(list []*Property, pos, endPos scanner.Position) { p.printToken("{", pos, wsBefore) if len(list) > 0 || pos.Line != endPos.Line { p.prev.ws = wsForceBreak p.indent(p.curIndent() + 4) for _, prop := range list { p.printProperty(prop) p.printToken(",", noPos, wsForceBreak) } p.unindent() } p.printToken("}", endPos, wsDontCare) } func (p *printer) printExpression(expression Expression) { p.printValue(expression.Args[0]) p.printToken(string(expression.Operator), expression.Pos, wsBothCanBreak) p.printValue(expression.Args[1]) } func (p *printer) printProperty(property *Property) { p.printToken(property.Name.Name, property.Name.Pos, wsDontCare) p.printToken(":", property.Pos, wsAfter) p.printValue(property.Value) } // Print a single token, including any necessary comments or whitespace between // this token and the previously printed token func (p *printer) printToken(s string, pos scanner.Position, ws whitespace) { this := tokenState{ token: s, pos: pos, ws: ws, indent: p.curIndent(), } if this.pos == noPos { this.pos = p.prev.pos } // Print the previously stored token allowLineBreak := false lineBreak := 0 switch p.prev.ws { case wsBothCanBreak, wsCanBreak, wsForceBreak, wsForceDoubleBreak: allowLineBreak = true } p.output = append(p.output, p.prev.token...) commentIndent := max(p.prev.indent, this.indent) p.printComments(this.pos, allowLineBreak, commentIndent) switch p.prev.ws { case wsForceBreak: lineBreak = 1 case wsForceDoubleBreak: lineBreak = 2 } if allowLineBreak && p.prev.pos.IsValid() && pos.Line-p.prev.pos.Line > lineBreak { lineBreak = pos.Line - p.prev.pos.Line } if lineBreak > 0 { p.printLineBreak(lineBreak, this.indent) } else { p.printWhitespace(this.ws) } p.prev = this } func (p *printer) printWhitespace(ws whitespace) { if p.prev.ws == wsBoth || ws == wsBoth || p.prev.ws == wsBothCanBreak || ws == wsBothCanBreak || p.prev.ws == wsAfter || ws == wsBefore { p.output = append(p.output, ' ') } } // Pr int all comments that occur before position pos func (p *printer) printComments(pos scanner.Position, allowLineBreak bool, indent int) { if allowLineBreak { for _, c := range p.skippedComments { p.printComment(c, indent) } p.skippedComments = []Comment{} } for p.curComment < len(p.comments) && p.comments[p.curComment].Pos.Offset < pos.Offset { if !allowLineBreak && p.comments[p.curComment].Comment[0:2] == "//" { p.skippedComments = append(p.skippedComments, p.comments[p.curComment]) } else { p.printComment(p.comments[p.curComment], indent) } p.curComment++ } } // Print a single comment, which may be a multi-line comment func (p *printer) printComment(comment Comment, indent int) { commentLines := strings.Split(comment.Comment, "\n") pos := comment.Pos for _, line := range commentLines { if p.prev.pos.IsValid() && pos.Line > p.prev.pos.Line { // Comment is on the next line if p.prev.ws == wsForceDoubleBreak { p.printLineBreak(2, indent) p.prev.ws = wsForceBreak } else { p.printLineBreak(pos.Line-p.prev.pos.Line, indent) } } else if p.prev.pos.IsValid() { // Comment is on the current line p.printWhitespace(wsBoth) } p.output = append(p.output, strings.TrimSpace(line)...) p.prev.pos = pos pos.Line++ } if comment.Comment[0:2] == "//" { if p.prev.ws != wsForceDoubleBreak { p.prev.ws = wsForceBreak } } else { p.prev.ws = wsBothCanBreak } } // Print one or two line breaks. n <= 0 is only valid if forceLineBreak is set, // n > 2 is collapsed to a single blank line. func (p *printer) printLineBreak(n, indent int) { if n > 2 { n = 2 } for i := 0; i < n; i++ { p.output = append(p.output, '\n') } p.pad(0, indent) } // Print any comments that occur after the last token, and a trailing newline func (p *printer) flush() { for _, c := range p.skippedComments { p.printComment(c, p.curIndent()) } p.printToken("", noPos, wsDontCare) for p.curComment < len(p.comments) { p.printComment(p.comments[p.curComment], p.curIndent()) p.curComment++ } p.output = append(p.output, '\n') } // Print whitespace to pad from column l to column max func (p *printer) pad(l, max int) { l = max - l if l > len(p.wsBuf) { p.wsBuf = make([]byte, l) for i := range p.wsBuf { p.wsBuf[i] = ' ' } } p.output = append(p.output, p.wsBuf[0:l]...) } func (p *printer) indent(i int) { p.indentList = append(p.indentList, i) } func (p *printer) unindent() { p.indentList = p.indentList[0 : len(p.indentList)-1] } func (p *printer) curIndent() int { return p.indentList[len(p.indentList)-1] } func max(a, b int) int { if a > b { return a } else { return b } }