Skip to content

Commit 8b158bc

Browse files
authored
Merge pull request #36 from haya14busa/printer
printer: support UnaryExpr, BinaryExpr and SubscriptExpr
2 parents cb5c6dd + d2c42d1 commit 8b158bc

File tree

5 files changed

+265
-58
lines changed

5 files changed

+265
-58
lines changed

printer/expr.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package printer
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/haya14busa/go-vimlparser/ast"
7+
"github.com/haya14busa/go-vimlparser/token"
8+
)
9+
10+
func (p *printer) expr(expr ast.Expr) {
11+
p.expr1(expr, 0)
12+
}
13+
14+
func (p *printer) expr1(expr ast.Expr, prec1 int) {
15+
switch x := expr.(type) {
16+
// case *ast.TernaryExpr:
17+
case *ast.BinaryExpr:
18+
p.binaryExpr(x, prec1)
19+
case *ast.UnaryExpr:
20+
p.unaryExpr(x, prec1)
21+
case *ast.SubscriptExpr:
22+
p.expr1(x.Left, opprec(x))
23+
p.token(token.SQOPEN)
24+
p.expr(x.Right)
25+
p.token(token.SQCLOSE)
26+
// case *ast.SliceExpr:
27+
// case *ast.CallExpr:
28+
// case *ast.DotExpr:
29+
// case *ast.List:
30+
// case *ast.Dict:
31+
// case *ast.CurlyName:
32+
// case *ast.CurlyNameLit:
33+
// case *ast.CurlyNameExpr:
34+
case *ast.BasicLit:
35+
p.writeString(x.Value)
36+
case *ast.Ident:
37+
p.writeString(x.Name)
38+
// case *ast.LambdaExpr:
39+
case *ast.ParenExpr:
40+
if _, hasParens := x.X.(*ast.ParenExpr); hasParens {
41+
p.expr(x.X)
42+
} else {
43+
p.token(token.POPEN)
44+
p.expr(x.X)
45+
p.token(token.PCLOSE)
46+
}
47+
default:
48+
panic(fmt.Errorf("unsupported expr type %T", x))
49+
}
50+
}
51+
52+
func (p *printer) binaryExpr(x *ast.BinaryExpr, prec1 int) {
53+
prec := opprec(x)
54+
if prec < prec1 {
55+
// parenthesis needed
56+
// Note: The parser inserts an ast.ParenExpr node; thus this case
57+
// can only occur if the AST is created in a different way.
58+
p.token(token.POPEN)
59+
p.expr(x)
60+
p.token(token.PCLOSE)
61+
return
62+
}
63+
// TODO(haya14busa): handle line break.
64+
p.expr1(x.Left, prec)
65+
p.printWhite(blank)
66+
p.token(x.Op)
67+
p.printWhite(blank)
68+
p.expr1(x.Right, prec+1)
69+
}
70+
71+
func (p *printer) unaryExpr(x *ast.UnaryExpr, prec1 int) {
72+
prec := opprec(x)
73+
if prec < prec1 {
74+
// parenthesis needed
75+
// Note: this case can only occcur if the AST is created manually and
76+
// the code should invalid code.
77+
p.token(token.POPEN)
78+
p.expr(x)
79+
p.token(token.PCLOSE)
80+
return
81+
}
82+
// no parenthesis needed
83+
p.token(x.Op)
84+
p.expr1(x.X, prec)
85+
}

printer/expr_test.go

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package printer
2+
3+
import (
4+
"bytes"
5+
"strings"
6+
"testing"
7+
8+
vimlparser "github.com/haya14busa/go-vimlparser"
9+
"github.com/haya14busa/go-vimlparser/ast"
10+
"github.com/haya14busa/go-vimlparser/token"
11+
)
12+
13+
func TestFprint_expr(t *testing.T) {
14+
tests := []struct {
15+
in string
16+
want string
17+
wantErr bool
18+
}{
19+
{in: `xyz`, want: `xyz`}, // Ident
20+
{in: `"double quote"`, want: `"double quote"`}, // BasicLit
21+
{in: `14`, want: `14`}, // BasicLit
22+
{in: `+1`, want: `+1`}, // UnaryExpr
23+
{in: `- 1`, want: `-1`}, // UnaryExpr
24+
{in: `! + - 1`, want: `!+-1`}, // UnaryExpr
25+
{in: `x+1`, want: `x + 1`}, // BinaryExpr
26+
{in: `1+2*3`, want: `1 + 2 * 3`}, // BinaryExpr
27+
{in: `1*2+3`, want: `1 * 2 + 3`}, // BinaryExpr
28+
{in: `(1+2)*(3-4)`, want: `(1 + 2) * (3 - 4)`}, // ParenExpr
29+
{in: `1+(2*3)`, want: `1 + (2 * 3)`}, // ParenExpr
30+
{in: `(((x+(1))))`, want: `(x + (1))`}, // ParenExpr
31+
{in: `x+1==14 ||-1`, want: `x + 1 == 14 || -1`}, // BinaryExpr
32+
{in: `x[ y ]`, want: `x[y]`}, // SubscriptExpr
33+
}
34+
35+
for _, tt := range tests {
36+
r := strings.NewReader(tt.in)
37+
node, err := vimlparser.ParseExpr(r)
38+
if err != nil {
39+
t.Fatal(err)
40+
}
41+
buf := new(bytes.Buffer)
42+
if err := Fprint(buf, node, nil); err != nil {
43+
if !tt.wantErr {
44+
t.Errorf("got unexpected error: %v", err)
45+
}
46+
continue
47+
}
48+
if got := buf.String(); got != tt.want {
49+
t.Errorf("got: %v, want: %v", got, tt.want)
50+
}
51+
}
52+
}
53+
54+
func TestFprint_expr_insert_paren_to_binary(t *testing.T) {
55+
want := `(x + y) * z`
56+
buf := new(bytes.Buffer)
57+
left := &ast.BinaryExpr{
58+
Left: &ast.Ident{Name: "x"},
59+
Op: token.PLUS,
60+
Right: &ast.Ident{Name: "y"},
61+
}
62+
node := &ast.BinaryExpr{
63+
Left: left,
64+
Op: token.STAR,
65+
Right: &ast.Ident{Name: "z"},
66+
}
67+
if err := Fprint(buf, node, nil); err != nil {
68+
t.Fatal(err)
69+
}
70+
if got := buf.String(); got != want {
71+
t.Errorf("got %q, want %q", got, want)
72+
}
73+
}
74+
75+
func TestFprint_expr_insert_paren_to_unary(t *testing.T) {
76+
want := `(-x)[y]`
77+
buf := new(bytes.Buffer)
78+
node := &ast.SubscriptExpr{
79+
Left: &ast.UnaryExpr{Op: token.MINUS, X: &ast.Ident{Name: "x"}},
80+
Right: &ast.Ident{Name: "y"},
81+
}
82+
if err := Fprint(buf, node, nil); err != nil {
83+
t.Fatal(err)
84+
}
85+
if got := buf.String(); got != want {
86+
t.Errorf("got %q, want %q", got, want)
87+
}
88+
}

printer/opprec.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package printer
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/haya14busa/go-vimlparser/ast"
7+
"github.com/haya14busa/go-vimlparser/token"
8+
)
9+
10+
// opprec returns operator precedence. See also go/gocompiler.vim
11+
func opprec(op ast.Expr) int {
12+
switch n := op.(type) {
13+
case *ast.TernaryExpr, *ast.ParenExpr:
14+
return 1
15+
case *ast.BinaryExpr:
16+
switch n.Op {
17+
case token.OROR:
18+
return 2
19+
case token.ANDAND:
20+
return 3
21+
case token.EQEQ,
22+
token.EQEQCI,
23+
token.EQEQCS,
24+
token.NEQ,
25+
token.NEQCI,
26+
token.NEQCS,
27+
token.GT,
28+
token.GTCI,
29+
token.GTCS,
30+
token.GTEQ,
31+
token.GTEQCI,
32+
token.GTEQCS,
33+
token.LT,
34+
token.LTCI,
35+
token.LTCS,
36+
token.LTEQ,
37+
token.LTEQCI,
38+
token.LTEQCS,
39+
token.MATCHCS,
40+
token.NOMATCH,
41+
token.NOMATCHCI,
42+
token.NOMATCHCS,
43+
token.IS,
44+
token.ISCI,
45+
token.ISCS,
46+
token.ISNOT,
47+
token.ISNOTCI,
48+
token.ISNOTCS:
49+
return 4
50+
case token.PLUS, token.MINUS, token.DOT:
51+
return 5
52+
case token.STAR, token.SLASH, token.PERCENT:
53+
return 6
54+
default:
55+
panic(fmt.Errorf("unexpected token of BinaryExpr: %v", n.Op))
56+
}
57+
case *ast.UnaryExpr:
58+
switch n.Op {
59+
case token.NOT, token.MINUS, token.PLUS:
60+
return 7
61+
default:
62+
panic(fmt.Errorf("unexpected token of UnaryExpr: %v", n.Op))
63+
}
64+
case *ast.SubscriptExpr, *ast.SliceExpr, *ast.CallExpr, *ast.DotExpr:
65+
return 8
66+
case *ast.BasicLit, *ast.Ident, *ast.List, *ast.Dict, *ast.CurlyName:
67+
return 9
68+
case *ast.CurlyNameExpr, *ast.CurlyNameLit, *ast.LambdaExpr:
69+
panic(fmt.Errorf("precedence is undefined for expr: %T", n))
70+
default:
71+
panic(fmt.Errorf("unexpected expr: %T", n))
72+
}
73+
}

printer/printer.go

Lines changed: 19 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
// Package printer implements printing of AST nodes.
22
//
3+
// ref: go/printer
4+
//
35
// This is WIP package. DO NOT USE.
46
package printer
57

@@ -9,6 +11,13 @@ import (
911
"io"
1012

1113
"github.com/haya14busa/go-vimlparser/ast"
14+
"github.com/haya14busa/go-vimlparser/token"
15+
)
16+
17+
type whiteSpace byte
18+
19+
const (
20+
blank = whiteSpace(' ')
1221
)
1322

1423
// A Config node controls the output of Fprint.
@@ -44,49 +53,32 @@ func (p *printer) writeString(s string) {
4453
p.output = append(p.output, s...)
4554
}
4655

56+
func (p *printer) token(t token.Token) {
57+
p.writeString(t.String())
58+
}
59+
60+
func (p *printer) printWhite(x whiteSpace) {
61+
p.output = append(p.output, byte(x))
62+
}
63+
4764
func (p *printer) printNode(node ast.Node) error {
4865
switch n := node.(type) {
4966
case *ast.File:
5067
return p.file(n)
5168
case ast.Expr:
52-
return p.expr(n)
69+
p.expr(n)
5370
case ast.Statement:
5471
return p.stmt(n)
5572
default:
5673
return fmt.Errorf("go-vimlparser/printer: unsupported node type %T", node)
5774
}
75+
return nil
5876
}
5977

6078
func (p *printer) file(f *ast.File) error {
6179
return errors.New("Not implemented: printer.file")
6280
}
6381

64-
func (p *printer) expr(expr ast.Expr) error {
65-
switch n := expr.(type) {
66-
// case *ast.TernaryExpr:
67-
// case *ast.BinaryExpr:
68-
// case *ast.UnaryExpr:
69-
// case *ast.SubscriptExpr:
70-
// case *ast.SliceExpr:
71-
// case *ast.CallExpr:
72-
// case *ast.DotExpr:
73-
// case *ast.List:
74-
// case *ast.Dict:
75-
// case *ast.CurlyName:
76-
// case *ast.CurlyNameLit:
77-
// case *ast.CurlyNameExpr:
78-
case *ast.BasicLit:
79-
p.writeString(n.Value)
80-
case *ast.Ident:
81-
p.writeString(n.Name)
82-
// case *ast.LambdaExpr:
83-
// case *ast.ParenExpr:
84-
default:
85-
return fmt.Errorf("unsupported expr type %T", n)
86-
}
87-
return nil
88-
}
89-
9082
func (p *printer) stmt(node ast.Statement) error {
9183
return errors.New("Not implemented: printer.stmt")
9284
}

printer/printer_test.go

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -20,34 +20,3 @@ func TestFprint_file(t *testing.T) {
2020
t.Error("want error")
2121
}
2222
}
23-
24-
func TestFprint_expr(t *testing.T) {
25-
tests := []struct {
26-
in string
27-
want string
28-
wantErr bool
29-
}{
30-
{in: `xyz`, want: `xyz`}, // Ident
31-
{in: `"double quote"`, want: `"double quote"`}, // BasicLit
32-
{in: `14`, want: `14`}, // BasicLit
33-
{in: `x+1`, want: `x + 1`, wantErr: true},
34-
}
35-
36-
for _, tt := range tests {
37-
r := strings.NewReader(tt.in)
38-
node, err := vimlparser.ParseExpr(r)
39-
if err != nil {
40-
t.Fatal(err)
41-
}
42-
buf := new(bytes.Buffer)
43-
if err := Fprint(buf, node, nil); err != nil {
44-
if !tt.wantErr {
45-
t.Errorf("got unexpected error: %v", err)
46-
}
47-
continue
48-
}
49-
if got := buf.String(); got != tt.want {
50-
t.Errorf("got: %v, want: %v", got, tt.want)
51-
}
52-
}
53-
}

0 commit comments

Comments
 (0)