An important new feature of the Binder Shell is that all of its data operations make use of SValue. That is, all environment variables, command arguments, and command results are actually typed data.
The default type the shell uses is a string (for POSIX compatibility), as can be seen here using the standard shell syntax of assigning a value to an environment variable:
/# S=foo Result: "foo"
There is also an extension to the POSIX shell syntax, @{ ... }
, that allows you to construct values of specific types using the underlying SValue APIs. Unless otherwise specified, the data inside of the braces will be interpreted as an integer, float, boolean, or string:
/# I=@{1} Result: int32_t(1 or 0x1) /# I=@{[abcd]} Result: int32_t(1633837924 or 0x61626364) /# F=@{1.0} Result: float(1.0) /# B=@{false} Result: false /# B=@{true} Result: true /# S=@{"1"} Result: "1" /# S=@{foo} Result: "foo"
You can also use conversion operators to generate some additional types or convert existing variables to another type:
/# I64=@{ (int64_t)10 } Result: int64_t(10 or 0xa) /# T=@{ (nsecs_t)123456 } Result: nsecs_t(123us 456ns or 0x1e240) /# B=@{ (bool)$I64 } Result: true /# I=@{ (int32_t)$B } Result: int32_t(1 or 0x1) /# S=@{ (string)$I } Result: "1" /# S=@{ (string)$B } Result: "true"
A value can also contain a complex mapping of other values. Such mappings (and sets) are constructed with the ->
and ,
operators:
/# M=@{ a -> b } Result: "a" -> "b" /# S=@{ a, b } Result: { "a", "b" } /# M2=@{ $M, c -> d } Result: { "a" -> "b", "c" -> "d" }
There are three types of operations that can be used to build values containing multiple mappings. A join is created with the ,
or +
operator, which you have already seen. It is non-destructive the result contains all data supplied on both sides of the join.
/# M=@{ a->b, a->c } Result: "a" -> { "b", "c" }
An overlay operation is performed with the +<
operator. It is like a join, except that it selects items on the right side when the same key appears on both the left and right sides. In other words, it replaces values on the left with values on the right, leaving the left side as-is when there is nothing on the right.
/# M=@{ {a->b,c->d} +< {a->e,f->g} } Result: { "a" -> "e", "c" -> "d", "f" -> "g" }
An inherit operation is performed with the +>
operator. It is the opposite of an overlay, selecting values on the left side instead of the right.
/# M=@{ {a->b,c->d} +> {a->e,f->g} } Result: { "a" -> "b", "c" -> "d", "f" -> "g" }
You can use the []
operator to look up a value from a mapping key:
/# M=@{ a -> b + c -> d } Result: { "a" -> "b", "c" -> "d" } /# V=@{ $M[c] } Result: "d"
There are three special types of values with different semantics than normal types, especially when used in mappings. The undefined
value is the state of a variable that has not been set; trying to create a mapping from some value to undefined
will result in undefined:
/# U=@{undef} Result: undefined /# M=@{ a -> undef } Result: undefined /# M=@{ undef -> a } Result: undefined
The wild
value is one that means "everything." In fact, every value you build is a mapping; a value containing a single data item is really the mapping { wild -> value }
, but the leading wild
is dropped for convenience. This allows us to create a consistent set of mapping operations on values, which are well-defined regardless of the contents of the values.
/# W=@{wild} Result: wild /# M=@{ wild -> a } Result: "a" /# M=@{ a -> wild } Result: "a" -> wild
The null
value means "not specified" or "empty". It is different than undefined in that it has no special significance in terms of mappings, however it does have special meaning when marshalling and unmarshalling across Binder calls it specifies that the default parameter value should be used.
/# N=@{null} Result: null /# M=@{ null -> a } Result: null -> "a" /# M=@{ a -> null } Result: "a" -> null