2017-12-17 11:41:25 +08:00
|
|
|
package eval
|
|
|
|
|
2018-01-01 04:31:45 +08:00
|
|
|
import (
|
|
|
|
"testing"
|
|
|
|
|
2020-04-12 01:43:52 +08:00
|
|
|
"github.com/elves/elvish/pkg/eval/errs"
|
2019-12-24 04:00:59 +08:00
|
|
|
"github.com/elves/elvish/pkg/eval/vals"
|
2018-01-01 04:31:45 +08:00
|
|
|
)
|
2017-12-22 04:49:14 +08:00
|
|
|
|
2019-04-27 06:29:20 +08:00
|
|
|
func TestCompileValue(t *testing.T) {
|
2020-01-14 21:03:41 +08:00
|
|
|
home, cleanup := InTempHome()
|
2020-04-11 01:10:42 +08:00
|
|
|
mustCreateEmpty("file1")
|
|
|
|
mustCreateEmpty("file2")
|
2020-01-14 21:03:41 +08:00
|
|
|
defer cleanup()
|
|
|
|
|
2018-05-22 08:08:11 +08:00
|
|
|
Test(t,
|
|
|
|
// Compounding
|
|
|
|
// -----------
|
|
|
|
That("put {fi,elvi}sh{1.0,1.1}").Puts(
|
|
|
|
"fish1.0", "fish1.1", "elvish1.0", "elvish1.1"),
|
2017-12-17 11:41:25 +08:00
|
|
|
|
2020-03-31 08:02:14 +08:00
|
|
|
// As a special case, an empty compound expression evaluates to an empty
|
|
|
|
// string.
|
|
|
|
That("put {}").Puts(""),
|
|
|
|
That("put [&k=][k]").Puts(""),
|
|
|
|
|
|
|
|
// TODO: Test the case where util.GetHome returns an error.
|
|
|
|
|
|
|
|
// Error in any of the components throws an exception.
|
2020-04-12 01:43:52 +08:00
|
|
|
That("put a{[][1]}").Throws(errWithType{errs.OutOfRange{}}, "[][1]"),
|
2020-03-31 08:02:14 +08:00
|
|
|
// Error in concatenating the values throws an exception.
|
|
|
|
That("put []a").ThrowsMessage("cannot concatenate list and string"),
|
|
|
|
// Error when applying tilde throws an exception.
|
|
|
|
That("put ~[]").ThrowsMessage("tilde doesn't work on value of type list"),
|
|
|
|
|
2018-05-22 08:08:11 +08:00
|
|
|
// List, Map and Indexing
|
|
|
|
// ----------------------
|
2017-12-17 11:41:25 +08:00
|
|
|
|
2018-07-06 08:32:42 +08:00
|
|
|
That("echo [a b c] [&key=value] | each $put~").
|
|
|
|
Puts("[a b c] [&key=value]"),
|
2018-05-22 08:08:11 +08:00
|
|
|
That("put [a b c][2]").Puts("c"),
|
|
|
|
That("put [&key=value][key]").Puts("value"),
|
2017-12-17 11:41:25 +08:00
|
|
|
|
2020-03-31 11:45:32 +08:00
|
|
|
// Map keys and values may evaluate to multiple values as long as their
|
|
|
|
// numbers match.
|
|
|
|
That("put [&{a b}={foo bar}]").Puts(vals.MakeMap("a", "foo", "b", "bar")),
|
|
|
|
|
|
|
|
// List expression errors if an element expression errors.
|
2020-04-12 01:43:52 +08:00
|
|
|
That("put [ [][0] ]").Throws(errWithType{errs.OutOfRange{}}, "[][0]"),
|
2020-03-31 11:45:32 +08:00
|
|
|
// Map expression errors if a key or value expression errors.
|
2020-04-12 01:43:52 +08:00
|
|
|
That("put [ &[][0]=a ]").Throws(errWithType{errs.OutOfRange{}}, "[][0]"),
|
|
|
|
That("put [ &a=[][0] ]").Throws(errWithType{errs.OutOfRange{}}, "[][0]"),
|
2020-03-31 11:45:32 +08:00
|
|
|
// Map expression errors if number of keys and values in a single pair
|
|
|
|
// does not match.
|
|
|
|
That("put [&{a b}={foo bar lorem}]").ThrowsMessage("2 keys but 3 values"),
|
|
|
|
|
2018-05-22 08:08:11 +08:00
|
|
|
// String Literals
|
|
|
|
// ---------------
|
|
|
|
That(`put 'such \"''literal'`).Puts(`such \"'literal`),
|
2018-07-06 08:32:42 +08:00
|
|
|
That(`put "much \n\033[31;1m$cool\033[m"`).
|
|
|
|
Puts("much \n\033[31;1m$cool\033[m"),
|
2017-12-17 11:41:25 +08:00
|
|
|
|
2018-05-22 08:08:11 +08:00
|
|
|
// Captures
|
|
|
|
// ---------
|
2017-12-17 11:41:25 +08:00
|
|
|
|
2018-05-22 08:08:11 +08:00
|
|
|
// Output capture
|
|
|
|
That("put (put lorem ipsum)").Puts("lorem", "ipsum"),
|
|
|
|
That("put (print \"lorem\nipsum\")").Puts("lorem", "ipsum"),
|
2020-05-04 06:25:25 +08:00
|
|
|
// \r\n is also supported as a line separator
|
|
|
|
That(`print "lorem\r\nipsum\r\n" | all`).Puts("lorem", "ipsum"),
|
2017-12-17 11:41:25 +08:00
|
|
|
|
2018-05-22 08:08:11 +08:00
|
|
|
// Exception capture
|
|
|
|
That("bool ?(nop); bool ?(e:false)").Puts(true, false),
|
2017-12-17 11:41:25 +08:00
|
|
|
|
2018-05-22 08:08:11 +08:00
|
|
|
// Variable Use
|
|
|
|
// ------------
|
2017-12-17 11:41:25 +08:00
|
|
|
|
2020-01-12 20:39:25 +08:00
|
|
|
That("x = foo", "put $x").Puts("foo"),
|
|
|
|
// Must exist before use
|
|
|
|
That("put $x").DoesNotCompile(),
|
|
|
|
That("put $x[0]").DoesNotCompile(),
|
2018-05-22 08:08:11 +08:00
|
|
|
// Compounding
|
2020-01-12 20:39:25 +08:00
|
|
|
That("x = SHELL", "put 'WOW, SUCH '$x', MUCH COOL'\n").
|
2018-07-06 08:32:42 +08:00
|
|
|
Puts("WOW, SUCH SHELL, MUCH COOL"),
|
2018-05-22 08:08:11 +08:00
|
|
|
// Splicing
|
2020-01-12 20:39:25 +08:00
|
|
|
That("x = [elvish rules]", "put $@x").Puts("elvish", "rules"),
|
2017-12-17 11:41:25 +08:00
|
|
|
|
2019-10-23 05:49:32 +08:00
|
|
|
// Variable namespace
|
|
|
|
// ------------------
|
|
|
|
|
|
|
|
// Pseudo-namespace local: accesses the local scope.
|
|
|
|
That("x = outer; { local:x = inner; put $local:x }").Puts("inner"),
|
|
|
|
// Pseudo-namespace up: accesses upvalues.
|
|
|
|
That("x = outer; { local:x = inner; put $up:x }").Puts("outer"),
|
|
|
|
// Pseudo-namespace builtin: accesses builtins.
|
|
|
|
That("put $builtin:true").Puts(true),
|
|
|
|
// Unqualified name prefers local: to up:.
|
|
|
|
That("x = outer; { local:x = inner; put $x }").Puts("inner"),
|
|
|
|
// Unqualified name resolves to upvalue if no local name exists.
|
|
|
|
That("x = outer; { put $x }").Puts("outer"),
|
|
|
|
// Unqualified name resolves to builtin if no local name or upvalue
|
|
|
|
// exists.
|
|
|
|
That("put $true").Puts(true),
|
|
|
|
// A name can be explicitly unqualified by having a leading colon.
|
|
|
|
That("x = val; put $:x").Puts("val"),
|
|
|
|
That("put $:true").Puts(true),
|
|
|
|
|
|
|
|
// Pseudo-namespace E: provides read-write access to environment
|
|
|
|
// variables. Colons inside the name are supported.
|
|
|
|
That("set-env a:b VAL; put $E:a:b").Puts("VAL"),
|
|
|
|
That("E:a:b = VAL2; get-env a:b").Puts("VAL2"),
|
|
|
|
|
|
|
|
// Pseudo-namespace e: provides readonly access to external commands.
|
|
|
|
// Only names ending in ~ are resolved, and resolution always succeeds
|
|
|
|
// regardless of whether the command actually exists. Colons inside the
|
|
|
|
// name are supported.
|
|
|
|
That("put $e:a:b~").Puts(ExternalCmd{Name: "a:b"}),
|
|
|
|
|
|
|
|
// A "normal" namespace access indexes the namespace as a variable.
|
|
|
|
That("ns: = (ns [&a= val]); put $ns:a").Puts("val"),
|
|
|
|
// Multi-level namespace access is supported.
|
|
|
|
That("ns: = (ns [&a:= (ns [&b= val])]); put $ns:a:b").Puts("val"),
|
|
|
|
// Multi-level namespace access can have a leading colon to signal that
|
|
|
|
// the first component is unqualified.
|
|
|
|
That("ns: = (ns [&a:= (ns [&b= val])]); put $:ns:a:b").Puts("val"),
|
|
|
|
// Multi-level namespace access can be combined with the local:
|
|
|
|
// pseudo-namespaces.
|
|
|
|
That("ns: = (ns [&a:= (ns [&b= val])]); put $local:ns:a:b").Puts("val"),
|
|
|
|
// Multi-level namespace access can be combined with the up:
|
|
|
|
// pseudo-namespaces.
|
|
|
|
That("ns: = (ns [&a:= (ns [&b= val])]); { put $up:ns:a:b }").Puts("val"),
|
|
|
|
|
2018-05-22 08:08:11 +08:00
|
|
|
// Tilde
|
|
|
|
// -----
|
2020-01-14 21:03:41 +08:00
|
|
|
That("put ~").Puts(home),
|
|
|
|
That("put ~/src").Puts(home+"/src"),
|
2020-03-31 11:45:32 +08:00
|
|
|
// Make sure that tilde processing retains trailing slashes.
|
2020-01-14 21:03:41 +08:00
|
|
|
That("put ~/src/").Puts(home+"/src/"),
|
2020-03-31 11:45:32 +08:00
|
|
|
// Tilde and wildcard.
|
|
|
|
That("put ~/*").Puts(home+"/file1", home+"/file2"),
|
2020-01-14 21:03:41 +08:00
|
|
|
// TODO(xiaq): Add regression test for #793.
|
2017-12-17 11:41:25 +08:00
|
|
|
|
2018-05-22 08:08:11 +08:00
|
|
|
// Closure
|
|
|
|
// -------
|
2017-12-17 11:41:25 +08:00
|
|
|
|
2018-05-22 08:08:11 +08:00
|
|
|
That("[]{ }").DoesNothing(),
|
|
|
|
That("[x]{put $x} foo").Puts("foo"),
|
2017-12-17 11:41:25 +08:00
|
|
|
|
2018-05-22 08:08:11 +08:00
|
|
|
// Variable capture
|
|
|
|
That("x=lorem; []{x=ipsum}; put $x").Puts("ipsum"),
|
|
|
|
That("x=lorem; []{ put $x; x=ipsum }; put $x").Puts("lorem", "ipsum"),
|
2017-12-17 11:41:25 +08:00
|
|
|
|
2018-05-22 08:08:11 +08:00
|
|
|
// Shadowing
|
|
|
|
That("x=ipsum; []{ local:x=lorem; put $x }; put $x").Puts("lorem", "ipsum"),
|
2017-12-17 11:41:25 +08:00
|
|
|
|
2018-05-22 08:08:11 +08:00
|
|
|
// Shadowing by argument
|
|
|
|
That("x=ipsum; [x]{ put $x; x=BAD } lorem; put $x").Puts("lorem", "ipsum"),
|
2017-12-17 11:41:25 +08:00
|
|
|
|
2018-05-22 08:08:11 +08:00
|
|
|
// Closure captures new local variables every time
|
|
|
|
That(`fn f []{ x=0; put []{x=(+ $x 1)} []{put $x} }
|
2018-05-22 07:54:29 +08:00
|
|
|
{inc1,put1}=(f); $put1; $inc1; $put1
|
2019-04-27 06:29:20 +08:00
|
|
|
{inc2,put2}=(f); $put2; $inc2; $put2`).Puts("0", 1.0, "0", 1.0),
|
2017-12-17 11:41:25 +08:00
|
|
|
|
2018-05-22 08:08:11 +08:00
|
|
|
// Rest argument.
|
|
|
|
That("[x @xs]{ put $x $xs } a b c").Puts("a", vals.MakeList("b", "c")),
|
|
|
|
// Options.
|
|
|
|
That("[a &k=v]{ put $a $k } foo &k=bar").Puts("foo", "bar"),
|
|
|
|
// Option default value.
|
|
|
|
That("[a &k=v]{ put $a $k } foo").Puts("foo", "v"),
|
2020-03-31 11:45:32 +08:00
|
|
|
|
|
|
|
// Argument name must be unqualified.
|
|
|
|
That("[a:b]{ }").DoesNotCompile(),
|
|
|
|
// Argument name must not be empty.
|
|
|
|
That("['']{ }").DoesNotCompile(),
|
|
|
|
That("[@]{ }").DoesNotCompile(),
|
|
|
|
// Only the last argument may be prefixed with @.
|
|
|
|
That("[@a b]{ }").DoesNotCompile(),
|
|
|
|
// Option name must be unqualified.
|
|
|
|
That("[&a:b=1]{ }").DoesNotCompile(),
|
|
|
|
// Option name must not be empty.
|
|
|
|
That("[&''=b]{ }").DoesNotCompile(),
|
|
|
|
|
2020-04-13 20:24:49 +08:00
|
|
|
// Exception when evaluating option default value.
|
|
|
|
That("[&a=[][0]]{ }").Throws(errWithType{errs.OutOfRange{}}, "[][0]"),
|
2020-03-31 11:45:32 +08:00
|
|
|
// Option default value must be one value.
|
2020-04-13 20:24:49 +08:00
|
|
|
That("[&a=(put foo bar)]{ }").Throws(
|
|
|
|
errs.ArityMismatch{
|
|
|
|
What: "option default value", ValidLow: 1, ValidHigh: 1, Actual: 2},
|
|
|
|
"(put foo bar)"),
|
2018-05-22 08:08:11 +08:00
|
|
|
)
|
2018-05-22 07:54:29 +08:00
|
|
|
}
|