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 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
79func (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
87func (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
97func (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
106func (p *printer) printModule(module *Module) {
107	p.printToken(module.Type, module.TypePos)
108	p.printMap(&module.Map)
109	p.requestDoubleNewline()
110}
111
112func (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
139func (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
159func (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
175func (p *printer) printOperator(operator *Operator) {
176	p.printOperatorInternal(operator, true)
177}
178
179func (p *printer) printOperatorInternal(operator *Operator, allowIndent bool) {
180	p.printExpression(operator.Args[0])
181	p.requestSpace()
182	p.printToken(string(operator.Operator), operator.OperatorPos)
183
184	indented := false
185	if operator.Args[0].End().Line == operator.Args[1].Pos().Line {
186		p.requestSpace()
187	} else {
188		if allowIndent {
189			indented = true
190			p.indent(p.curIndent() + 4)
191		}
192		p.requestNewline()
193	}
194
195	if op, isOp := operator.Args[1].(*Operator); isOp {
196		p.printOperatorInternal(op, false)
197	} else {
198		p.printExpression(operator.Args[1])
199	}
200
201	if indented {
202		p.unindent(p.pos)
203	}
204}
205
206func (p *printer) printProperty(property *Property) {
207	p.printToken(property.Name, property.NamePos)
208	p.printToken(":", property.ColonPos)
209	p.requestSpace()
210	p.printExpression(property.Value)
211}
212
213// Print a single token, including any necessary comments or whitespace between
214// this token and the previously printed token
215func (p *printer) printToken(s string, pos scanner.Position) {
216	newline := p.pendingNewline != 0
217
218	if pos == noPos {
219		pos = p.pos
220	}
221
222	if newline {
223		p.printEndOfLineCommentsBefore(pos)
224		p.requestNewlinesForPos(pos)
225	}
226
227	p.printInLineCommentsBefore(pos)
228
229	p.flushSpace()
230
231	p.output = append(p.output, s...)
232
233	p.pos = pos
234}
235
236// Print any in-line (single line /* */) comments that appear _before_ pos
237func (p *printer) printInLineCommentsBefore(pos scanner.Position) {
238	for p.curComment < len(p.comments) && p.comments[p.curComment].Pos().Offset < pos.Offset {
239		c := p.comments[p.curComment]
240		if c.Comments[0].Comment[0][0:2] == "//" || len(c.Comments[0].Comment) > 1 {
241			p.skippedComments = append(p.skippedComments, c)
242		} else {
243			p.printComment(c)
244			p.requestSpace()
245		}
246		p.curComment++
247	}
248}
249
250// Print any comments, including end of line comments, that appear _before_ the line specified
251// by pos
252func (p *printer) printEndOfLineCommentsBefore(pos scanner.Position) {
253	if len(p.skippedComments) > 0 {
254		for _, c := range p.skippedComments {
255			p.printComment(c)
256		}
257		p._requestNewline()
258		p.skippedComments = nil
259	}
260	for p.curComment < len(p.comments) && p.comments[p.curComment].Pos().Line < pos.Line {
261		c := p.comments[p.curComment]
262		p.printComment(c)
263		p._requestNewline()
264		p.curComment++
265	}
266}
267
268// Compare the line numbers of the previous and current positions to determine whether extra
269// newlines should be inserted.  A second newline is allowed anywhere requestNewline() is called.
270func (p *printer) requestNewlinesForPos(pos scanner.Position) bool {
271	if pos.Line > p.pos.Line {
272		p._requestNewline()
273		if pos.Line > p.pos.Line+1 {
274			p.pendingNewline = 2
275		}
276		return true
277	}
278
279	return false
280}
281
282func (p *printer) requestSpace() {
283	p.pendingSpace = true
284}
285
286// Ask for a newline to be inserted before the next token, but do not insert any comments.  Used
287// by the comment printers.
288func (p *printer) _requestNewline() {
289	if p.pendingNewline == 0 {
290		p.pendingNewline = 1
291	}
292}
293
294// Ask for a newline to be inserted before the next token.  Also inserts any end-of line comments
295// for the current line
296func (p *printer) requestNewline() {
297	pos := p.pos
298	pos.Line++
299	p.printEndOfLineCommentsBefore(pos)
300	p._requestNewline()
301}
302
303// Ask for two newlines to be inserted before the next token.  Also inserts any end-of line comments
304// for the current line
305func (p *printer) requestDoubleNewline() {
306	p.requestNewline()
307	p.pendingNewline = 2
308}
309
310// Flush any pending whitespace, ignoring pending spaces if there is a pending newline
311func (p *printer) flushSpace() {
312	if p.pendingNewline == 1 {
313		p.output = append(p.output, '\n')
314		p.pad(p.curIndent())
315	} else if p.pendingNewline == 2 {
316		p.output = append(p.output, "\n\n"...)
317		p.pad(p.curIndent())
318	} else if p.pendingSpace == true && p.pendingNewline != -1 {
319		p.output = append(p.output, ' ')
320	}
321
322	p.pendingSpace = false
323	p.pendingNewline = 0
324}
325
326// Print a single comment, which may be a multi-line comment
327func (p *printer) printComment(cg *CommentGroup) {
328	for _, comment := range cg.Comments {
329		if !p.requestNewlinesForPos(comment.Pos()) {
330			p.requestSpace()
331		}
332		for i, line := range comment.Comment {
333			line = strings.TrimRightFunc(line, unicode.IsSpace)
334			p.flushSpace()
335			if i != 0 {
336				lineIndent := strings.IndexFunc(line, func(r rune) bool { return !unicode.IsSpace(r) })
337				lineIndent = max(lineIndent, p.curIndent())
338				p.pad(lineIndent - p.curIndent())
339			}
340			p.output = append(p.output, strings.TrimSpace(line)...)
341			if i < len(comment.Comment)-1 {
342				p._requestNewline()
343			}
344		}
345		p.pos = comment.End()
346	}
347}
348
349// Print any comments that occur after the last token, and a trailing newline
350func (p *printer) flush() {
351	for _, c := range p.skippedComments {
352		if !p.requestNewlinesForPos(c.Pos()) {
353			p.requestSpace()
354		}
355		p.printComment(c)
356	}
357	for p.curComment < len(p.comments) {
358		p.printComment(p.comments[p.curComment])
359		p.curComment++
360	}
361	p.output = append(p.output, '\n')
362}
363
364// Print whitespace to pad from column l to column max
365func (p *printer) pad(l int) {
366	if l > len(p.wsBuf) {
367		p.wsBuf = make([]byte, l)
368		for i := range p.wsBuf {
369			p.wsBuf[i] = ' '
370		}
371	}
372	p.output = append(p.output, p.wsBuf[0:l]...)
373}
374
375func (p *printer) indent(i int) {
376	p.indentList = append(p.indentList, i)
377}
378
379func (p *printer) unindent(pos scanner.Position) {
380	p.printEndOfLineCommentsBefore(pos)
381	p.indentList = p.indentList[0 : len(p.indentList)-1]
382}
383
384func (p *printer) curIndent() int {
385	return p.indentList[len(p.indentList)-1]
386}
387
388func max(a, b int) int {
389	if a > b {
390		return a
391	} else {
392		return b
393	}
394}
395