1// Copyright 2015 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 kati
16
17import (
18	"reflect"
19	"testing"
20)
21
22func TestParseExpr(t *testing.T) {
23	for _, tc := range []struct {
24		in    string
25		val   Value
26		isErr bool
27	}{
28		{
29			in:  "foo",
30			val: literal("foo"),
31		},
32		{
33			in:  "(foo)",
34			val: literal("(foo)"),
35		},
36		{
37			in:  "{foo}",
38			val: literal("{foo}"),
39		},
40		{
41			in:  "$$",
42			val: literal("$"),
43		},
44		{
45			in:  "foo$$bar",
46			val: literal("foo$bar"),
47		},
48		{
49			in:  "$foo",
50			val: expr{&varref{varname: literal("f")}, literal("oo")},
51		},
52		{
53			in:  "$(foo)",
54			val: &varref{varname: literal("foo"), paren: '('},
55		},
56		{
57			in: "$(foo:.c=.o)",
58			val: varsubst{
59				varname: literal("foo"),
60				pat:     literal(".c"),
61				subst:   literal(".o"),
62				paren:   '(',
63			},
64		},
65		{
66			in: "$(subst $(space),$(,),$(foo))/bar",
67			val: expr{
68				&funcSubst{
69					fclosure: fclosure{
70						args: []Value{
71							literal("(subst"),
72							&varref{
73								varname: literal("space"),
74								paren:   '(',
75							},
76							&varref{
77								varname: literal(","),
78								paren:   '(',
79							},
80							&varref{
81								varname: literal("foo"),
82								paren:   '(',
83							},
84						},
85					},
86				},
87				literal("/bar"),
88			},
89		},
90		{
91			in: "$(subst $(space),$,,$(foo))",
92			val: &funcSubst{
93				fclosure: fclosure{
94					args: []Value{
95						literal("(subst"),
96						&varref{
97							varname: literal("space"),
98							paren:   '(',
99						},
100						&varref{
101							varname: literal(""),
102						},
103						expr{
104							literal(","),
105							&varref{
106								varname: literal("foo"),
107								paren:   '(',
108							},
109						},
110					},
111				},
112			},
113		},
114		{
115			in: `$(shell echo '()')`,
116			val: &funcShell{
117				fclosure: fclosure{
118					args: []Value{
119						literal("(shell"),
120						literal("echo '()'"),
121					},
122				},
123			},
124		},
125		{
126			in: `${shell echo '()'}`,
127			val: &funcShell{
128				fclosure: fclosure{
129					args: []Value{
130						literal("{shell"),
131						literal("echo '()'"),
132					},
133				},
134			},
135		},
136		{
137			in: `$(shell echo ')')`,
138			val: expr{
139				&funcShell{
140					fclosure: fclosure{
141						args: []Value{
142							literal("(shell"),
143							literal("echo '"),
144						},
145					},
146				},
147				literal("')"),
148			},
149		},
150		{
151			in: `${shell echo ')'}`,
152			val: &funcShell{
153				fclosure: fclosure{
154					args: []Value{
155						literal("{shell"),
156						literal("echo ')'"),
157					},
158				},
159			},
160		},
161		{
162			in: `${shell echo '}'}`,
163			val: expr{
164				&funcShell{
165					fclosure: fclosure{
166						args: []Value{
167							literal("{shell"),
168							literal("echo '"),
169						},
170					},
171				},
172				literal("'}"),
173			},
174		},
175		{
176			in: `$(shell make --version | ruby -n0e 'puts $$_[/Make (\d)/,1]')`,
177			val: &funcShell{
178				fclosure: fclosure{
179					args: []Value{
180						literal("(shell"),
181						literal(`make --version | ruby -n0e 'puts $_[/Make (\d)/,1]'`),
182					},
183				},
184			},
185		},
186		{
187			in: `$(and ${TRUE}, $(X)   )`,
188			val: &funcAnd{
189				fclosure: fclosure{
190					args: []Value{
191						literal("(and"),
192						&varref{
193							varname: literal("TRUE"),
194							paren:   '{',
195						},
196						&varref{
197							varname: literal("X"),
198							paren:   '(',
199						},
200					},
201				},
202			},
203		},
204		{
205			in: `$(call func, \
206	foo)`,
207			val: &funcCall{
208				fclosure: fclosure{
209					args: []Value{
210						literal("(call"),
211						literal("func"),
212						literal(" foo"),
213					},
214				},
215			},
216		},
217		{
218			in: `$(call func, \)`,
219			val: &funcCall{
220				fclosure: fclosure{
221					args: []Value{
222						literal("(call"),
223						literal("func"),
224						literal(` \`),
225					},
226				},
227			},
228		},
229		{
230			in: `$(eval ## comment)`,
231			val: &funcNop{
232				expr: `$(eval ## comment)`,
233			},
234		},
235		{
236			in: `$(eval foo = bar)`,
237			val: &funcEvalAssign{
238				lhs: "foo",
239				op:  "=",
240				rhs: literal("bar"),
241			},
242		},
243		{
244			in: `$(eval foo :=)`,
245			val: &funcEvalAssign{
246				lhs: "foo",
247				op:  ":=",
248				rhs: literal(""),
249			},
250		},
251		{
252			in: `$(eval foo := $(bar))`,
253			val: &funcEvalAssign{
254				lhs: "foo",
255				op:  ":=",
256				rhs: &varref{
257					varname: literal("bar"),
258					paren:   '(',
259				},
260			},
261		},
262		{
263			in: `$(eval foo := $$(bar))`,
264			val: &funcEvalAssign{
265				lhs: "foo",
266				op:  ":=",
267				rhs: literal("$(bar)"),
268			},
269		},
270		{
271			in: `$(strip $1)`,
272			val: &funcStrip{
273				fclosure: fclosure{
274					args: []Value{
275						literal("(strip"),
276						paramref(1),
277					},
278				},
279			},
280		},
281		{
282			in: `$(strip $(1))`,
283			val: &funcStrip{
284				fclosure: fclosure{
285					args: []Value{
286						literal("(strip"),
287						paramref(1),
288					},
289				},
290			},
291		},
292	} {
293		val, _, err := parseExpr([]byte(tc.in), nil, parseOp{alloc: true})
294		if tc.isErr {
295			if err == nil {
296				t.Errorf(`parseExpr(%q)=_, _, nil; want error`, tc.in)
297			}
298			continue
299		}
300		if err != nil {
301			t.Errorf(`parseExpr(%q)=_, _, %v; want nil error`, tc.in, err)
302			continue
303		}
304		if got, want := val, tc.val; !reflect.DeepEqual(got, want) {
305			t.Errorf("parseExpr(%[1]q)=%[2]q %#[2]v, _, _;\n want %[3]q %#[3]v, _, _", tc.in, got, want)
306		}
307	}
308}
309