platform_build_blueprint/parser/printer.go
Colin Cross 8e0c51192a Add license headers and LICENSE file
Change-Id: I6f7c7374093c0745ee4aa677480376a06648b358
2015-01-23 14:23:27 -08:00

293 lines
6.9 KiB
Go

// 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 (
wsNone whitespace = iota
wsBoth
wsAfter
wsBefore
wsMaybe
)
type printer struct {
defs []Definition
comments []Comment
curComment int
prev scanner.Position
ws whitespace
output []byte
indentList []int
wsBuf []byte
forceLineBreak int
}
func newPrinter(file *File) *printer {
return &printer{
defs: file.Defs,
comments: file.Comments,
indentList: []int{0},
}
}
func Print(file *File) ([]byte, error) {
p := newPrinter(file)
for _, def := range p.defs {
p.printDef(def)
}
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, wsMaybe)
p.printToken("=", assignment.Pos, wsBoth)
p.printValue(assignment.Value)
}
func (p *printer) printModule(module *Module) {
p.printToken(module.Type.Name, module.Type.Pos, wsBoth)
p.printMap(module.Properties, module.LbracePos, module.RbracePos, true)
p.forceLineBreak = 2
}
func (p *printer) printValue(value Value) {
if value.Variable != "" {
p.printToken(value.Variable, value.Pos, wsMaybe)
} 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, wsMaybe)
case String:
p.printToken(strconv.Quote(value.StringValue), value.Pos, wsMaybe)
case List:
p.printList(value.ListValue, value.Pos, value.EndPos)
case Map:
p.printMap(value.MapValue, value.Pos, value.EndPos, false)
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.forceLineBreak = 1
p.indent(p.curIndent() + 4)
for _, value := range list {
p.printValue(value)
p.printToken(",", noPos, wsAfter)
p.forceLineBreak = 1
}
p.unindent()
} else {
for _, value := range list {
p.printValue(value)
}
}
p.printToken("]", endPos, wsAfter)
}
func (p *printer) printMap(list []*Property, pos, endPos scanner.Position, isModule bool) {
if isModule {
p.printToken("(", pos, wsNone)
} else {
p.printToken("{", pos, wsBefore)
}
if len(list) > 0 || pos.Line != endPos.Line {
p.forceLineBreak = 1
p.indent(p.curIndent() + 4)
for _, prop := range list {
p.printProperty(prop, isModule)
p.printToken(",", noPos, wsAfter)
p.forceLineBreak = 1
}
p.unindent()
}
if isModule {
p.printToken(")", endPos, wsAfter)
} else {
p.printToken("}", endPos, wsAfter)
}
}
func (p *printer) printExpression(expression Expression) {
p.printValue(expression.Args[0])
p.printToken(string(expression.Operator), expression.Pos, wsBoth)
p.printValue(expression.Args[1])
}
func (p *printer) printProperty(property *Property, isModule bool) {
p.printToken(property.Name.Name, property.Name.Pos, wsMaybe)
if isModule {
p.printToken("=", property.Pos, wsBoth)
} else {
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) {
p.printComments(pos, false)
if p.forceLineBreak > 0 || p.prev.Line != 0 && pos.Line > p.prev.Line {
p.printLineBreak(pos.Line - p.prev.Line)
} else {
p.printWhitespace(ws)
}
p.output = append(p.output, s...)
p.ws = ws
if pos != noPos {
p.prev = pos
}
}
// Print all comments that occur before position pos
func (p *printer) printComments(pos scanner.Position, flush bool) {
for p.curComment < len(p.comments) && p.comments[p.curComment].Pos.Offset < pos.Offset {
p.printComment(p.comments[p.curComment])
p.curComment++
}
}
// Print a single comment, which may be a multi-line comment
func (p *printer) printComment(comment Comment) {
commentLines := strings.Split(comment.Comment, "\n")
pos := comment.Pos
for _, line := range commentLines {
if p.prev.Line != 0 && pos.Line > p.prev.Line {
// Comment is on the next line
p.printLineBreak(pos.Line - p.prev.Line)
} else {
// Comment is on the current line
p.printWhitespace(wsBoth)
}
p.output = append(p.output, strings.TrimSpace(line)...)
p.prev = pos
pos.Line++
}
p.ws = wsBoth
}
// 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 int) {
if n > 2 {
n = 2
}
if p.forceLineBreak > n {
if p.forceLineBreak == 0 {
panic("unexpected 0 line break")
}
n = p.forceLineBreak
}
for i := 0; i < n; i++ {
p.output = append(p.output, '\n')
}
p.pad(0, p.curIndent())
p.forceLineBreak = 0
p.ws = wsNone
}
// Print any necessary whitespace before the next token, based on the current
// ws value and the previous ws value.
func (p *printer) printWhitespace(ws whitespace) {
if (ws == wsBefore || ws == wsBoth) && p.ws != wsNone ||
ws == wsMaybe && (p.ws == wsMaybe || p.ws == wsAfter || p.ws == wsBoth) {
p.output = append(p.output, ' ')
}
p.ws = ws
}
// Print any comments that occur after the last token, and a trailing newline
func (p *printer) flush() {
for p.curComment < len(p.comments) {
p.printComment(p.comments[p.curComment])
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]
}