S-Lang supports a variety of operators that are grouped into three classes: assignment operators, binary operators, and unary operators.
An assignment operator is used to assign a value to a variable. They will be discussed more fully in the context of the assignment statement in the section on Assignment Statements.
An unary operator acts only upon a single quantity while a binary
operation is an operation between two quantities. The boolean
operator not
is an example of an unary operator. Examples of
binary operators include the usual arithmetic operators
+
, -
, *
, and /
. The operator given by
-
can be either an unary operator (negation) or a binary operator
(subtraction); the actual operation is determined from the context
in which it is used.
Binary operators are used in algebraic forms, e.g., a + b
.
Unary operators fall into one of two classes: postfix-unary or
prefix-unary. For example, in the expression -x
, the minus
sign is a prefix-unary operator.
All binary and unary operators may be defined for any supported
data type. For example, the arithmetic plus operator has been
extended to the String_Type
data type to permit
concatenation between strings. But just because it is possible to
define the action of an operator upon a data type, it does not mean
that all data types support all the binary and unary operators.
For example, while String_Type
supports the +
operator, it does not admit the *
operator.
The unary operators operate only upon a single operand. They
include: not
, ~
, -
, @
, &
, as well as the
increment and decrement operators ++
and --
,
respectively.
The boolean operator not
acts only upon integers and produces
0
if its operand is non-zero, otherwise it produces 1.
The bit-level not operator ~
performs a similar function,
except that it operates on the individual bits of its integer
operand.
The arithmetic negation operator -
is perhaps the most
well-known unary operator. It simply reverses the sign of its
operand.
The reference (&
) and dereference (@
) operators will be
discussed in greater detail in the section on
Referencing Variables.
Similarly, the increment (++
) and decrement (--
)
operators will be discussed in the context of the assignment
operator.
The binary operators may be grouped according to several classes: arithmetic operators, relational operators, boolean operators, and bitwise operators.
The arithmetic operators include +
, -
, *
, and /
,
which perform addition, subtraction, multiplication, and division,
respectively. In addition to these, S-Lang supports the mod
operator, which divides two numbers and produces the remainder, as
as well as the power operator ^
.
The data type of the result produced by the use of one of these
operators depends upon the data types of the binary participants.
If they are both integers, the result will be an integer. However,
if the operands are not of the same type, they will be converted to
a common type before the operation is performed. For example, if
one is a floating point type and the other is an integer, the
integer will be converted to a float. In general, the promotion
from one type to another is such that no information is lost, if
possible. As an example, consider the expression 8/5
which
indicates division of the integer 8 by the integer 5.
The result will be the integer 1 and not the floating
point value 1.6
. However, 8/5.0
will produce
1.6
because 5.0
is a floating point number.
The relational operators are >
, >=
, <
, <=
,
==
, and !=
. These perform the comparisons greater
than, greater than or equal, less than, less than or equal, equal,
and not equal, respectively. For most data types, the result of
the comparison will be a boolean value; however, for arrays the
result will be an array of boolean values. The section on arrays
will explain this is greater detail.
Note: For S-Lang versions 2.1 and higher, relational expressions
such as a<b<=c
are defined in the mathematical sense, i.e.,
((a < b) and (b <= c))
Simarily, (a < b <= c < d)
is the same as
((a < b) and (b <= c) and (c < d))
and so on. In previous versions of S-Lang, (a<b<c)
meant
(a<b)<c
; however this interpretation was not very useful.
S-Lang supports four boolean binary operators: or
,
and
, ||
, and &&
, which for most data types,
return a boolean result. In particular, the or
and
||
operators return a non-zero value (boolean TRUE) if
either of their operands are non-zero, otherwise they produce zero
(boolean FALSE). The and
and &&
operators produce a
non-zero value if and only if both their operands are non-zero,
otherwise they produce zero.
Unlike the operators &&
and ||
, the and
and
or
operators do not perform the so-called boolean
short-circuit evaluation. For example, consider the expression:
(x != 0) and (1/x > 10)
Here, if x
were to have a value of zero, a division by zero error
would occur because even though x!=0
evaluates to zero, the
and
operator is not short-circuited and the 1/x
expression
would still be evaluated. This problem can be avoided using the
short-circuiting &&
operator:
(x != 0) && (1/x > 10)
Another difference between the short-circuiting (&&,||
) and
the non-short-circuiting operators (and,or
) is that the
short-circuiting forms work only with integer or boolean types. In
contrast, if either of the operands of the and
or or
operators is an array then a corresponding array of boolean values
will result. This is explained in more detail in the section on
arrays.
Note: the short-circuiting operators &&
and ||
were
first introduced in S-Lang 2.1; they are not available in older
versions.
The bitwise binary operators are currently defined for integer operands
and are used for bit-level operations. Operators that fall in this
class include &
, |
, shl
, shr
, and
xor
. The &
operator performs a boolean AND operation
between the corresponding bits of the operands. Similarly, the
|
operator performs the boolean OR operation on the bits.
The bit-shifting operators shl
and shr
shift the bits
of the first operand by the number given by the second operand to
the left or right, respectively. Finally, the xor
performs
an EXCLUSIVE-OR operation.
These operators are commonly used to manipulate variables whose
individual bits have distinct meanings. In particular, &
is
usually used to test bits, |
can be used to set bits, and
xor
may be used to flip a bit.
As an example of using &
to perform tests on bits, consider
the following: The jed text editor stores some of the information
about a buffer in a bitmapped integer variable. The value of this
variable may be retrieved using the jed intrinsic function
getbuf_info
, which actually returns four quantities: the
buffer flags, the name of the buffer, directory name, and file
name. For the purposes of this section, only the buffer flags are
of interest and can be retrieved via a function such as
define get_buffer_flags ()
{
variable flags;
(,,,flags) = getbuf_info ();
return flags;
}
The buffer flags object is a bitmapped quantity where the 0th bit
indicates whether or not the buffer has been modified, the first
bit indicates whether or not autosave has been enabled for the
buffer, and so on. Consider for the moment the task of determining
if the buffer has been modified. This can be determined by looking
at the zeroth bit: if it is 0 the buffer has not been
modified, otherwise it has been modified. Thus we can create the
function,
define is_buffer_modified ()
{
variable flags = get_buffer_flags ();
return (flags & 1);
}
where the integer 1 has been used since it is represented as
an object with all bits unset, except for the zeroth one, which is
set. (At this point, it should also be apparent that bits are
numbered from zero, thus an 8 bit integer consists of bits
0 to 7, where 0 is the least significant bit
and 7 is the most significant one.) Similarly, we can create
another function
define is_autosave_on ()
{
variable flags = get_buffer_flags ();
return (flags & 2);
}
to determine whether or not autosave has been turned on for the
buffer.
The shl
operator may be used to form the integer with only
the nth bit set. For example, 1 shl 6
produces an
integer with all bits set to zero except the sixth bit, which is
set to one. The following example exploits this fact:
define test_nth_bit (flags, nth)
{
return flags & (1 shl nth);
}
The operator ->
is used to in conjunction with a
namespace to access an object within the namespace. For example,
if A
is the name of a namespace containing the variable
v
, then A->v
refers to that variable. Namespaces are
discussed more fully in the chapter on
Namespaces.
Care must be exercised when using binary operators with an operand that returns multiple values. In fact, the current implementation of the S-Lang language will produce incorrect results if both operands of a binary expression return multiple values. At most, only one of operands of a binary expression can return multiple values, and that operand must be the first one, not the second. For example,
define read_line (fp)
{
variable line, status;
status = fgets (&line, fp);
if (status == -1)
return -1;
return (line, status);
}
defines a function, read_line
that takes a single argument
specifying a handle to an open file, and returns one or two values,
depending upon the return value of fgets
. Now consider
while (read_line (fp) > 0)
{
text = ();
% Do something with text
.
.
}
Here the relational binary operator >
forms a comparison
between one of the return values (the one at the top of the stack)
and 0. In accordance with the above rule, since read_line
returns multiple values, it must occur as the left binary operand.
Putting it on the right as in
while (0 < read_line (fp)) % Incorrect
{
text = ();
% Do something with text
.
.
}
violates the rule and will result in the wrong answer. For this
reason, one should avoid using a function that returns muliple
return values as a binary operand.
If a binary operation (+
, -
, *
, /
) is
performed on two integers, the result is an integer. If at least
one of the operands is a floating point value, the other will be
converted to a floating point value, and a floating point result
be produced. For example:
11 / 2 --> 5 (integer)
11 / 2.0 --> 5.5 (double)
11.0 / 2 --> 5.5 (double)
11.0 / 2.0 --> 5.5 (double)
Sometimes to achive the desired result, it is necessary to
explicitly convert from one data type to another. For example,
suppose that a
and b
are integers, and that one wants
to compute a/b
using floating point arithmetic. In such a
case, it is necessary to convert at least one of the operands to a
floating point value using, e.g., the double
function:
x = a/double(b);
As of S-Lang version 2.1, use of the andelse
and
orelse
have been deprecated in favor of the &&
and
||
short-circuiting operators.
The boolean operators or
and and
are not short
circuited as they are in some languages. S-Lang uses orelse
and andelse
expressions for short circuit boolean evaluation.
However, these are not binary operators. Expressions of the form:
expr-1 and expr-2 and ... expr-n
can be replaced by the short circuited version using andelse
:
andelse {expr-1} {expr-2} ... {expr-n}
A similar syntax holds for the orelse
operator. For example, consider
the statement:
if ((x != 0) and (1/x > 10)) do_something ();
Here, if x
were to have a value of zero, a division by zero error
would occur because even though x!=0
evaluates to zero, the
and
operator is not short circuited and the 1/x
expression
would be evaluated causing division by zero. For this case, the
andelse
expression could be used to avoid the problem:
if (andelse
{x != 0}
{1 / x > 10}) do_something ();