• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
15package parser
16
17import (
18	"fmt"
19	"strconv"
20	"strings"
21	"text/scanner"
22	"unicode"
23)
24
25var noPos scanner.Position
26
27type 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
46func 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
61func 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
71func (p *printer) Print() ([]byte, error) {
72	for _, def := range p.defs {
73		p.printDef(def)
74	}
75	p.flush()
76	return p.output, nil
77}
78
79func (p *printer) printDef(def Definition) {
80	if assignment, ok := def.(*Assignment); ok {
81		p.printAssignment(assignment)
82	} else if module, ok := def.(*Module); ok {
83		p.printModule(module)
84	} else {
85		panic("Unknown definition")
86	}
87}
88
89func (p *printer) printAssignment(assignment *Assignment) {
90	p.printToken(assignment.Name, assignment.NamePos)
91	p.requestSpace()
92	p.printToken(assignment.Assigner, assignment.EqualsPos)
93	p.requestSpace()
94	p.printExpression(assignment.OrigValue)
95	p.requestNewline()
96}
97
98func (p *printer) printModule(module *Module) {
99	p.printToken(module.Type, module.TypePos)
100	p.printMap(&module.Map)
101	p.requestDoubleNewline()
102}
103
104func (p *printer) printExpression(value Expression) {
105	switch v := value.(type) {
106	case *Variable:
107		p.printToken(v.Name, v.NamePos)
108	case *Operator:
109		p.printOperator(v)
110	case *Bool:
111		var s string
112		if v.Value {
113			s = "true"
114		} else {
115			s = "false"
116		}
117		p.printToken(s, v.LiteralPos)
118	case *String:
119		p.printToken(strconv.Quote(v.Value), v.LiteralPos)
120	case *List:
121		p.printList(v.Values, v.LBracePos, v.RBracePos)
122	case *Map:
123		p.printMap(v)
124	default:
125		panic(fmt.Errorf("bad property type: %s", value.Type()))
126	}
127}
128
129func (p *printer) printList(list []Expression, pos, endPos scanner.Position) {
130	p.requestSpace()
131	p.printToken("[", pos)
132	if len(list) > 1 || pos.Line != endPos.Line {
133		p.requestNewline()
134		p.indent(p.curIndent() + 4)
135		for _, value := range list {
136			p.printExpression(value)
137			p.printToken(",", noPos)
138			p.requestNewline()
139		}
140		p.unindent(endPos)
141	} else {
142		for _, value := range list {
143			p.printExpression(value)
144		}
145	}
146	p.printToken("]", endPos)
147}
148
149func (p *printer) printMap(m *Map) {
150	p.requestSpace()
151	p.printToken("{", m.LBracePos)
152	if len(m.Properties) > 0 || m.LBracePos.Line != m.RBracePos.Line {
153		p.requestNewline()
154		p.indent(p.curIndent() + 4)
155		for _, prop := range m.Properties {
156			p.printProperty(prop)
157			p.printToken(",", noPos)
158			p.requestNewline()
159		}
160		p.unindent(m.RBracePos)
161	}
162	p.printToken("}", m.RBracePos)
163}
164
165func (p *printer) printOperator(operator *Operator) {
166	p.printExpression(operator.Args[0])
167	p.requestSpace()
168	p.printToken(string(operator.Operator), operator.OperatorPos)
169	if operator.Args[0].End().Line == operator.Args[1].Pos().Line {
170		p.requestSpace()
171	} else {
172		p.requestNewline()
173	}
174	p.printExpression(operator.Args[1])
175}
176
177func (p *printer) printProperty(property *Property) {
178	p.printToken(property.Name, property.NamePos)
179	p.printToken(":", property.ColonPos)
180	p.requestSpace()
181	p.printExpression(property.Value)
182}
183
184// Print a single token, including any necessary comments or whitespace between
185// this token and the previously printed token
186func (p *printer) printToken(s string, pos scanner.Position) {
187	newline := p.pendingNewline != 0
188
189	if pos == noPos {
190		pos = p.pos
191	}
192
193	if newline {
194		p.printEndOfLineCommentsBefore(pos)
195		p.requestNewlinesForPos(pos)
196	}
197
198	p.printInLineCommentsBefore(pos)
199
200	p.flushSpace()
201
202	p.output = append(p.output, s...)
203
204	p.pos = pos
205}
206
207// Print any in-line (single line /* */) comments that appear _before_ pos
208func (p *printer) printInLineCommentsBefore(pos scanner.Position) {
209	for p.curComment < len(p.comments) && p.comments[p.curComment].Pos().Offset < pos.Offset {
210		c := p.comments[p.curComment]
211		if c.Comments[0].Comment[0][0:2] == "//" || len(c.Comments[0].Comment) > 1 {
212			p.skippedComments = append(p.skippedComments, c)
213		} else {
214			p.printComment(c)
215			p.requestSpace()
216		}
217		p.curComment++
218	}
219}
220
221// Print any comments, including end of line comments, that appear _before_ the line specified
222// by pos
223func (p *printer) printEndOfLineCommentsBefore(pos scanner.Position) {
224	if len(p.skippedComments) > 0 {
225		for _, c := range p.skippedComments {
226			p.printComment(c)
227		}
228		p._requestNewline()
229		p.skippedComments = nil
230	}
231	for p.curComment < len(p.comments) && p.comments[p.curComment].Pos().Line < pos.Line {
232		c := p.comments[p.curComment]
233		p.printComment(c)
234		p._requestNewline()
235		p.curComment++
236	}
237}
238
239// Compare the line numbers of the previous and current positions to determine whether extra
240// newlines should be inserted.  A second newline is allowed anywhere requestNewline() is called.
241func (p *printer) requestNewlinesForPos(pos scanner.Position) bool {
242	if pos.Line > p.pos.Line {
243		p._requestNewline()
244		if pos.Line > p.pos.Line+1 {
245			p.pendingNewline = 2
246		}
247		return true
248	}
249
250	return false
251}
252
253func (p *printer) requestSpace() {
254	p.pendingSpace = true
255}
256
257// Ask for a newline to be inserted before the next token, but do not insert any comments.  Used
258// by the comment printers.
259func (p *printer) _requestNewline() {
260	if p.pendingNewline == 0 {
261		p.pendingNewline = 1
262	}
263}
264
265// Ask for a newline to be inserted before the next token.  Also inserts any end-of line comments
266// for the current line
267func (p *printer) requestNewline() {
268	pos := p.pos
269	pos.Line++
270	p.printEndOfLineCommentsBefore(pos)
271	p._requestNewline()
272}
273
274// Ask for two newlines to be inserted before the next token.  Also inserts any end-of line comments
275// for the current line
276func (p *printer) requestDoubleNewline() {
277	p.requestNewline()
278	p.pendingNewline = 2
279}
280
281// Flush any pending whitespace, ignoring pending spaces if there is a pending newline
282func (p *printer) flushSpace() {
283	if p.pendingNewline == 1 {
284		p.output = append(p.output, '\n')
285		p.pad(p.curIndent())
286	} else if p.pendingNewline == 2 {
287		p.output = append(p.output, "\n\n"...)
288		p.pad(p.curIndent())
289	} else if p.pendingSpace == true && p.pendingNewline != -1 {
290		p.output = append(p.output, ' ')
291	}
292
293	p.pendingSpace = false
294	p.pendingNewline = 0
295}
296
297// Print a single comment, which may be a multi-line comment
298func (p *printer) printComment(cg *CommentGroup) {
299	for _, comment := range cg.Comments {
300		if !p.requestNewlinesForPos(comment.Pos()) {
301			p.requestSpace()
302		}
303		for i, line := range comment.Comment {
304			line = strings.TrimRightFunc(line, unicode.IsSpace)
305			p.flushSpace()
306			if i != 0 {
307				lineIndent := strings.IndexFunc(line, func(r rune) bool { return !unicode.IsSpace(r) })
308				lineIndent = max(lineIndent, p.curIndent())
309				p.pad(lineIndent - p.curIndent())
310			}
311			p.output = append(p.output, strings.TrimSpace(line)...)
312			if i < len(comment.Comment)-1 {
313				p._requestNewline()
314			}
315		}
316		p.pos = comment.End()
317	}
318}
319
320// Print any comments that occur after the last token, and a trailing newline
321func (p *printer) flush() {
322	for _, c := range p.skippedComments {
323		if !p.requestNewlinesForPos(c.Pos()) {
324			p.requestSpace()
325		}
326		p.printComment(c)
327	}
328	for p.curComment < len(p.comments) {
329		p.printComment(p.comments[p.curComment])
330		p.curComment++
331	}
332	p.output = append(p.output, '\n')
333}
334
335// Print whitespace to pad from column l to column max
336func (p *printer) pad(l int) {
337	if l > len(p.wsBuf) {
338		p.wsBuf = make([]byte, l)
339		for i := range p.wsBuf {
340			p.wsBuf[i] = ' '
341		}
342	}
343	p.output = append(p.output, p.wsBuf[0:l]...)
344}
345
346func (p *printer) indent(i int) {
347	p.indentList = append(p.indentList, i)
348}
349
350func (p *printer) unindent(pos scanner.Position) {
351	p.printEndOfLineCommentsBefore(pos)
352	p.indentList = p.indentList[0 : len(p.indentList)-1]
353}
354
355func (p *printer) curIndent() int {
356	return p.indentList[len(p.indentList)-1]
357}
358
359func max(a, b int) int {
360	if a > b {
361		return a
362	} else {
363		return b
364	}
365}
366