| |
| |
| |
|
|
| |
|
|
| package syntax |
|
|
| import ( |
| "fmt" |
| "io" |
| "strings" |
| ) |
|
|
| |
| type Form uint |
|
|
| const ( |
| _ Form = iota |
| LineForm |
| ShortForm |
| ) |
|
|
| |
| |
| func Fprint(w io.Writer, x Node, form Form) (n int, err error) { |
| p := printer{ |
| output: w, |
| form: form, |
| linebreaks: form == 0, |
| } |
|
|
| defer func() { |
| n = p.written |
| if e := recover(); e != nil { |
| err = e.(writeError).err |
| } |
| }() |
|
|
| p.print(x) |
| p.flush(_EOF) |
|
|
| return |
| } |
|
|
| |
| |
| func String(n Node) string { |
| var buf strings.Builder |
| _, err := Fprint(&buf, n, ShortForm) |
| if err != nil { |
| fmt.Fprintf(&buf, "<<< ERROR: %s", err) |
| } |
| return buf.String() |
| } |
|
|
| type ctrlSymbol int |
|
|
| const ( |
| none ctrlSymbol = iota |
| semi |
| blank |
| newline |
| indent |
| outdent |
| |
| |
| ) |
|
|
| type whitespace struct { |
| last token |
| kind ctrlSymbol |
| |
| } |
|
|
| type printer struct { |
| output io.Writer |
| written int |
| form Form |
| linebreaks bool |
|
|
| indent int |
| nlcount int |
|
|
| pending []whitespace |
| lastTok token |
| } |
|
|
| |
| |
| func (p *printer) write(data []byte) { |
| n, err := p.output.Write(data) |
| p.written += n |
| if err != nil { |
| panic(writeError{err}) |
| } |
| } |
|
|
| var ( |
| tabBytes = []byte("\t\t\t\t\t\t\t\t") |
| newlineByte = []byte("\n") |
| blankByte = []byte(" ") |
| ) |
|
|
| func (p *printer) writeBytes(data []byte) { |
| if len(data) == 0 { |
| panic("expected non-empty []byte") |
| } |
| if p.nlcount > 0 && p.indent > 0 { |
| |
| n := p.indent |
| for n > len(tabBytes) { |
| p.write(tabBytes) |
| n -= len(tabBytes) |
| } |
| p.write(tabBytes[:n]) |
| } |
| p.write(data) |
| p.nlcount = 0 |
| } |
|
|
| func (p *printer) writeString(s string) { |
| p.writeBytes([]byte(s)) |
| } |
|
|
| |
| |
| |
| func impliesSemi(tok token) bool { |
| switch tok { |
| case _Name, |
| _Break, _Continue, _Fallthrough, _Return, |
| _Rparen, _Rbrack, _Rbrace: |
| return true |
| } |
| return false |
| } |
|
|
| |
|
|
| func (p *printer) addWhitespace(kind ctrlSymbol, text string) { |
| p.pending = append(p.pending, whitespace{p.lastTok, kind }) |
| switch kind { |
| case semi: |
| p.lastTok = _Semi |
| case newline: |
| p.lastTok = 0 |
| |
| } |
| } |
|
|
| func (p *printer) flush(next token) { |
| |
| sawNewline := next == _EOF |
| sawParen := next == _Rparen || next == _Rbrace |
| for i := len(p.pending) - 1; i >= 0; i-- { |
| switch p.pending[i].kind { |
| case semi: |
| k := semi |
| if sawParen { |
| sawParen = false |
| k = none |
| } else if sawNewline && impliesSemi(p.pending[i].last) { |
| sawNewline = false |
| k = none |
| } |
| p.pending[i].kind = k |
| case newline: |
| sawNewline = true |
| case blank, indent, outdent: |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| default: |
| panic("unreachable") |
| } |
| } |
|
|
| |
| prev := none |
| for i := range p.pending { |
| switch p.pending[i].kind { |
| case none: |
| |
| case semi: |
| p.writeString(";") |
| p.nlcount = 0 |
| prev = semi |
| case blank: |
| if prev != blank { |
| |
| p.writeBytes(blankByte) |
| p.nlcount = 0 |
| prev = blank |
| } |
| case newline: |
| const maxEmptyLines = 1 |
| if p.nlcount <= maxEmptyLines { |
| p.write(newlineByte) |
| p.nlcount++ |
| prev = newline |
| } |
| case indent: |
| p.indent++ |
| case outdent: |
| p.indent-- |
| if p.indent < 0 { |
| panic("negative indentation") |
| } |
| |
| |
| |
| |
| |
| |
| |
| default: |
| panic("unreachable") |
| } |
| } |
|
|
| p.pending = p.pending[:0] |
| } |
|
|
| func mayCombine(prev token, next byte) (b bool) { |
| return |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| } |
|
|
| func (p *printer) print(args ...any) { |
| for i := 0; i < len(args); i++ { |
| switch x := args[i].(type) { |
| case nil: |
| |
|
|
| case Node: |
| p.printNode(x) |
|
|
| case token: |
| |
| |
| var s string |
| if x == _Name { |
| i++ |
| if i >= len(args) { |
| panic("missing string argument after _Name") |
| } |
| s = args[i].(string) |
| } else { |
| s = x.String() |
| } |
|
|
| |
| |
| if mayCombine(p.lastTok, s[0]) { |
| panic("adjacent tokens combine without whitespace") |
| } |
|
|
| if x == _Semi { |
| |
| p.addWhitespace(semi, "") |
| } else { |
| p.flush(x) |
| p.writeString(s) |
| p.nlcount = 0 |
| p.lastTok = x |
| } |
|
|
| case Operator: |
| if x != 0 { |
| p.flush(_Operator) |
| p.writeString(x.String()) |
| } |
|
|
| case ctrlSymbol: |
| switch x { |
| case none, semi : |
| panic("unreachable") |
| case newline: |
| |
| if !p.linebreaks { |
| x = blank |
| } |
| } |
| p.addWhitespace(x, "") |
|
|
| |
| |
|
|
| default: |
| panic(fmt.Sprintf("unexpected argument %v (%T)", x, x)) |
| } |
| } |
| } |
|
|
| func (p *printer) printNode(n Node) { |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| p.printRawNode(n) |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| } |
|
|
| func (p *printer) printRawNode(n Node) { |
| switch n := n.(type) { |
| case nil: |
| |
|
|
| |
| case *BadExpr: |
| p.print(_Name, "<bad expr>") |
|
|
| case *Name: |
| p.print(_Name, n.Value) |
|
|
| case *BasicLit: |
| p.print(_Name, n.Value) |
|
|
| case *FuncLit: |
| p.print(n.Type, blank) |
| if n.Body != nil { |
| if p.form == ShortForm { |
| p.print(_Lbrace) |
| if len(n.Body.List) > 0 { |
| p.print(_Name, "…") |
| } |
| p.print(_Rbrace) |
| } else { |
| p.print(n.Body) |
| } |
| } |
|
|
| case *CompositeLit: |
| if n.Type != nil { |
| p.print(n.Type) |
| } |
| p.print(_Lbrace) |
| if p.form == ShortForm { |
| if len(n.ElemList) > 0 { |
| p.print(_Name, "…") |
| } |
| } else { |
| if n.NKeys > 0 && n.NKeys == len(n.ElemList) { |
| p.printExprLines(n.ElemList) |
| } else { |
| p.printExprList(n.ElemList) |
| } |
| } |
| p.print(_Rbrace) |
|
|
| case *ParenExpr: |
| p.print(_Lparen, n.X, _Rparen) |
|
|
| case *SelectorExpr: |
| p.print(n.X, _Dot, n.Sel) |
|
|
| case *IndexExpr: |
| p.print(n.X, _Lbrack, n.Index, _Rbrack) |
|
|
| case *SliceExpr: |
| p.print(n.X, _Lbrack) |
| if i := n.Index[0]; i != nil { |
| p.printNode(i) |
| } |
| p.print(_Colon) |
| if j := n.Index[1]; j != nil { |
| p.printNode(j) |
| } |
| if k := n.Index[2]; k != nil { |
| p.print(_Colon, k) |
| } |
| p.print(_Rbrack) |
|
|
| case *AssertExpr: |
| p.print(n.X, _Dot, _Lparen, n.Type, _Rparen) |
|
|
| case *TypeSwitchGuard: |
| if n.Lhs != nil { |
| p.print(n.Lhs, blank, _Define, blank) |
| } |
| p.print(n.X, _Dot, _Lparen, _Type, _Rparen) |
|
|
| case *CallExpr: |
| p.print(n.Fun, _Lparen) |
| p.printExprList(n.ArgList) |
| if n.HasDots { |
| p.print(_DotDotDot) |
| } |
| p.print(_Rparen) |
|
|
| case *Operation: |
| if n.Y == nil { |
| |
| p.print(n.Op) |
| |
| |
| |
| p.print(n.X) |
| } else { |
| |
| |
| |
| p.print(n.X, blank, n.Op, blank, n.Y) |
| } |
|
|
| case *KeyValueExpr: |
| p.print(n.Key, _Colon, blank, n.Value) |
|
|
| case *ListExpr: |
| p.printExprList(n.ElemList) |
|
|
| case *ArrayType: |
| var len any = _DotDotDot |
| if n.Len != nil { |
| len = n.Len |
| } |
| p.print(_Lbrack, len, _Rbrack, n.Elem) |
|
|
| case *SliceType: |
| p.print(_Lbrack, _Rbrack, n.Elem) |
|
|
| case *DotsType: |
| p.print(_DotDotDot, n.Elem) |
|
|
| case *StructType: |
| p.print(_Struct) |
| if len(n.FieldList) > 0 && p.linebreaks { |
| p.print(blank) |
| } |
| p.print(_Lbrace) |
| if len(n.FieldList) > 0 { |
| if p.linebreaks { |
| p.print(newline, indent) |
| p.printFieldList(n.FieldList, n.TagList, _Semi) |
| p.print(outdent, newline) |
| } else { |
| p.printFieldList(n.FieldList, n.TagList, _Semi) |
| } |
| } |
| p.print(_Rbrace) |
|
|
| case *FuncType: |
| p.print(_Func) |
| p.printSignature(n) |
|
|
| case *InterfaceType: |
| p.print(_Interface) |
| if p.linebreaks && len(n.MethodList) > 1 { |
| p.print(blank) |
| p.print(_Lbrace) |
| p.print(newline, indent) |
| p.printMethodList(n.MethodList) |
| p.print(outdent, newline) |
| } else { |
| p.print(_Lbrace) |
| p.printMethodList(n.MethodList) |
| } |
| p.print(_Rbrace) |
|
|
| case *MapType: |
| p.print(_Map, _Lbrack, n.Key, _Rbrack, n.Value) |
|
|
| case *ChanType: |
| if n.Dir == RecvOnly { |
| p.print(_Arrow) |
| } |
| p.print(_Chan) |
| if n.Dir == SendOnly { |
| p.print(_Arrow) |
| } |
| p.print(blank) |
| if e, _ := n.Elem.(*ChanType); n.Dir == 0 && e != nil && e.Dir == RecvOnly { |
| |
| p.print(_Lparen) |
| p.print(n.Elem) |
| p.print(_Rparen) |
| } else { |
| p.print(n.Elem) |
| } |
|
|
| |
| case *DeclStmt: |
| p.printDecl(n.DeclList) |
|
|
| case *EmptyStmt: |
| |
|
|
| case *LabeledStmt: |
| p.print(outdent, n.Label, _Colon, indent, newline, n.Stmt) |
|
|
| case *ExprStmt: |
| p.print(n.X) |
|
|
| case *SendStmt: |
| p.print(n.Chan, blank, _Arrow, blank, n.Value) |
|
|
| case *AssignStmt: |
| p.print(n.Lhs) |
| if n.Rhs == nil { |
| |
| |
| p.print(n.Op, n.Op) |
| } else { |
| p.print(blank, n.Op, _Assign, blank) |
| p.print(n.Rhs) |
| } |
|
|
| case *CallStmt: |
| p.print(n.Tok, blank, n.Call) |
|
|
| case *ReturnStmt: |
| p.print(_Return) |
| if n.Results != nil { |
| p.print(blank, n.Results) |
| } |
|
|
| case *BranchStmt: |
| p.print(n.Tok) |
| if n.Label != nil { |
| p.print(blank, n.Label) |
| } |
|
|
| case *BlockStmt: |
| p.print(_Lbrace) |
| if len(n.List) > 0 { |
| p.print(newline, indent) |
| p.printStmtList(n.List, true) |
| p.print(outdent, newline) |
| } |
| p.print(_Rbrace) |
|
|
| case *IfStmt: |
| p.print(_If, blank) |
| if n.Init != nil { |
| p.print(n.Init, _Semi, blank) |
| } |
| p.print(n.Cond, blank, n.Then) |
| if n.Else != nil { |
| p.print(blank, _Else, blank, n.Else) |
| } |
|
|
| case *SwitchStmt: |
| p.print(_Switch, blank) |
| if n.Init != nil { |
| p.print(n.Init, _Semi, blank) |
| } |
| if n.Tag != nil { |
| p.print(n.Tag, blank) |
| } |
| p.printSwitchBody(n.Body) |
|
|
| case *SelectStmt: |
| p.print(_Select, blank) |
| p.printSelectBody(n.Body) |
|
|
| case *RangeClause: |
| if n.Lhs != nil { |
| tok := _Assign |
| if n.Def { |
| tok = _Define |
| } |
| p.print(n.Lhs, blank, tok, blank) |
| } |
| p.print(_Range, blank, n.X) |
|
|
| case *ForStmt: |
| p.print(_For, blank) |
| if n.Init == nil && n.Post == nil { |
| if n.Cond != nil { |
| p.print(n.Cond, blank) |
| } |
| } else { |
| if n.Init != nil { |
| p.print(n.Init) |
| |
| if _, ok := n.Init.(*RangeClause); ok { |
| p.print(blank, n.Body) |
| break |
| } |
| } |
| p.print(_Semi, blank) |
| if n.Cond != nil { |
| p.print(n.Cond) |
| } |
| p.print(_Semi, blank) |
| if n.Post != nil { |
| p.print(n.Post, blank) |
| } |
| } |
| p.print(n.Body) |
|
|
| case *ImportDecl: |
| if n.Group == nil { |
| p.print(_Import, blank) |
| } |
| if n.LocalPkgName != nil { |
| p.print(n.LocalPkgName, blank) |
| } |
| p.print(n.Path) |
|
|
| case *ConstDecl: |
| if n.Group == nil { |
| p.print(_Const, blank) |
| } |
| p.printNameList(n.NameList) |
| if n.Type != nil { |
| p.print(blank, n.Type) |
| } |
| if n.Values != nil { |
| p.print(blank, _Assign, blank, n.Values) |
| } |
|
|
| case *TypeDecl: |
| if n.Group == nil { |
| p.print(_Type, blank) |
| } |
| p.print(n.Name) |
| if n.TParamList != nil { |
| p.printParameterList(n.TParamList, _Type) |
| } |
| p.print(blank) |
| if n.Alias { |
| p.print(_Assign, blank) |
| } |
| p.print(n.Type) |
|
|
| case *VarDecl: |
| if n.Group == nil { |
| p.print(_Var, blank) |
| } |
| p.printNameList(n.NameList) |
| if n.Type != nil { |
| p.print(blank, n.Type) |
| } |
| if n.Values != nil { |
| p.print(blank, _Assign, blank, n.Values) |
| } |
|
|
| case *FuncDecl: |
| p.print(_Func, blank) |
| if r := n.Recv; r != nil { |
| p.print(_Lparen) |
| if r.Name != nil { |
| p.print(r.Name, blank) |
| } |
| p.printNode(r.Type) |
| p.print(_Rparen, blank) |
| } |
| p.print(n.Name) |
| if n.TParamList != nil { |
| p.printParameterList(n.TParamList, _Func) |
| } |
| p.printSignature(n.Type) |
| if n.Body != nil { |
| p.print(blank, n.Body) |
| } |
|
|
| case *printGroup: |
| p.print(n.Tok, blank, _Lparen) |
| if len(n.Decls) > 0 { |
| p.print(newline, indent) |
| for _, d := range n.Decls { |
| p.printNode(d) |
| p.print(_Semi, newline) |
| } |
| p.print(outdent) |
| } |
| p.print(_Rparen) |
|
|
| |
| case *File: |
| p.print(_Package, blank, n.PkgName) |
| if len(n.DeclList) > 0 { |
| p.print(_Semi, newline, newline) |
| p.printDeclList(n.DeclList) |
| } |
|
|
| default: |
| panic(fmt.Sprintf("syntax.Iterate: unexpected node type %T", n)) |
| } |
| } |
|
|
| func (p *printer) printFields(fields []*Field, tags []*BasicLit, i, j int) { |
| if i+1 == j && fields[i].Name == nil { |
| |
| p.printNode(fields[i].Type) |
| } else { |
| for k, f := range fields[i:j] { |
| if k > 0 { |
| p.print(_Comma, blank) |
| } |
| p.printNode(f.Name) |
| } |
| p.print(blank) |
| p.printNode(fields[i].Type) |
| } |
| if i < len(tags) && tags[i] != nil { |
| p.print(blank) |
| p.printNode(tags[i]) |
| } |
| } |
|
|
| func (p *printer) printFieldList(fields []*Field, tags []*BasicLit, sep token) { |
| i0 := 0 |
| var typ Expr |
| for i, f := range fields { |
| if f.Name == nil || f.Type != typ { |
| if i0 < i { |
| p.printFields(fields, tags, i0, i) |
| p.print(sep, newline) |
| i0 = i |
| } |
| typ = f.Type |
| } |
| } |
| p.printFields(fields, tags, i0, len(fields)) |
| } |
|
|
| func (p *printer) printMethodList(methods []*Field) { |
| for i, m := range methods { |
| if i > 0 { |
| p.print(_Semi, newline) |
| } |
| if m.Name != nil { |
| p.printNode(m.Name) |
| p.printSignature(m.Type.(*FuncType)) |
| } else { |
| p.printNode(m.Type) |
| } |
| } |
| } |
|
|
| func (p *printer) printNameList(list []*Name) { |
| for i, x := range list { |
| if i > 0 { |
| p.print(_Comma, blank) |
| } |
| p.printNode(x) |
| } |
| } |
|
|
| func (p *printer) printExprList(list []Expr) { |
| for i, x := range list { |
| if i > 0 { |
| p.print(_Comma, blank) |
| } |
| p.printNode(x) |
| } |
| } |
|
|
| func (p *printer) printExprLines(list []Expr) { |
| if len(list) > 0 { |
| p.print(newline, indent) |
| for _, x := range list { |
| p.print(x, _Comma, newline) |
| } |
| p.print(outdent) |
| } |
| } |
|
|
| func groupFor(d Decl) (token, *Group) { |
| switch d := d.(type) { |
| case *ImportDecl: |
| return _Import, d.Group |
| case *ConstDecl: |
| return _Const, d.Group |
| case *TypeDecl: |
| return _Type, d.Group |
| case *VarDecl: |
| return _Var, d.Group |
| case *FuncDecl: |
| return _Func, nil |
| default: |
| panic("unreachable") |
| } |
| } |
|
|
| type printGroup struct { |
| node |
| Tok token |
| Decls []Decl |
| } |
|
|
| func (p *printer) printDecl(list []Decl) { |
| tok, group := groupFor(list[0]) |
|
|
| if group == nil { |
| if len(list) != 1 { |
| panic("unreachable") |
| } |
| p.printNode(list[0]) |
| return |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| |
| var pg printGroup |
| |
| pg.Tok = tok |
| pg.Decls = list |
| p.printNode(&pg) |
| } |
|
|
| func (p *printer) printDeclList(list []Decl) { |
| i0 := 0 |
| var tok token |
| var group *Group |
| for i, x := range list { |
| if s, g := groupFor(x); g == nil || g != group { |
| if i0 < i { |
| p.printDecl(list[i0:i]) |
| p.print(_Semi, newline) |
| |
| |
| if g != group || s != tok || s == _Func { |
| p.print(newline) |
| } |
| i0 = i |
| } |
| tok, group = s, g |
| } |
| } |
| p.printDecl(list[i0:]) |
| } |
|
|
| func (p *printer) printSignature(sig *FuncType) { |
| p.printParameterList(sig.ParamList, 0) |
| if list := sig.ResultList; list != nil { |
| p.print(blank) |
| if len(list) == 1 && list[0].Name == nil { |
| p.printNode(list[0].Type) |
| } else { |
| p.printParameterList(list, 0) |
| } |
| } |
| } |
|
|
| |
| |
| |
| func (p *printer) printParameterList(list []*Field, tok token) { |
| open, close := _Lparen, _Rparen |
| if tok != 0 { |
| open, close = _Lbrack, _Rbrack |
| } |
| p.print(open) |
| for i, f := range list { |
| if i > 0 { |
| p.print(_Comma, blank) |
| } |
| if f.Name != nil { |
| p.printNode(f.Name) |
| if i+1 < len(list) { |
| f1 := list[i+1] |
| if f1.Name != nil && f1.Type == f.Type { |
| continue |
| } |
| } |
| p.print(blank) |
| } |
| p.printNode(f.Type) |
| } |
| |
| |
| |
| |
| if tok == _Type && len(list) == 1 && combinesWithName(list[0].Type) { |
| p.print(_Comma) |
| } |
| p.print(close) |
| } |
|
|
| |
| |
| |
| |
| |
| func combinesWithName(x Expr) bool { |
| switch x := x.(type) { |
| case *Operation: |
| if x.Y == nil { |
| |
| return x.Op == Mul && !isTypeElem(x.X) |
| } |
| |
| return combinesWithName(x.X) && !isTypeElem(x.Y) |
| case *ParenExpr: |
| |
| |
| |
| |
| |
| |
| return !isTypeElem(x.X) |
| } |
| return false |
| } |
|
|
| func (p *printer) printStmtList(list []Stmt, braces bool) { |
| for i, x := range list { |
| p.print(x, _Semi) |
| if i+1 < len(list) { |
| p.print(newline) |
| } else if braces { |
| |
| |
| |
| if _, ok := x.(*EmptyStmt); ok { |
| p.print(x, _Semi) |
| } |
| } |
| } |
| } |
|
|
| func (p *printer) printSwitchBody(list []*CaseClause) { |
| p.print(_Lbrace) |
| if len(list) > 0 { |
| p.print(newline) |
| for i, c := range list { |
| p.printCaseClause(c, i+1 == len(list)) |
| p.print(newline) |
| } |
| } |
| p.print(_Rbrace) |
| } |
|
|
| func (p *printer) printSelectBody(list []*CommClause) { |
| p.print(_Lbrace) |
| if len(list) > 0 { |
| p.print(newline) |
| for i, c := range list { |
| p.printCommClause(c, i+1 == len(list)) |
| p.print(newline) |
| } |
| } |
| p.print(_Rbrace) |
| } |
|
|
| func (p *printer) printCaseClause(c *CaseClause, braces bool) { |
| if c.Cases != nil { |
| p.print(_Case, blank, c.Cases) |
| } else { |
| p.print(_Default) |
| } |
| p.print(_Colon) |
| if len(c.Body) > 0 { |
| p.print(newline, indent) |
| p.printStmtList(c.Body, braces) |
| p.print(outdent) |
| } |
| } |
|
|
| func (p *printer) printCommClause(c *CommClause, braces bool) { |
| if c.Comm != nil { |
| p.print(_Case, blank) |
| p.print(c.Comm) |
| } else { |
| p.print(_Default) |
| } |
| p.print(_Colon) |
| if len(c.Body) > 0 { |
| p.print(newline, indent) |
| p.printStmtList(c.Body, braces) |
| p.print(outdent) |
| } |
| } |
|
|