1package eval_test
2
3import (
4	"testing"
5
6	. "src.elv.sh/pkg/eval"
7	"src.elv.sh/pkg/eval/errs"
8	"src.elv.sh/pkg/eval/vals"
9	"src.elv.sh/pkg/eval/vars"
10	"src.elv.sh/pkg/parse"
11	"src.elv.sh/pkg/prog/progtest"
12
13	. "src.elv.sh/pkg/eval/evaltest"
14	. "src.elv.sh/pkg/testutil"
15)
16
17func TestPragma(t *testing.T) {
18	Test(t,
19		That("pragma unknown-command").DoesNotCompile(),
20		That("pragma unknown-command =").DoesNotCompile(),
21		That("pragma unknown-command x").DoesNotCompile(),
22		That("pragma bad-name = some-value").DoesNotCompile(),
23		That("pragma unknown-command = bad").DoesNotCompile(),
24	)
25	// Actual effect of the unknown-command pragma is tested in TestCommand_External
26}
27
28func TestVar(t *testing.T) {
29	Test(t,
30		// Declaring one variable
31		That("var x", "put $x").Puts(nil),
32		// Declaring one variable whose name needs to be quoted
33		That("var 'a/b'", "put $'a/b'").Puts(nil),
34		// Declaring one variable whose name ends in ":".
35		That("var a:").DoesNothing(),
36		// Declaring a variable whose name ends in "~" initializes it to the
37		// builtin nop function.
38		That("var cmd~; cmd &ignored-opt ignored-arg").DoesNothing(),
39		// Declaring multiple variables
40		That("var x y", "put $x $y").Puts(nil, nil),
41		// Declaring one variable with initial value
42		That("var x = foo", "put $x").Puts("foo"),
43		// Declaring multiple variables with initial values
44		That("var x y = foo bar", "put $x $y").Puts("foo", "bar"),
45		// Declaring multiple variables with initial values, including a rest
46		// variable in the assignment LHS
47		That("var x @y z = a b c d", "put $x $y $z").
48			Puts("a", vals.MakeList("b", "c"), "d"),
49		// An empty RHS is technically legal although rarely useful.
50		That("var @x =", "put $x").Puts(vals.EmptyList),
51		// Shadowing.
52		That("var x = old; fn f { put $x }", "var x = new; put $x; f").
53			Puts("new", "old"),
54		// Explicit local: is allowed
55		That("var local:x = foo", "put $x").Puts("foo"),
56
57		// Variable name that must be quoted after $ must be quoted
58		That("var a/b").DoesNotCompile(),
59		// Multiple @ not allowed
60		That("var x @y @z = a b c d").DoesNotCompile(),
61		// Non-local not allowed
62		That("var ns:a").DoesNotCompile(),
63		// Index not allowed
64		That("var a[0]").DoesNotCompile(),
65		// Composite expression not allowed
66		That("var a'b'").DoesNotCompile(),
67	)
68}
69
70func TestSet(t *testing.T) {
71	Test(t,
72		// Setting one variable
73		That("var x; set x = foo", "put $x").Puts("foo"),
74		// An empty RHS is technically legal although rarely useful.
75		That("var x; set @x =", "put $x").Puts(vals.EmptyList),
76		// Variable must already exist
77		That("set x = foo").DoesNotCompile(),
78		// Not duplicating tests with TestCommand_Assignment.
79		//
80		// TODO: After legacy assignment form is removed, transfer tests here.
81
82		// = is required.
83		That("var x; set x").DoesNotCompile(),
84	)
85}
86
87func TestDel(t *testing.T) {
88	Setenv(t, "TEST_ENV", "test value")
89
90	Test(t,
91		// Deleting variable
92		That("x = 1; del x").DoesNothing(),
93		That("x = 1; del x; echo $x").DoesNotCompile(),
94		That("x = 1; del :x; echo $x").DoesNotCompile(),
95		That("x = 1; del local:x; echo $x").DoesNotCompile(),
96		// Deleting environment variable
97		That("has-env TEST_ENV", "del E:TEST_ENV", "has-env TEST_ENV").Puts(true, false),
98		// Deleting variable whose name contains special characters
99		That("'a/b' = foo; del 'a/b'").DoesNothing(),
100		// Deleting element
101		That("x = [&k=v &k2=v2]; del x[k2]; keys $x").Puts("k"),
102		That("x = [[&k=v &k2=v2]]; del x[0][k2]; keys $x[0]").Puts("k"),
103
104		// Error cases
105
106		// Deleting nonexistent variable
107		That("del x").DoesNotCompile(),
108		// Deleting element of nonexistent variable
109		That("del x[0]").DoesNotCompile(),
110		// Deleting variable in non-local namespace
111		That("var a: = (ns [&b=$nil])", "del a:b").DoesNotCompile(),
112		// Variable name given with $
113		That("x = 1; del $x").DoesNotCompile(),
114		// Variable name not given as a single primary expression
115		That("ab = 1; del a'b'").DoesNotCompile(),
116		// Variable name not a string
117		That("del [a]").DoesNotCompile(),
118		// Variable name has sigil
119		That("x = []; del @x").DoesNotCompile(),
120		// Variable name not quoted when it should be
121		That("'a/b' = foo; del a/b").DoesNotCompile(),
122
123		// Index is multiple values
124		That("x = [&k1=v1 &k2=v2]", "del x[k1 k2]").Throws(
125			ErrorWithMessage("index must evaluate to a single value in argument to del"),
126			"k1 k2"),
127		// Index expression throws exception
128		That("x = [&k]", "del x[(fail x)]").Throws(FailError{"x"}, "fail x"),
129		// Value does not support element removal
130		That("x = (num 1)", "del x[k]").Throws(
131			ErrorWithMessage("value does not support element removal"),
132			// TODO: Fix the stack trace so that it is "x[k]"
133			"x[k"),
134		// Intermediate element does not exist
135		That("x = [&]", "del x[k][0]").Throws(
136			ErrorWithMessage("no such key: k"),
137			// TODO: Fix the stack trace so that it is "x[k]"
138			"x"),
139	)
140}
141
142func TestAnd(t *testing.T) {
143	Test(t,
144		That("and $true $false").Puts(false),
145		That("and a b").Puts("b"),
146		That("and $false b").Puts(false),
147		That("and $true b").Puts("b"),
148		// short circuit
149		That("x = a; and $false (x = b); put $x").Puts(false, "a"),
150
151		// Exception
152		That("and a (fail x)").Throws(FailError{"x"}, "fail x"),
153		thatOutputErrorIsBubbled("and a"),
154	)
155}
156
157func TestOr(t *testing.T) {
158	Test(t,
159		That("or $true $false").Puts(true),
160		That("or a b").Puts("a"),
161		That("or $false b").Puts("b"),
162		That("or $true b").Puts(true),
163		// short circuit
164		That("x = a; or $true (x = b); put $x").Puts(true, "a"),
165
166		// Exception
167		That("or $false (fail x)").Throws(FailError{"x"}, "fail x"),
168		thatOutputErrorIsBubbled("or a"),
169	)
170}
171
172func TestCoalesce(t *testing.T) {
173	Test(t,
174		That("coalesce a b").Puts("a"),
175		That("coalesce $nil b").Puts("b"),
176		That("coalesce $nil $nil").Puts(nil),
177		That("coalesce").Puts(nil),
178		// exception propagation
179		That("coalesce $nil (fail foo)").Throws(FailError{"foo"}),
180		// short circuit
181		That("coalesce a (fail foo)").Puts("a"),
182
183		thatOutputErrorIsBubbled("coalesce a"),
184	)
185}
186
187func TestIf(t *testing.T) {
188	Test(t,
189		That("if true { put then }").Puts("then"),
190		That("if $false { put then } else { put else }").Puts("else"),
191		That("if $false { put 1 } elif $false { put 2 } else { put 3 }").
192			Puts("3"),
193		That("if $false { put 2 } elif true { put 2 } else { put 3 }").Puts("2"),
194
195		// Exception in condition expression
196		That("if (fail x) { }").Throws(FailError{"x"}, "fail x"),
197	)
198}
199
200func TestTry(t *testing.T) {
201	Test(t,
202		That("try { nop } except { put bad } else { put good }").Puts("good"),
203		That("try { e:false } except - { put bad } else { put good }").
204			Puts("bad"),
205		That("try { fail tr }").Throws(ErrorWithMessage("tr")),
206		That("try { fail tr } finally { put final }").
207			Puts("final").
208			Throws(ErrorWithMessage("tr")),
209
210		That("try { fail tr } except { fail ex } finally { put final }").
211			Puts("final").
212			Throws(ErrorWithMessage("ex")),
213
214		That("try { fail tr } except { put ex } finally { fail final }").
215			Puts("ex").
216			Throws(ErrorWithMessage("final")),
217
218		That("try { fail tr } except { fail ex } finally { fail final }").
219			Throws(ErrorWithMessage("final")),
220
221		// wrong syntax
222		That("try { nop } except @a { }").DoesNotCompile(),
223
224		// A quoted var name, that would be invalid as a bareword, should be allowed as the referent
225		// in a `try...except...` block.
226		That("try { fail hard } except 'x=' { put 'x= ='(to-string $'x=') }").
227			Puts("x= =[&reason=[&content=hard &type=fail]]"),
228	)
229}
230
231func TestWhile(t *testing.T) {
232	Test(t,
233		That("var x = (num 0)", "while (< $x 4) { put $x; set x = (+ $x 1) }").
234			Puts(0, 1, 2, 3),
235		// break
236		That("var x = (num 0)", "while (< $x 4) { put $x; break }").Puts(0),
237		// continue
238		That("var x = (num 0)",
239			"while (< $x 4) { put $x; set x = (+ $x 1); continue; put bad }").
240			Puts(0, 1, 2, 3),
241		// Exception in body
242		That("var x = 0; while (< $x 4) { fail haha }").Throws(AnyError),
243		// Exception in condition
244		That("while (fail x) { }").Throws(FailError{"x"}, "fail x"),
245
246		// else branch - not taken
247		That("var x = 0; while (< $x 4) { put $x; set x = (+ $x 1) } else { put bad }").
248			Puts("0", 1, 2, 3),
249		// else branch - taken
250		That("while $false { put bad } else { put good }").Puts("good"),
251	)
252}
253
254func TestFor(t *testing.T) {
255	Test(t,
256		// for
257		That("for x [tempora mores] { put 'O '$x }").
258			Puts("O tempora", "O mores"),
259		// break
260		That("for x [a] { break } else { put $x }").DoesNothing(),
261		// else
262		That("for x [a] { put $x } else { put $x }").Puts("a"),
263		// continue
264		That("for x [a b] { put $x; continue; put $x; }").Puts("a", "b"),
265		// else
266		That("for x [] { } else { put else }").Puts("else"),
267		That("for x [a] { } else { put else }").DoesNothing(),
268		// Propagating exception.
269		That("for x [a] { fail foo }").Throws(FailError{"foo"}),
270
271		// More than one iterator.
272		That("for {x,y} [] { }").DoesNotCompile(),
273		// Invalid for loop lvalue. You can't use a var in a namespace other
274		// than the local namespace as the lvalue in a for loop.
275		That("for no-such-namespace:x [a b] { }").DoesNotCompile(),
276		// Exception with the variable
277		That("var a: = (ns [&])", "for a:b [] { }").Throws(
278			ErrorWithMessage("no variable $a:b"),
279			"a:b"),
280		// Exception when evaluating iterable.
281		That("for x [][0] { }").Throws(ErrorWithType(errs.OutOfRange{}), "[][0]"),
282		// More than one iterable.
283		That("for x (put a b) { }").Throws(
284			errs.ArityMismatch{What: "value being iterated",
285				ValidLow: 1, ValidHigh: 1, Actual: 2},
286			"(put a b)"),
287		// Non-iterable value
288		That("for x (num 0) { }").Throws(ErrorWithMessage("cannot iterate number")),
289	)
290}
291
292func TestFn(t *testing.T) {
293	Test(t,
294		That("fn f {|x| put x=$x'.' }; f lorem; f ipsum").
295			Puts("x=lorem.", "x=ipsum."),
296		// Recursive functions with fn. Regression test for #1206.
297		That("fn f {|n| if (== $n 0) { num 1 } else { * $n (f (- $n 1)) } }; f 3").
298			Puts(6),
299		// Exception thrown by return is swallowed by a fn-defined function.
300		That("fn f { put a; return; put b }; f").Puts("a"),
301
302		// Error when evaluating the lambda
303		That("fn f {|&opt=(fail x)| }").Throws(FailError{"x"}, "fail x"),
304	)
305}
306
307// Regression test for #1225
308func TestUse_SetsVariableCorrectlyIfModuleCallsExtendGlobal(t *testing.T) {
309	libdir := InTempDir(t)
310
311	ApplyDir(Dir{"a.elv": "add-var"})
312	ev := NewEvaler()
313	ev.LibDirs = []string{libdir}
314	addVar := func() {
315		ev.ExtendGlobal(BuildNs().AddVar("b", vars.NewReadOnly("foo")))
316	}
317	ev.ExtendBuiltin(BuildNs().AddGoFn("add-var", addVar))
318
319	err := ev.Eval(parse.Source{Code: "use a"}, EvalCfg{})
320	if err != nil {
321		t.Fatal(err)
322	}
323
324	g := ev.Global()
325	if g.IndexString("a:").Get().(*Ns) == nil {
326		t.Errorf("$a: is nil")
327	}
328	if g.IndexString("b").Get().(string) != "foo" {
329		t.Errorf(`$b is not "foo"`)
330	}
331}
332
333func TestUse_SupportsCircularDependency(t *testing.T) {
334	libdir := InTempDir(t)
335	ApplyDir(Dir{
336		"a.elv": "var pre = apre; use b; put $b:pre $b:post; var post = apost",
337		"b.elv": "var pre = bpre; use a; put $a:pre $a:post; var post = bpost",
338	})
339
340	TestWithSetup(t, func(ev *Evaler) { ev.LibDirs = []string{libdir} },
341		That(`use a`).Puts(
342			// When b.elv is imported from a.elv, $a:pre is set but $a:post is
343			// not
344			"apre", nil,
345			// After a.elv imports b.elv, both $b:pre and $b:post are set
346			"bpre", "bpost"),
347	)
348}
349
350func TestUse(t *testing.T) {
351	libdir1 := InTempDir(t)
352	ApplyDir(Dir{
353		"shadow.elv": "put lib1",
354	})
355
356	libdir2 := InTempDir(t)
357	ApplyDir(Dir{
358		"has-init.elv": "put has-init",
359		"put-x.elv":    "put $x",
360		"lorem.elv":    "name = lorem; fn put-name { put $name }",
361		"d.elv":        "name = d",
362		"shadow.elv":   "put lib2",
363		"a": Dir{
364			"b": Dir{
365				"c": Dir{
366					"d.elv": "name = a/b/c/d",
367					"x.elv": "use ./d; d = $d:name; use ../../../lorem; lorem = $lorem:name",
368				},
369			},
370		},
371	})
372
373	TestWithSetup(t, func(ev *Evaler) { ev.LibDirs = []string{libdir1, libdir2} },
374		That(`use lorem; put $lorem:name`).Puts("lorem"),
375		// imports are lexically scoped
376		// TODO: Support testing for compilation error
377		That(`{ use lorem }; put $lorem:name`).DoesNotCompile(),
378
379		// prefers lib dir that appear earlier
380		That("use shadow").Puts("lib1"),
381
382		// use of imported variable is captured in upvalue
383		That(`use lorem; { put $lorem:name }`).Puts("lorem"),
384		That(`{ use lorem; { put $lorem:name } }`).Puts("lorem"),
385		That(`({ use lorem; put { { put $lorem:name } } })`).Puts("lorem"),
386		// use of imported function is also captured in upvalue
387		That(`{ use lorem; { lorem:put-name } }`).Puts("lorem"),
388
389		// use of a nested module
390		That(`use a/b/c/d; put $d:name`).Puts("a/b/c/d"),
391		// module is cached after first use
392		That(`use has-init; use has-init`).Puts("has-init"),
393		// repeated uses result in the same namespace being imported
394		That("use lorem; use lorem lorem2; put $lorem:name $lorem2:name").
395			Puts("lorem", "lorem"),
396		// overriding module
397		That(`use d; put $d:name; use a/b/c/d; put $d:name`).
398			Puts("d", "a/b/c/d"),
399		// relative uses
400		That(`use a/b/c/x; put $x:d $x:lorem`).Puts("a/b/c/d", "lorem"),
401		// relative uses from top-level
402		That(`use ./d; put $d:name`).Puts("d"),
403
404		// Renaming module
405		That(`use a/b/c/d mod; put $mod:name`).Puts("a/b/c/d"),
406
407		// Variables defined in the default global scope is invisible from
408		// modules
409		That("x = foo; use put-x").Throws(AnyError),
410
411		// Using an unknown module spec fails.
412		That("use unknown").Throws(ErrorWithType(NoSuchModule{})),
413		That("use ./unknown").Throws(ErrorWithType(NoSuchModule{})),
414		That("use ../unknown").Throws(ErrorWithType(NoSuchModule{})),
415
416		// Nonexistent module
417		That("use non-existent").Throws(ErrorWithMessage("no such module: non-existent")),
418
419		// Wrong uses of "use".
420		That("use").DoesNotCompile(),
421		That("use a b c").DoesNotCompile(),
422	)
423}
424
425// Regression test for #1072
426func TestUse_WarnsAboutDeprecatedFeatures(t *testing.T) {
427	progtest.SetDeprecationLevel(t, 17)
428	libdir := InTempDir(t)
429	MustWriteFile("dep.elv", "fn x { dir-history }")
430
431	TestWithSetup(t, func(ev *Evaler) { ev.LibDirs = []string{libdir} },
432		// Importing module triggers check for deprecated features
433		That("use dep").PrintsStderrWith("is deprecated"),
434	)
435}
436