Skip to content

Operators

datanorris edited this page Jan 24, 2016 · 25 revisions

Class-defined operators

Unary operators

  • (unary minus)
  • (unary +) ~

! not

Binary arithmetic operators

& (binary & or formal block arg)

  • (binary minus) %
  • (binary *) ** / (binary /) ^ |
  • (binary +) << (left shift)

Match operators

=~ !~

Comparison operators

<

= <= <=> == != ===

Language-defined operators

The following operators are defined by the language - their behaviour can't be altered.

Simple assignment

<lhs> = <value>
<lhs> = <asgn_value> rescue <resc_value>

Returns: The value assigned

Same as other languages - assigns a value to a variable. <lhs> can be:

  • a local variable
  • an instance variable
  • a class variable
  • a global variable
  • an array reference
  • an attribute reference
  • a constant reference

Note that a local variable in <lhs> which did not previously exist will be created by this expression. That same local variable can actually be used as part of the <value> expression - its value in such usage will be nil.

In some circumstances a rescue modifier may be attached to the assignment expression, whereas normally they can only be applied to whole statements. This results in the value of <asgn_value> rescue <resc_value> being assigned to the variable. See the operator precedence section for rules on when this is permitted.

Multiple right-hand-side assignment

<lhs> = <mrhs>

Returns: the value of <mrhs> as encapsulated in an Array

<mrhs> is a comma-separated list of regular and splat call arguments, and is processed similar to a method call. The resulting list of values is put in an Array, and this value is assigned to the <lhs>. In other words, this syntax is equivalent to assigning an array literal with <lhs> = [ <mrhs> ] except that the <mrhs> syntax does not permit hash associations or trailing commas.

Multiple left-hand-side assignment

<mlhs> = <value>
<mlhs> = <mrhs>

Returns: the value of <value> or <mrhs>

Multiple comma-separated variable references are provided in <mlhs>, and the value to be assigned is treated as an Array, its contents assigned to variable references in <mlhs>. Any variable references permitted in a simple assignments are also permitted in multiple left-hand-side assignments.

<mlhs> is a comma-separated list of at least 1 of the following elements, in order:

  • 0 or more nested "mandatory" variable references, which may be (in any order):
  • Simple variable references
  • Nested multiple left-hand-side structures enclosed in ( and )
  • An optional "rest" variable reference, which may be:
  • A regular "rest" variable reference, which is written as a variable reference prefixed by * (e.g. *rest)
  • An anonymous "rest" reference, which is written as *
  • If a "rest" variable reference is present
  • 0 or more "post" variable references, which have the same form as nested "mandatory" variable references

Nested multiple left-hand-side structures have the same form as <mlhs>.

Some additional syntax rules apply:

  • If <mlhs> or a nested multiple left-hand-side structure contains only "mandatory" references, it may be followed by a trailing ,
  • If there is only a single "mandatory" reference, it MUST be followed by a trailing ,
  • <mlhs> (excluding nested structures) may optionally be enclosed in ( and ), without changing the meaning of the expression (i.e. without it being considered a nested expression). Note that this means that a, b = c and (a, b) = c mean the same thing, however ((a, b)) = c is different.

A value is assigned to <mlhs> as follows:

  • Convert the value to Array if necessary:
  • If the value is already an Array or an instance of a subclass of Array, OK
  • Otherwise, conversion to an Array is attempted by calling the to_ary method on the value
    • If the to_ary method call fails to resolve or returns nil, the conversion has failed
    • If the returned object from to_ary is not a subclass of Array, an exception is thrown
    • Otherwise, OK
  • If the conversion failed (without throwing an exception), then the conversion is effected by creating a new Array of 1 element, which is the value
  • The contents of the array are assigned to:
  • "mandatory" variable references, from left to right, then
  • if a "rest" reference is present, any values in the array in excess of the number of mandatory/post references are selected and:
    • if the "rest" reference is anonymous, ignored
    • otherwise, compiled into an array and assigned to the "rest" reference
  • "post" variable references, from left to right
  • if there are fewer elements in the array than there are mandatory/post variable references, the unset references are set to nil
  • Any remaining values in the array are ignored
  • If there is a regular "rest" variable reference and no array elements to assign to it, it is assigned an empty array
  • If there are nested multiple left-hand-side structures in mandatory or post, this array conversion and assignment process is repeated for them

Some examples:

a, b = [1, 2, 3] # a = 1, b = 2
a, *, (b, c) = [1, 2, 3, [4, 5]] # a = 1, b = 4, c = 5

Complex assignment - binary arithmetic operators

<lhs> <op>= <value>

Returns: the value of after the expression is processed

There are complex assignment forms for all the binary arithmetic operators, formed by writing an = on the end of the operator e.g. +=. For these operators, an expression a <op>= b is equivalent to a = a <op> b, e.g. a += b is equivalent to a = a + b.

All complex assignments may not have multiple left- or right-hand-sides, and they may not have explicitly-scoped constant references on their left-hand-side.

Complex assignment - logical operators

<lhs> &&= <value>
<lhs> ||= <value>

Returns: the value of after the expression is processed

Mostly, these are similar to other complex assignment operators (i.e. a &&= b is like a = a && b), except:

  • If the expression is short circuited and <value> is never evaluated, then the assignment is not performed at all, which has implications for assignments that may have side effects, especially:
  • global variable assignments may cause trace procedures to be invoked
  • constant assignments may assign to a different constant than the one that is accessed
  • attribute and array reference assignments invoke methods to perform the assignment, which may have whatever side effects they like
  • In some cases, the ||= expression has the ability to determine that <lhs> is falsy when it's not defined at all - so it assigns to the <lhs> when other complex assignments would throw an exception

Specific behaviour for a &&= b:

  • If a is other than a local variable, equivalent to a && (a = b), i.e. doesn't assign if a is falsy
  • If a is a local variable, it is equivalent to a && (a = b) except that if a was not defined before the expression, a &&= b defines it and therefore doesn't cause a syntax error (whereas a && (a = b) would cause a syntax error). Since assigning to local variables has no side effects, you might say this form is equivalent to a = a && b even though an assignment is not performed if a is falsy

Specific behaviour for a ||= b:

  • If a is an array or attribute reference, equivalent to a || (a = b), i.e. doesn't assign if a is truthy
  • If a is a local variable, it is equivalent to a || (a = b) except that if a was not defined before the expression, a ||= b defines it and therefore doesn't cause a syntax error (whereas a || (a = b) would cause a syntax error). Since assigning to local variables has no side effects, you might say this form is equivalent to a = a || b even though an assignment is not performed if a is truthy
  • If a is a global variable, instance variable, class variable, or (implicitly namespaced) constant, it will also test to see if the referenced variable is defined and, if not, perform the assignment i.e. it's equivalent to (defined? a && a) || (a = b). For undefined global variables, this appears to make no difference as they return nil anyway, but for the others it prevents an exception being thrown due to the variables being undefined

Logical operators

Logical and

<value> && <value>
<value> and <value>

Returns: the left value, if falsy, otherwise the right value

Similar to other languages, in that it returns a truthy value if both operands are truthy, and a falsy value otherwise. Uses short-circuit evaluation - the second operand is not evaluated if the first is falsy. Typically used to test whether two values are both truthy, however can also be used to evaluate the second value on the condition that the first one is truthy.

a && b therefore behaves like:

(
  tmp = a
  if tmp
    b
  else
    tmp
  end
)

Both &&, and forms of the operator have the same meaning, however they have different positions in the syntax grammar and precedence.

Logical or

<value> || <value>
<value> or <value>

Returns: the left value, if truthy, otherwise the right value

Similar to other languages, in that returns a truthy value if one of the operands is truthy, and a falsy value otherwise. Uses short-circuit evaluation - the second operand is not evaluated if the first is truthy. Typically used to test whether at least one of two values is truthy, however can also be used to evaluate the second value on the condition that the first one is falsy.

a || b therefore behaves like:

(
  tmp = a
  unless tmp
    b
  else
    tmp
  end
)

Both ||, or forms of the operator have the same meaning, however they have different positions in the syntax grammar and precedence.

Ternary ?:

<cond> ? <value-if-true> : <value-if-false>

Returns: see if expression

Standard ternary operator - equivalent to if <cond> then <value-if-true> else <value-if-false> end, including the special condition processing that applies to if expressions

defined?

defined? <expr>

Returns: nil if undefined, otherwise a String (see below)

The defined? operator is used to test whether <expr> is defined. <expr> is not evaluated, its structure is examined at runtime to determine whether it is defined. If not, it returns nil, otherwise it returns a String. The behaviour of defined? based on the <expr> that is passed to it is described below:

<expr> Is defined if Return value
nil Always "nil"
self Always "self"
true Always "true"
false Always "false"
An array literal Every element within is defined? (short-circuiting on the first failure) "expression"
A local variable Always (a local variable which is not defined is a method call) "local-variable"
An instance variable The variable is found "instance-variable"
A class variable The variable is found "class-variable"
$&, $` , $', $", backreferences The variable is not nil "global-variable"
An other global variable The variable exists (except for where it has been referenced but never assigned to) "global-variable"
An implicitly-namespaced constant The constant is defined according to constant-defined logic "constant"
An explicitly-namespaced constant The namespace is defined? and (after evaluating it) the constant is defined on it according to constant-defined logic "constant"
An assignment expression Always "assignment"
Any other Always "expression"
  • DEFINED_FUNC: rb_method_boundp(CLASS_OF(scope), obj, 0 (any visibility)) => ""method""
  • DEFINED_METHOD:
    • me = rb_method_entry(CLASS_OF(scope), obj)
    • if me is public or protected and rb_obj_is_kind_of(GET_SELF(), rb_class_real(CLASS_OF(scope))), ""method""
    • if scope.respond_to_missing?(obj, false) => ""method""
  • DEFINED_YIELD: GET_BLOCK_PTR() => ""yield""
  • DEFINED_ZSUPER:
    • climb GET_ISEQ()'s ->parent_iseqs until one has defined_method_id
    • rb_method_boundp(vm_search_normal_superclass(iseq->klass, GET_SELF()), iseq->defined_method_id, 0 (any visibility)) => ""super""

" - NODE_[FV]?CALL/ATTRASGN:

  • if ->nd_args
    • defined_expr(->nd_args, Qfalse) // will deep-dive into simple arg NODE_ARRAYS but not for other types of args
    • emit branchunless(lfinish[1]) (creating lfinish[1] if necessary)
  • if we're a self call (i.e. [FV]CALL or ATTRASGN with ->nd_recv==1)
    • emit putself
    • emit defined(DEFINED_FUNC, ->nd_mid)
  • otherwise
    • defined_expr(->nd_recv, Qfalse)
    • emit branchunless(lfinish[1]) (creating lfinish[1] if necessary)
    • COMPILE(->nd_recv)
    • emit defined(DEFINED_METHOD, ->nd_mid)
  • return
  • NODE_YIELD - emit putnil, defined(DEFINED_YIELD, 0), return
  • NODE_Z?SUPER - emit putnil, defined(DEFINED_ZSUPER, 0), return if we have a string (always by this point?):
  • if needstr != Qfalse, emit putstring(str), otherwise emit putobject(Qtrue)"

No exceptions

Range constructors

Also for flipflops

.. ...

Operator precedence

Broadly, the following table describes the precedence of Ruby operators.

No|Type/Associativity|Description|Operators ---|---|---|---|--- 1|Binary left|Reference|. :: 2|Unary|General|! ~ + 3|Binary right|Power|** 4|Unary|Minus|- 5|Binary left|Products|* / % 6|Binary left|Additions|+ - 7|Binary left|Shifts|<< >> 8|Binary left|Bitwise and|& 9|Binary left|Bitwise or|| ^ 10|Binary left (TODO why?)|Directional comparisons|> >= < <= 11|Binary none|General comparisons|<=> == === != =~ !~ 12|Binary left|Logical and, argument form|&& 13|Binary left|Logical or, argument form||| 14|Binary left|Range constructors|.. ... 15|Ternary right|Logical ternary|?: 16|Unary|Defined|defined? 17|Binary none|Assignment modifier|rescue 18|Binary right|Assignments|= += -= etc. 19|Unary|Logical not, expression form|not 20|Binary left|Logical and/or, expression form|and or 21|Binary left|Statement modifiers|if unless while until rescue

Some special rules also apply:

  • If a rescue modifier is applied to an assignment, e.g. <lhs> = <rhs> rescue <value>, rescue becomes an assignment modifier and takes precedence over the assignment operator (i.e. it will be interpreted as <lhs> = ( <rhs> rescue <value> )) according to the following rules:
  • The assignment operator may be simple or complex
  • This does not apply to an assignment with a multiple left-hand-side, a multiple right-hand-side or a command call on the right-hand-side
  • If the argument to defined? or not? is within parentheses and there is no whitespace preceding it, then the operator has the highest precedence (higher even than . and ::)

Clone this wiki locally