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