2026-05-30 20:14:24 +02:00
|
|
|
package treerack
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
|
|
|
|
"fmt"
|
|
|
|
|
"io"
|
|
|
|
|
"strings"
|
|
|
|
|
"unicode"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const initialTargetWidth = 112
|
|
|
|
|
|
|
|
|
|
type commentFormat int
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
commentFormatNone commentFormat = iota
|
|
|
|
|
standaloneComment
|
|
|
|
|
headerComment
|
|
|
|
|
suffixComment
|
|
|
|
|
inlineComment
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type formatItem struct {
|
|
|
|
|
commentFormat commentFormat
|
2026-06-01 22:26:27 +02:00
|
|
|
node Node
|
2026-05-30 20:14:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type formatGroup struct {
|
|
|
|
|
items []formatItem
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-01 22:26:27 +02:00
|
|
|
func topLevelCommentFormat(ast Node, i int, n Node) commentFormat {
|
2026-05-30 20:14:24 +02:00
|
|
|
if n.Name != "comment" {
|
|
|
|
|
return commentFormatNone
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if i > 0 &&
|
|
|
|
|
ast.Nodes[i-1].Name != "comment" &&
|
|
|
|
|
!strings.Contains(string(ast.tokens[ast.Nodes[i-1].To:n.From]), "\n") {
|
|
|
|
|
return suffixComment
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(ast.Nodes) == i+1 {
|
|
|
|
|
return standaloneComment
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
next := ast.Nodes[i+1]
|
|
|
|
|
if next.Name == "comment" {
|
|
|
|
|
return standaloneComment
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var lines int
|
|
|
|
|
space := ast.tokens[n.To:next.From]
|
|
|
|
|
for _, s := range space {
|
|
|
|
|
if s == '\n' {
|
|
|
|
|
lines++
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if lines >= 2 {
|
|
|
|
|
return standaloneComment
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return headerComment
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-01 22:26:27 +02:00
|
|
|
func groupASTByComments(ast Node) []formatGroup {
|
2026-05-30 20:14:24 +02:00
|
|
|
var (
|
|
|
|
|
groups []formatGroup
|
|
|
|
|
currentGroup formatGroup
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
for i, n := range ast.Nodes {
|
|
|
|
|
last := len(currentGroup.items) - 1
|
|
|
|
|
cf := topLevelCommentFormat(ast, i, n)
|
|
|
|
|
item := formatItem{
|
|
|
|
|
commentFormat: cf,
|
|
|
|
|
node: n,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if cf == commentFormatNone {
|
|
|
|
|
if last >= 0 && currentGroup.items[last].commentFormat == standaloneComment {
|
|
|
|
|
groups = append(groups, currentGroup)
|
|
|
|
|
currentGroup.items = nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
currentGroup.items = append(currentGroup.items, item)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if cf == suffixComment {
|
|
|
|
|
currentGroup.items = append(currentGroup.items, item)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if last >= 0 {
|
|
|
|
|
groups = append(groups, currentGroup)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
currentGroup.items = []formatItem{item}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
groups = append(groups, currentGroup)
|
|
|
|
|
return groups
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-01 20:28:39 +02:00
|
|
|
func trimComment(text string, withBlockBody bool) string {
|
|
|
|
|
var inBlockComment, inLineComment, newBlockLine bool
|
2026-05-30 20:14:24 +02:00
|
|
|
tr := []rune(text)
|
|
|
|
|
rr := make([]rune, 0, len(tr))
|
|
|
|
|
for i := 0; i < len(tr); i++ {
|
|
|
|
|
r := tr[i]
|
|
|
|
|
if inBlockComment {
|
2026-06-01 20:28:39 +02:00
|
|
|
if withBlockBody && r == '\n' {
|
|
|
|
|
rr = append(rr, '\n')
|
|
|
|
|
newBlockLine = true
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if withBlockBody && newBlockLine && unicode.IsSpace(r) {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
newBlockLine = false
|
2026-05-30 20:14:24 +02:00
|
|
|
if r != '*' || len(tr) <= i+1 || tr[i+1] != '/' {
|
|
|
|
|
rr = append(rr, r)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rr = append(rr, '*', '/')
|
|
|
|
|
inBlockComment = false
|
|
|
|
|
if len(tr) > i+2 && !unicode.IsSpace(tr[i+2]) {
|
|
|
|
|
rr = append(rr, ' ')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
i++
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if inLineComment {
|
|
|
|
|
rr = append(rr, r)
|
|
|
|
|
inLineComment = r != '\n'
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if r == '/' && len(tr) > i+1 && tr[i+1] == '*' {
|
|
|
|
|
rr = append(rr, '/', '*')
|
|
|
|
|
inBlockComment = true
|
|
|
|
|
i++
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if r == '/' && len(tr) > i+1 && tr[i+1] == '/' {
|
|
|
|
|
rr = append(rr, '/', '/')
|
|
|
|
|
inLineComment = true
|
|
|
|
|
if len(tr) > i+2 && tr[i+2] != ' ' {
|
|
|
|
|
rr = append(rr, ' ')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
i++
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if r == '\n' || len(rr) > 0 && !unicode.IsSpace(rr[len(rr)-1]) {
|
|
|
|
|
rr = append(rr, r)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lines := strings.Split(string(rr), "\n")
|
|
|
|
|
for i := range lines {
|
|
|
|
|
lines[i] = strings.TrimRightFunc(lines[i], unicode.IsSpace)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return strings.Join(lines, "\n")
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-01 22:26:27 +02:00
|
|
|
func formatComment(out io.Writer, n Node, withBlockBody bool) error {
|
2026-05-30 20:14:24 +02:00
|
|
|
text := n.Text()
|
2026-06-01 20:28:39 +02:00
|
|
|
text = trimComment(text, withBlockBody)
|
2026-05-30 20:14:24 +02:00
|
|
|
_, err := fmt.Fprint(out, text)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func formatDefinitionName(item formatItem) string {
|
|
|
|
|
name := item.node.Nodes[0].Text()
|
|
|
|
|
flags := make([]string, 0, len(item.node.Nodes)-2)
|
|
|
|
|
for i := 1; i < len(item.node.Nodes)-1; i++ {
|
2026-06-01 20:28:39 +02:00
|
|
|
if item.node.Nodes[i].Name == "comment" {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-30 20:14:24 +02:00
|
|
|
flags = append(flags, item.node.Nodes[i].Name)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(flags) > 0 {
|
|
|
|
|
name += ":" + strings.Join(flags, ":")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return name
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func formatItemNames(g formatGroup) ([]string, int) {
|
|
|
|
|
var maxWidth int
|
|
|
|
|
ordered := make([]string, 0, len(g.items))
|
|
|
|
|
for _, item := range g.items {
|
|
|
|
|
if item.commentFormat != commentFormatNone {
|
|
|
|
|
ordered = append(ordered, "")
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
name := formatDefinitionName(item)
|
|
|
|
|
maxWidth = max(maxWidth, len(name))
|
|
|
|
|
ordered = append(ordered, name)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ordered, maxWidth
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func formatAnyChar(out io.Writer) error {
|
|
|
|
|
_, err := fmt.Fprint(out, ".")
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-01 22:26:27 +02:00
|
|
|
func formatCharClass(out io.Writer, n Node) error {
|
2026-05-30 20:14:24 +02:00
|
|
|
_, err := fmt.Fprint(out, n.Text())
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-01 22:26:27 +02:00
|
|
|
func formatCharSequence(out io.Writer, n Node) error {
|
2026-05-30 20:14:24 +02:00
|
|
|
_, err := fmt.Fprint(out, n.Text())
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-01 22:26:27 +02:00
|
|
|
func formatSymbol(out io.Writer, n Node) error {
|
2026-05-30 20:14:24 +02:00
|
|
|
_, err := fmt.Fprint(out, n.Text())
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func decTargetWidth(w, by int) int {
|
|
|
|
|
if w <= 0 {
|
|
|
|
|
return w
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
w -= by
|
|
|
|
|
if w < 0 {
|
|
|
|
|
w = 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return w
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-01 22:26:27 +02:00
|
|
|
func formatSequenceItemNode(out io.Writer, targetWidth int, n Node) error {
|
2026-05-30 20:14:24 +02:00
|
|
|
var (
|
|
|
|
|
min, max int
|
|
|
|
|
err error
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
fprint := func(a ...any) {
|
|
|
|
|
if err != nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_, err = fmt.Fprint(out, a...)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(n.Nodes) == 2 {
|
|
|
|
|
if min, max, err = getQuantity(n.Nodes[1]); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
min, max = normalizeItemRange(min, max)
|
|
|
|
|
needsQuantifier := min != 1 || max != 1
|
|
|
|
|
isChoice := n.Nodes[0].Name == "choice"
|
|
|
|
|
isChoiceOfMultiple := isChoice && len(n.Nodes[0].Nodes) > 1
|
|
|
|
|
isSequence := n.Nodes[0].Name == "sequence"
|
|
|
|
|
isSequenceOfMultiple := isSequence && len(n.Nodes[0].Nodes) > 1
|
|
|
|
|
needsGrouping := isChoiceOfMultiple || isSequenceOfMultiple
|
|
|
|
|
if needsGrouping {
|
|
|
|
|
var buf bytes.Buffer
|
|
|
|
|
targetWidth = decTargetWidth(targetWidth, 2)
|
|
|
|
|
if err := formatExpression(&buf, targetWidth, n.Nodes[0]); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
multiline := strings.Contains(buf.String(), "\n")
|
|
|
|
|
if multiline {
|
|
|
|
|
lines := strings.Split(buf.String(), "\n")
|
|
|
|
|
fprint("( ")
|
|
|
|
|
fprint(lines[0])
|
|
|
|
|
for _, l := range lines[1:] {
|
|
|
|
|
fprint("\n ")
|
|
|
|
|
fprint(l)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fprint("\n )")
|
|
|
|
|
} else {
|
|
|
|
|
fprint("(")
|
|
|
|
|
if _, err := io.Copy(out, &buf); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fprint(")")
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if err := formatExpression(out, targetWidth, n.Nodes[0]); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !needsQuantifier {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if min == 0 && max == 1 {
|
|
|
|
|
fprint("?")
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if min == 0 && max < 0 {
|
|
|
|
|
fprint("*")
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if min == 1 && max < 0 {
|
|
|
|
|
fprint("+")
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fprint("{")
|
|
|
|
|
if min == max {
|
|
|
|
|
fprint(min)
|
|
|
|
|
} else {
|
|
|
|
|
if min > 0 {
|
|
|
|
|
fprint(min)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fprint(",")
|
|
|
|
|
if max >= 0 {
|
|
|
|
|
fprint(max)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-01 21:29:33 +02:00
|
|
|
fprint("}")
|
2026-05-30 20:14:24 +02:00
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-01 22:26:27 +02:00
|
|
|
func formatSequenceItemNodes(out io.Writer, targetWidth int, n []Node) error {
|
2026-05-30 20:14:24 +02:00
|
|
|
sep := " "
|
|
|
|
|
if targetWidth >= 0 {
|
|
|
|
|
sep = "\n "
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for i, ni := range n {
|
|
|
|
|
if i > 0 {
|
|
|
|
|
if _, err := fmt.Fprint(out, sep); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ni.Name == "comment" {
|
2026-06-01 20:28:39 +02:00
|
|
|
if err := formatComment(out, ni, true); err != nil {
|
2026-05-30 20:14:24 +02:00
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := formatSequenceItemNode(out, targetWidth, ni); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-01 22:26:27 +02:00
|
|
|
func formatSequence(out io.Writer, targetWidth int, n []Node) error {
|
2026-06-01 20:28:39 +02:00
|
|
|
var hasComments bool
|
|
|
|
|
for _, ni := range n {
|
|
|
|
|
if ni.Name == "comment" {
|
|
|
|
|
hasComments = true
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if hasComments {
|
|
|
|
|
return formatSequenceItemNodes(out, 0, n)
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-30 20:14:24 +02:00
|
|
|
var buf bytes.Buffer
|
|
|
|
|
if err := formatSequenceItemNodes(&buf, -1, n); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if targetWidth >= 0 && buf.Len() > targetWidth {
|
|
|
|
|
(&buf).Reset()
|
|
|
|
|
if err := formatSequenceItemNodes(&buf, targetWidth, n); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_, err := io.Copy(out, &buf)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-01 22:26:27 +02:00
|
|
|
func formatChoiceOptionNodes(out io.Writer, targetWidth int, n []Node) error {
|
2026-05-30 20:14:24 +02:00
|
|
|
sep, commentSep := " | ", " "
|
|
|
|
|
if targetWidth >= 0 {
|
|
|
|
|
sep, commentSep = "\n| ", "\n"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for i, ni := range n {
|
|
|
|
|
if ni.Name == "comment" {
|
|
|
|
|
if i > 0 {
|
|
|
|
|
if _, err := fmt.Fprint(out, commentSep); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-01 20:28:39 +02:00
|
|
|
if err := formatComment(out, ni, true); err != nil {
|
2026-05-30 20:14:24 +02:00
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if i > 0 {
|
|
|
|
|
if _, err := fmt.Fprint(out, sep); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := formatExpression(out, targetWidth, ni); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-01 22:26:27 +02:00
|
|
|
func formatChoice(out io.Writer, targetWidth int, n []Node) error {
|
2026-06-01 20:28:39 +02:00
|
|
|
var hasComments bool
|
|
|
|
|
for _, ni := range n {
|
|
|
|
|
if ni.Name == "comment" {
|
|
|
|
|
hasComments = true
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if hasComments {
|
|
|
|
|
return formatChoiceOptionNodes(out, 0, n)
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-30 20:14:24 +02:00
|
|
|
var buf bytes.Buffer
|
|
|
|
|
if err := formatChoiceOptionNodes(&buf, -1, n); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if targetWidth >= 0 && buf.Len() > targetWidth {
|
|
|
|
|
(&buf).Reset()
|
|
|
|
|
if err := formatChoiceOptionNodes(&buf, targetWidth, n); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_, err := io.Copy(out, &buf)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-01 22:26:27 +02:00
|
|
|
func formatExpression(out io.Writer, targetWidth int, n Node) error {
|
2026-05-30 20:14:24 +02:00
|
|
|
var err error
|
|
|
|
|
switch n.Name {
|
|
|
|
|
case "comment":
|
2026-06-01 20:28:39 +02:00
|
|
|
err = formatComment(out, n, true)
|
2026-05-30 20:14:24 +02:00
|
|
|
case "any-char":
|
|
|
|
|
err = formatAnyChar(out)
|
|
|
|
|
case "char-class":
|
|
|
|
|
err = formatCharClass(out, n)
|
|
|
|
|
case "char-sequence":
|
|
|
|
|
err = formatCharSequence(out, n)
|
|
|
|
|
case "symbol":
|
|
|
|
|
err = formatSymbol(out, n)
|
|
|
|
|
case "sequence":
|
|
|
|
|
err = formatSequence(out, targetWidth, n.Nodes)
|
|
|
|
|
case "choice":
|
|
|
|
|
err = formatChoice(out, targetWidth, n.Nodes)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-01 22:26:27 +02:00
|
|
|
func formatDefinition(out io.Writer, targetWidth, namesWidth int, pad, name string, n Node) error {
|
2026-06-01 20:28:39 +02:00
|
|
|
var (
|
|
|
|
|
buf bytes.Buffer
|
|
|
|
|
foundComment bool
|
|
|
|
|
err error
|
|
|
|
|
)
|
2026-05-30 20:14:24 +02:00
|
|
|
|
|
|
|
|
fprint := func(a ...any) {
|
|
|
|
|
if err != nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_, err = fmt.Fprint(out, a...)
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-01 20:28:39 +02:00
|
|
|
fprintBuf := func(a ...any) {
|
|
|
|
|
if err != nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_, err = fmt.Fprint(&buf, a...)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fprint(name, pad[:namesWidth-len(name)], " = ")
|
|
|
|
|
for _, n := range n.Nodes {
|
|
|
|
|
if n.Name != "comment" {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if foundComment {
|
|
|
|
|
fprintBuf("\n")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foundComment = true
|
|
|
|
|
if err := formatComment(&buf, n, true); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if foundComment {
|
|
|
|
|
lines := strings.Split(buf.String(), "\n")
|
|
|
|
|
(&buf).Reset()
|
|
|
|
|
for _, l := range lines {
|
|
|
|
|
fprint(l, "\n", pad, " ")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
targetWidth = decTargetWidth(targetWidth, namesWidth+3)
|
|
|
|
|
if err := formatExpression(&buf, targetWidth, n.Nodes[len(n.Nodes)-1]); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-30 20:14:24 +02:00
|
|
|
lines := strings.Split(buf.String(), "\n")
|
|
|
|
|
fprint(lines[0])
|
|
|
|
|
for _, l := range lines[1:] {
|
|
|
|
|
fprint("\n ")
|
|
|
|
|
fprint(pad)
|
|
|
|
|
fprint(l)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fprint(";")
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func formatASTGroup(out io.Writer, g formatGroup) error {
|
|
|
|
|
if g.items[0].commentFormat == standaloneComment {
|
2026-06-01 20:28:39 +02:00
|
|
|
return formatComment(out, g.items[0].node, false)
|
2026-05-30 20:14:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hasHeaderComment := g.items[0].commentFormat == headerComment
|
|
|
|
|
if hasHeaderComment {
|
2026-06-01 20:28:39 +02:00
|
|
|
if err := formatComment(out, g.items[0].node, false); err != nil {
|
2026-05-30 20:14:24 +02:00
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
g.items = g.items[1:]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
names, namesWidth := formatItemNames(g)
|
|
|
|
|
pad := strings.Join(make([]string, namesWidth+1), " ")
|
|
|
|
|
for i, item := range g.items {
|
|
|
|
|
name := names[i]
|
|
|
|
|
if item.commentFormat == suffixComment {
|
|
|
|
|
if _, err := fmt.Fprint(out, " "); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-01 20:28:39 +02:00
|
|
|
var buf bytes.Buffer
|
|
|
|
|
if err := formatComment(&buf, item.node, true); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lines := strings.Split(buf.String(), "\n")
|
|
|
|
|
if _, err := fmt.Fprint(out, lines[0]); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, l := range lines[1:] {
|
|
|
|
|
if _, err := fmt.Fprintf(out, "\n %s%s", pad, l); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-30 20:14:24 +02:00
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if hasHeaderComment || i > 0 {
|
|
|
|
|
if _, err := fmt.Fprintln(out); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := formatDefinition(
|
|
|
|
|
out,
|
|
|
|
|
initialTargetWidth,
|
|
|
|
|
namesWidth,
|
|
|
|
|
pad,
|
|
|
|
|
name,
|
|
|
|
|
item.node,
|
|
|
|
|
); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-01 22:26:27 +02:00
|
|
|
func formatAST(out io.Writer, ast Node) error {
|
2026-05-30 20:14:24 +02:00
|
|
|
// drop whitespace comments
|
|
|
|
|
// use line comments by default
|
|
|
|
|
// comment types:
|
|
|
|
|
// - standalone comment
|
|
|
|
|
// - header comment
|
|
|
|
|
// - suffix comment
|
|
|
|
|
// - inline comment
|
|
|
|
|
//
|
|
|
|
|
// standalone comment:
|
|
|
|
|
// - preceeded by definition or at least two empty lines and followed by at least two empty lines
|
|
|
|
|
// - separate it by two empty lines above and below
|
|
|
|
|
//
|
|
|
|
|
// header comment:
|
|
|
|
|
// - separated from the subsequent definition by zero or one empty lines
|
|
|
|
|
// - separate it by two empty lines above and one empty line below
|
|
|
|
|
//
|
|
|
|
|
// suffix comment:
|
|
|
|
|
// - starts on the same line as the definition it belongs to
|
|
|
|
|
// - append to the definition
|
|
|
|
|
// - if it consists of multiple lines, append a new line below
|
|
|
|
|
//
|
|
|
|
|
// inline comment:
|
|
|
|
|
// - it's inside a definition
|
2026-06-01 20:28:39 +02:00
|
|
|
// - if it's outside of the expression, only one, we don't know if it's before or after the eq sign,
|
|
|
|
|
// treat it as after
|
|
|
|
|
// - if it's outside of the expression, put it below the name and the eq sign unindented, and put the
|
|
|
|
|
// expression below the comment or two comments, indented
|
|
|
|
|
// - it it's in the expression, always wrap the expression into lines, and put the comment on its own
|
|
|
|
|
// line
|
2026-05-30 20:14:24 +02:00
|
|
|
|
|
|
|
|
groups := groupASTByComments(ast)
|
|
|
|
|
for i, g := range groups {
|
|
|
|
|
if i > 0 {
|
|
|
|
|
if _, err := fmt.Fprint(out, "\n\n"); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := formatASTGroup(out, g); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-01 21:29:33 +02:00
|
|
|
if _, err := fmt.Fprintln(out); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-30 20:14:24 +02:00
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func formatDefinitions(out io.Writer, s *Syntax) error {
|
|
|
|
|
var o formatOptions
|
|
|
|
|
o.mode = formatPretty
|
|
|
|
|
o.targetWidth = initialTargetWidth
|
|
|
|
|
|
|
|
|
|
var (
|
|
|
|
|
namesWidth int
|
|
|
|
|
orderedDefs []string
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
defs := make(map[string]definition)
|
|
|
|
|
for _, def := range s.registry.definitions {
|
|
|
|
|
if def.commitType()&userDefined == 0 {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
defName := def.nodeName()
|
|
|
|
|
ct := def.commitType()
|
|
|
|
|
ct &^= userDefined
|
2026-06-06 04:50:27 +02:00
|
|
|
if ct&Alias > 0 {
|
|
|
|
|
ct &^= FailPass
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-30 20:14:24 +02:00
|
|
|
if sq, ok := def.(*sequenceDefinition); ok && sq.isCharSequence(s.registry) {
|
|
|
|
|
ct &^= NoWhitespace
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ct != None {
|
|
|
|
|
defName = fmt.Sprintf("%s:%v", defName, ct)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
orderedDefs = append(orderedDefs, defName)
|
|
|
|
|
defs[defName] = def
|
|
|
|
|
namesWidth = max(namesWidth, len([]rune(defName)))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
o.targetWidth = decTargetWidth(o.targetWidth, namesWidth+3)
|
|
|
|
|
pad := strings.Join(make([]string, namesWidth+1), " ")
|
|
|
|
|
for _, name := range orderedDefs {
|
|
|
|
|
def := defs[name]
|
|
|
|
|
f := def.format(s.registry, o)
|
|
|
|
|
lines := strings.Split(f, "\n")
|
|
|
|
|
if _, err := fmt.Fprintf(
|
|
|
|
|
out,
|
|
|
|
|
"%s%s = %s",
|
|
|
|
|
name,
|
|
|
|
|
pad[:namesWidth-len(name)],
|
|
|
|
|
lines[0],
|
|
|
|
|
); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, l := range lines[1:] {
|
|
|
|
|
if _, err := fmt.Fprintf(out, "\n%s %s", pad, l); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if _, err := fmt.Fprint(out, ";\n"); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|