fmt.mx raw
1 package main
2
3 import (
4 "os"
5 )
6
7 const maxLine = int32(80)
8
9 type Printer struct {
10 toks []FTok
11 out []byte
12 pos int32
13 indent int32
14 col int32
15 atBOL bool
16 errs []string
17 caseDepth int32
18 }
19
20 func newPrinter(toks []FTok) (p *Printer) {
21 p = &Printer{
22 toks: toks,
23 out: []byte{:0:4096},
24 errs: []string{:0:4},
25 pos: 0,
26 indent: 0,
27 col: 0,
28 atBOL: true,
29 }
30 return
31 }
32
33 func (p *Printer) errorf(line uint32, msg string) {
34 s := int32(line).String() | ":" | msg
35 p.errs = append(p.errs, s)
36 }
37
38 func (p *Printer) emit(s string) {
39 p.out = append(p.out, s...)
40 for i := int32(0); i < int32(len(s)); i++ {
41 if s[i] == '\n' {
42 p.col = 0
43 p.atBOL = true
44 } else if s[i] == '\t' {
45 p.col += 4 - (p.col % 4)
46 } else {
47 p.col++
48 }
49 }
50 }
51
52 func (p *Printer) newline() { p.emit("\n"); p.atBOL = true }
53
54 func (p *Printer) writeIndent() {
55 for i := int32(0); i < p.indent; i++ { p.emit("\t") }
56 p.atBOL = false
57 }
58
59 func (p *Printer) space() { p.emit(" ") }
60
61 func (p *Printer) cur() (t *FTok) { t = &p.toks[p.pos]; return }
62
63 func (p *Printer) peek(off int32) (t *FTok) {
64 idx := p.pos + off
65 if idx < 0 { idx = 0 }
66 if idx >= int32(len(p.toks)) { idx = int32(len(p.toks)) - 1 }
67 t = &p.toks[idx]
68 return
69 }
70
71 func (p *Printer) advance() {
72 if p.pos < int32(len(p.toks))-1 { p.pos++ }
73 }
74
75 // --- matching ---
76
77 func matchBrace(toks []FTok, from int32) (end int32) {
78 depth := int32(1)
79 i := from + 1
80 for i < int32(len(toks)) {
81 tt := toks[i].tok
82 if tt == TokLbrace { depth++ } else if tt == TokRbrace {
83 depth--
84 if depth == 0 { end = i; return }
85 } else if tt == TokEOF { end = i; return }
86 i++
87 }
88 end = int32(len(toks)) - 1
89 return
90 }
91
92 func matchBrack(toks []FTok, from int32) (end int32) {
93 depth := int32(1)
94 i := from + 1
95 for i < int32(len(toks)) {
96 tt := toks[i].tok
97 if tt == TokLbrack { depth++ } else if tt == TokRbrack {
98 depth--
99 if depth == 0 { end = i; return }
100 } else if tt == TokEOF { end = i; return }
101 i++
102 }
103 end = int32(len(toks)) - 1
104 return
105 }
106
107 func matchParen(toks []FTok, from int32) (end int32) {
108 depth := int32(1)
109 i := from + 1
110 for i < int32(len(toks)) {
111 tt := toks[i].tok
112 if tt == TokLparen { depth++ } else if tt == TokRparen {
113 depth--
114 if depth == 0 { end = i; return }
115 } else if tt == TokEOF { end = i; return }
116 i++
117 }
118 end = int32(len(toks)) - 1
119 return
120 }
121
122 // --- width measurement ---
123
124 func singleLineWidth(toks []FTok, from int32, to int32) (w int32) {
125 w = 0
126 for i := from; i <= to; i++ {
127 t := &toks[i]
128 if t.tok == TokComment { w = maxLine + 1; return }
129 if t.tok == TokSemi { w += 2; continue }
130 txt := tokText(t)
131 w += int32(len(txt))
132 if i < to {
133 next := &toks[i+1]
134 if needSpace(t, next) { w++ }
135 }
136 }
137 return
138 }
139
140 func parenFitsLine(toks []FTok, openIdx int32, startCol int32) (yes bool) {
141 end := matchParen(toks, openIdx)
142 w := int32(1)
143 for i := openIdx + 1; i <= end; i++ {
144 t := &toks[i]
145 if t.tok == TokSemi { w += 2; continue }
146 if t.tok == TokComment { return }
147 w += int32(len(tokText(t)))
148 if i < end {
149 next := &toks[i+1]
150 if needSpace(t, next) { w++ }
151 }
152 }
153 yes = startCol + w <= maxLine
154 return
155 }
156
157 func hasInnerBrace(toks []FTok, from int32, to int32) (yes bool) {
158 for i := from; i <= to; i++ {
159 if toks[i].tok == TokLbrace { yes = true; return }
160 }
161 return
162 }
163
164 // --- binary/unary disambiguation ---
165
166 func isBinaryMulStar(toks []FTok, idx int32) (yes bool) {
167 if idx <= 0 { return }
168 prev := toks[idx-1].tok
169 if prev != TokName && prev != TokLit && prev != TokRparen &&
170 prev != TokRbrack && prev != TokRbrace { return }
171 if idx < 2 { yes = true; return }
172 pp := toks[idx-2].tok
173 if pp == TokVar || pp == TokConst || pp == TokType || pp == TokComma ||
174 pp == TokFunc || pp == TokChan || pp == TokMap || pp == TokReturn ||
175 pp == TokAssign || pp == TokDefine || pp == TokArrow ||
176 pp == TokColon || pp == TokLparen || pp == TokLbrack ||
177 pp == TokCase || pp == TokSemi || pp == TokLbrace {
178 return
179 }
180 yes = true
181 return
182 }
183
184 func isBinaryArith(toks []FTok, idx int32) (yes bool) {
185 if idx <= 0 { return }
186 prev := toks[idx-1].tok
187 if prev == TokName || prev == TokLit || prev == TokRparen ||
188 prev == TokRbrack || prev == TokRbrace {
189 yes = true
190 }
191 return
192 }
193
194 // --- brace spacing ---
195
196 func braceNeedsSpace(toks []FTok, braceIdx int32) (yes bool) {
197 if braceIdx < 1 { return }
198 prev := toks[braceIdx-1].tok
199 if prev != TokName && prev != TokRparen { return }
200 if prev == TokRparen { yes = true; return }
201 for i := braceIdx - 1; i >= 0; i-- {
202 tt := toks[i].tok
203 if tt == TokFunc || tt == TokIf || tt == TokFor || tt == TokElse ||
204 tt == TokSelect || tt == TokSwitch {
205 yes = true; return
206 }
207 if tt == TokSemi || tt == TokLbrace || tt == TokRbrace || tt == TokEOF { return }
208 }
209 return
210 }
211
212 func isSliceColonCtx(toks []FTok, idx int32) (yes bool) {
213 bdepth := int32(0)
214 pdepth := int32(0)
215 for i := idx - 1; i >= 0; i-- {
216 tt := toks[i].tok
217 if tt == TokRbrack { bdepth++ } else if tt == TokLbrack {
218 if bdepth == 0 { yes = true; return }
219 bdepth--
220 } else if tt == TokRbrace { pdepth++ } else if tt == TokLbrace {
221 if pdepth == 0 { yes = true; return }
222 pdepth--
223 } else if tt == TokSemi { return }
224 }
225 return
226 }
227
228 // --- spacing ---
229
230 func needSpaceCtx(toks []FTok, curIdx int32, prev *FTok, cur *FTok) (yes bool) {
231 if cur.tok == TokLbrack && prev.tok == TokName && curIdx+1 < int32(len(toks)) {
232 next := toks[curIdx+1].tok
233 if next == TokRbrack {
234 rbIdx := curIdx + 2
235 if rbIdx < int32(len(toks)) {
236 after := toks[rbIdx].tok
237 if after == TokName || after == TokStar || after == TokLbrack ||
238 after == TokFunc || after == TokMap || after == TokChan ||
239 after == TokStruct || after == TokInterface {
240 yes = true; return
241 }
242 }
243 }
244 if next == TokLit {
245 end := matchBrack(toks, curIdx)
246 if end+1 < int32(len(toks)) {
247 after := toks[end+1].tok
248 if after == TokName || after == TokStar || after == TokLbrack ||
249 after == TokFunc || after == TokMap || after == TokChan ||
250 after == TokStruct || after == TokInterface {
251 yes = true; return
252 }
253 }
254 }
255 }
256 if cur.tok == TokLbrace && braceNeedsSpace(toks, curIdx) {
257 yes = true; return
258 }
259 if prev.tok == TokStar && isBinaryMulStar(toks, curIdx-1) {
260 yes = true; return
261 }
262 if cur.tok == TokStar && isBinaryMulStar(toks, curIdx) {
263 yes = true; return
264 }
265 if prev.tok == TokOp && (prev.lit == "-" || prev.lit == "+" || prev.lit == "^") {
266 if !isBinaryArith(toks, curIdx-1) { return }
267 }
268 if prev.tok == TokOp && (prev.lit == "&" || prev.lit == "!") {
269 if !isBinaryArith(toks, curIdx-1) { return }
270 }
271 if cur.tok == TokColon && isSliceColonCtx(toks, curIdx) { return }
272 if prev.tok == TokColon && isSliceColonCtx(toks, curIdx-1) { return }
273 yes = needSpaceBefore(prev, cur)
274 return
275 }
276
277 func needSpaceBefore(prev *FTok, cur *FTok) (yes bool) {
278 if cur.tok == TokLbrace {
279 pt := prev.tok
280 if pt == TokStruct || pt == TokInterface || pt == TokRbrack ||
281 pt == TokName || pt == TokStar { return }
282 yes = true; return
283 }
284 yes = needSpace(prev, cur)
285 return
286 }
287
288 func needSpace(prev *FTok, next *FTok) (yes bool) {
289 pt := prev.tok
290 nt := next.tok
291
292 if pt == TokComment || nt == TokComment { yes = true; return }
293 if nt == TokIncOp { return }
294
295 if nt == TokLparen {
296 if pt == TokName || pt == TokRparen || pt == TokRbrack { return }
297 yes = true; return
298 }
299 if nt == TokLbrack {
300 if pt == TokName || pt == TokRparen || pt == TokRbrack || pt == TokLit { return }
301 }
302 if pt == TokRbrack {
303 if nt == TokName || nt == TokStar || nt == TokLbrack || nt == TokMap ||
304 nt == TokStruct || nt == TokInterface || nt == TokFunc || nt == TokChan { return }
305 }
306 if pt == TokLparen || pt == TokLbrack || nt == TokRparen || nt == TokRbrack { return }
307 if nt == TokComma || nt == TokSemi || nt == TokColon { return }
308 if pt == TokComma || pt == TokSemi { yes = true; return }
309 if pt == TokColon { yes = true; return }
310 if nt == TokDot || pt == TokDot { return }
311
312 if pt == TokStar {
313 if nt == TokName || nt == TokLparen || nt == TokStar || nt == TokLbrack { return }
314 yes = true; return
315 }
316 if nt == TokStar {
317 if pt == TokLparen || pt == TokComma || pt == TokLbrack ||
318 pt == TokFunc || pt == TokChan || pt == TokMap ||
319 pt == TokArrow || pt == TokAssign || pt == TokDefine ||
320 pt == TokReturn || pt == TokCase || pt == TokOp ||
321 pt == TokColon || pt == TokDotDotDot { return }
322 yes = true; return
323 }
324
325 if pt == TokOp && prev.lit == "!" { return }
326
327 if nt == TokLbrace {
328 pt2 := prev.tok
329 if pt2 == TokStruct || pt2 == TokInterface || pt2 == TokRbrack || pt2 == TokName { return }
330 yes = true; return
331 }
332 if pt == TokLbrace || nt == TokRbrace { yes = true; return }
333 if pt == TokOp || pt == TokAssign || pt == TokDefine || pt == TokAssignOp ||
334 pt == TokArrow { yes = true; return }
335 if nt == TokOp || nt == TokAssign || nt == TokDefine || nt == TokAssignOp ||
336 nt == TokArrow { yes = true; return }
337 if pt == TokIncOp { yes = true; return }
338 if pt == TokDotDotDot || nt == TokDotDotDot { return }
339
340 yes = true
341 return
342 }
343
344 // --- predicates ---
345
346 func isEmptyBrace(toks []FTok, bracePos int32) (yes bool) {
347 end := matchBrace(toks, bracePos)
348 for i := bracePos + 1; i < end; i++ {
349 if toks[i].tok != TokSemi { return }
350 }
351 yes = true; return
352 }
353
354 func fitsOneLine(toks []FTok, bracePos int32, preambleCol int32) (yes bool) {
355 end := matchBrace(toks, bracePos)
356 innerFrom := bracePos + 1
357 innerTo := end - 1
358 if innerTo < innerFrom { yes = true; return }
359 for i := innerFrom; i <= innerTo; i++ {
360 if toks[i].tok == TokComment { return }
361 }
362 if hasInnerBrace(toks, innerFrom, innerTo) { return }
363 w := singleLineWidth(toks, innerFrom, innerTo)
364 total := preambleCol + 2 + w + 2
365 yes = total <= maxLine
366 return
367 }
368
369 func isGroupKeyword(tok Token) (yes bool) {
370 if tok == TokImport || tok == TokConst || tok == TokVar || tok == TokType { yes = true }
371 return
372 }
373
374 func isImportContext(p *Printer) (yes bool) {
375 for i := p.pos - 1; i >= 0; i-- {
376 t := &p.toks[i]
377 if t.tok == TokImport { yes = true; return }
378 if t.tok == TokSemi || t.tok == TokRbrace || t.tok == TokLbrace { return }
379 }
380 return
381 }
382
383 func isLabel(toks []FTok, colonIdx int32) (yes bool) {
384 if colonIdx < 1 { return }
385 if toks[colonIdx-1].tok != TokName { return }
386 if colonIdx < 2 { yes = true; return }
387 pp := toks[colonIdx-2].tok
388 if pp == TokSemi || pp == TokLbrace || pp == TokColon { yes = true }
389 return
390 }
391
392 func isSliceLitBrace(toks []FTok, bracePos int32) (yes bool) {
393 end := matchBrace(toks, bracePos)
394 for i := bracePos + 1; i < end; i++ {
395 if toks[i].tok == TokSemi { continue }
396 if toks[i].tok == TokColon { yes = true }
397 return
398 }
399 return
400 }
401
402 // --- bare return type diagnostic ---
403
404 func checkBareReturns(toks []FTok, p *Printer) {
405 i := int32(0)
406 for i < int32(len(toks)) {
407 if toks[i].tok == TokFunc {
408 checkFuncSig(toks, i, p)
409 }
410 i++
411 }
412 }
413
414 func checkFuncSig(toks []FTok, funcIdx int32, p *Printer) {
415 i := funcIdx + 1
416 n := int32(len(toks))
417 if i >= n { return }
418 if toks[i].tok == TokLparen {
419 i = matchParen(toks, i) + 1
420 }
421 if i >= n { return }
422 if toks[i].tok != TokName { return }
423 i++
424 if i >= n { return }
425 if toks[i].tok != TokLparen { return }
426 i = matchParen(toks, i) + 1
427 if i >= n { return }
428 tt := toks[i].tok
429 if tt == TokLbrace || tt == TokSemi { return }
430 if tt == TokLparen { return }
431 p.errorf(toks[funcIdx].line, " bare return type (wrap in parens with name)")
432 }
433
434 // --- line-break after comma at 80+ ---
435
436 func (p *Printer) emitSpaceOrBreak(prev *FTok) {
437 if prev.tok == TokComma && p.col >= maxLine {
438 p.newline()
439 p.writeIndent()
440 } else {
441 p.space()
442 }
443 }
444
445 // --- paren group wrapping ---
446
447 func (p *Printer) formatParenGroup() {
448 end := matchParen(p.toks, p.pos)
449 if parenFitsLine(p.toks, p.pos, p.col) {
450 p.emit("(")
451 p.advance()
452 for p.pos < end {
453 t := p.cur()
454 if t.tok == TokSemi { p.advance(); continue }
455 prev := p.peek(-1)
456 if needSpaceCtx(p.toks, p.pos, prev, t) { p.space() }
457 p.emit(tokText(t))
458 p.advance()
459 }
460 p.emit(")")
461 p.advance()
462 return
463 }
464 p.emit("(")
465 p.advance()
466 p.indent++
467
468 needNL2 := true
469 for p.pos < end {
470 t := p.cur()
471 if t.tok == TokSemi { p.advance(); continue }
472 if t.tok == TokComment {
473 p.formatComment(t)
474 p.advance()
475 continue
476 }
477 if t.tok == TokComma {
478 p.emit(",")
479 p.advance()
480 needNL2 = true
481 continue
482 }
483 if needNL2 {
484 p.newline()
485 p.writeIndent()
486 needNL2 = false
487 } else {
488 prev := p.peek(-1)
489 if needSpaceCtx(p.toks, p.pos, prev, t) { p.space() }
490 }
491 if t.tok == TokLparen {
492 p.formatParenGroup()
493 } else {
494 p.emit(tokText(t))
495 p.advance()
496 }
497 }
498
499 p.indent--
500 p.newline()
501 p.writeIndent()
502 p.emit(")")
503 p.advance()
504 }
505
506 // --- main format ---
507
508 func (p *Printer) format() {
509 for p.pos < int32(len(p.toks)) {
510 t := p.cur()
511 if t.tok == TokEOF { break }
512 if t.tok == TokComment { p.formatComment(t); p.advance(); continue }
513 if t.tok == TokSemi { p.advance(); continue }
514
515 if t.tok == TokElse && p.pos > 0 {
516 prev := p.peek(-1)
517 if prev.tok == TokRbrace {
518 p.space()
519 p.emit(tokText(t))
520 p.advance()
521 continue
522 }
523 }
524
525 if t.nlBefore && p.pos > 0 {
526 p.ensureNewline()
527 if t.blankBef { p.newline() }
528 p.writeIndent()
529 } else if p.atBOL {
530 p.writeIndent()
531 } else if p.pos > 0 {
532 prev := p.peek(-1)
533 if needSpaceCtx(p.toks, p.pos, prev, t) { p.space() }
534 }
535
536 if t.tok == TokLbrace {
537 p.formatBrace()
538 } else if t.tok == TokLparen && p.pos > 0 && isGroupKeyword(p.peek(-1).tok) {
539 p.formatGroupBlock()
540 } else if t.tok == TokLparen && !parenFitsLine(p.toks, p.pos, p.col) {
541 p.formatParenGroup()
542 } else {
543 p.emit(tokText(t))
544 p.advance()
545 }
546 }
547
548 if len(p.out) > 0 && p.out[len(p.out)-1] != '\n' { p.newline() }
549 }
550
551 func (p *Printer) ensureNewline() {
552 if len(p.out) > 0 && p.out[len(p.out)-1] != '\n' { p.newline() }
553 }
554
555 func (p *Printer) formatComment(t *FTok) {
556 if t.nlBefore && p.pos > 0 {
557 p.ensureNewline()
558 if t.blankBef { p.newline() }
559 p.writeIndent()
560 } else if p.atBOL {
561 p.writeIndent()
562 } else {
563 p.space()
564 }
565 p.emit(t.lit)
566 }
567
568 // --- brace formatting ---
569
570 func (p *Printer) formatBrace() {
571 if fitsOneLine(p.toks, p.pos, p.col) && !isImportContext(p) {
572 p.formatBraceSingleLine()
573 } else {
574 p.formatBraceMultiLine()
575 }
576 }
577
578 func (p *Printer) formatBraceSingleLine() {
579 end := matchBrace(p.toks, p.pos)
580 p.emit("{")
581 p.advance()
582
583 if isEmptyBrace(p.toks, p.pos-1) {
584 for p.pos < end { p.advance() }
585 p.emit("}")
586 p.advance()
587 return
588 }
589
590 sliceLit := isSliceLitBrace(p.toks, p.pos-1)
591 if !sliceLit { p.space() }
592 first := true
593 for p.pos < end {
594 t := p.cur()
595 if t.tok == TokSemi {
596 next := p.peek(1)
597 if next.tok != TokRbrace && p.pos+1 < end { p.emit("; ") }
598 p.advance()
599 first = true
600 continue
601 }
602 if !first {
603 prev := p.peek(-1)
604 if prev.tok != TokSemi && needSpaceCtx(p.toks, p.pos, prev, t) { p.space() }
605 }
606 first = false
607 p.emit(tokText(t))
608 p.advance()
609 }
610 if !sliceLit { p.space() }
611 p.emit("}")
612 p.advance()
613 }
614
615 func (p *Printer) formatBraceMultiLine() {
616 end := matchBrace(p.toks, p.pos)
617 p.emit("{")
618 p.advance()
619 p.indent++
620
621 caseIndented := false
622 caseAwait := false
623 needNL := true
624 for p.pos < end {
625 t := p.cur()
626
627 if t.tok == TokSemi {
628 if t.lit == ";" {
629 p.emit(";")
630 needNL = false
631 } else {
632 needNL = true
633 }
634 p.advance()
635 continue
636 }
637
638 if t.tok == TokComment {
639 p.formatComment(t)
640 needNL = true
641 p.advance()
642 continue
643 }
644
645 if t.tok == TokElse {
646 p.space()
647 p.emit(tokText(t))
648 p.advance()
649 needNL = false
650 continue
651 }
652
653 if (t.tok == TokCase || t.tok == TokDefault) && needNL {
654 if caseIndented { p.indent--; caseIndented = false }
655 caseAwait = true
656 p.ensureNewline()
657 if t.blankBef { p.newline() }
658 p.writeIndent()
659 needNL = false
660 p.emit(tokText(t))
661 p.advance()
662 continue
663 }
664
665 if needNL {
666 p.ensureNewline()
667 if t.blankBef { p.newline() }
668 p.writeIndent()
669 needNL = false
670 } else {
671 prev := p.peek(-1)
672 if prev.tok == TokComma && p.col >= maxLine {
673 p.newline()
674 p.writeIndent()
675 } else if needSpaceCtx(p.toks, p.pos, prev, t) {
676 p.space()
677 }
678 }
679
680 if t.tok == TokColon && caseAwait {
681 p.emit(":")
682 p.advance()
683 caseAwait = false
684 caseIndented = true
685 p.indent++
686 needNL = true
687 } else if t.tok == TokColon && isLabel(p.toks, p.pos) {
688 p.emit(":")
689 p.advance()
690 needNL = true
691 } else if t.tok == TokLbrace {
692 p.formatBrace()
693 needNL = true
694 } else if t.tok == TokLparen && p.pos > 0 && isGroupKeyword(p.peek(-1).tok) {
695 p.formatGroupBlock()
696 } else if t.tok == TokLparen && !parenFitsLine(p.toks, p.pos, p.col) {
697 p.formatParenGroup()
698 } else {
699 p.emit(tokText(t))
700 p.advance()
701 }
702 }
703
704 if caseIndented { p.indent-- }
705 p.indent--
706 p.ensureNewline()
707 p.writeIndent()
708 p.emit("}")
709 p.advance()
710 }
711
712 // --- group block (import/const/var/type) ---
713
714 func (p *Printer) formatGroupBlock() {
715 end := matchParen(p.toks, p.pos)
716 p.emit("(")
717 p.advance()
718 p.indent++
719
720 needNL := true
721 for p.pos < end {
722 t := p.cur()
723 if t.tok == TokSemi { needNL = true; p.advance(); continue }
724 if t.tok == TokComment {
725 p.formatComment(t)
726 needNL = true
727 p.advance()
728 continue
729 }
730 if needNL {
731 p.ensureNewline()
732 if t.blankBef { p.newline() }
733 p.writeIndent()
734 needNL = false
735 } else {
736 prev := p.peek(-1)
737 if needSpaceCtx(p.toks, p.pos, prev, t) { p.space() }
738 }
739 p.emit(tokText(t))
740 p.advance()
741 }
742
743 p.indent--
744 p.ensureNewline()
745 p.writeIndent()
746 p.emit(")")
747 p.advance()
748 }
749
750 // --- entry ---
751
752 func formatSource(src string) (out string, errs []string) {
753 toks := tokenize(src)
754 p := newPrinter(toks)
755 checkBareReturns(toks, p)
756 p.format()
757 out = alignOutput(string(p.out))
758 errs = p.errs
759 return
760 }
761
762 func reportErrors(path string, errs []string) {
763 for i := int32(0); i < int32(len(errs)); i++ {
764 os.Stderr().Write([]byte(path | ":" | errs[i] | "\n"))
765 }
766 }
767