space_ex v0.8.0 SpaceEx.ExpressionBuilder.Syntax

Syntax functions and special forms allowed in SpaceEx.ExpressionBuilder blocks.

In order to use the expression builder, your expression must be capable of being parsed using only the functions in this module, plus the procedure call syntax (see below).

Wherever possible, the expression builder syntax is designed to be similar to real Elixir code. Infix operators (like ==/2 and &&/2) are used when available; function syntax is used when not (e.g. xor/2).

Procedure calls

Procedure calls are the only “special” syntax not covered by the functions in this module.

Any expression in the form of Module.function(args) will be treated as a kRPC call. Thus,

SpaceEx.ExpressionBuilder.build conn1 do
  SpaceEx.SpaceCenter.ut(conn2)
end

will be turned into

SpaceEx.KRPC.Expression.call(
  conn1,
  SpaceEx.SpaceCenter.ut(conn2) |> SpaceEx.ProcedureCall.create()
)

Using this automatic syntax, you can build complex expressions with relative ease:

ExpressionBuilder.build conn do
  Orbit.periapsis_altitude(orbit) > double(150_000) && Orbit.eccentricity(orbit) < double(0.1)
end

This results in seven different SpaceEx.KRPC.Expression objects being created, and would normally involve a nested mess of code that is too long and ugly to include here.

Note that you can include arbitrary arguments to your procedure calls, and even pipeline procedure calls together within the expression builder syntax, but only the outermost procedure call (or the rightmost, if using the |>/2 operator) will be stored as part of the expression. See the next section for details.

Expressions are constant

The expression builder syntax is very minimal, and it does not tolerate syntax outside of the functions and operators listed here. However, several of these do allow for arbitrary Elixir expressions to be nested inside them — for example, you could do int(x + 10), string("My name is #{inigo.name}"), etc.

Since expressions are used for SpaceEx.Event calls, and Events are effectively running over and over on the kRPC server, you might wonder if this is somehow executing arbitrary code on the server. But what’s actually going on is, your code is being evaluated only once — at the time you build the expression — and then “baked in” to the expression.

So if you try to do something like string("The current time is #{Time.utc_now()}"), you’re effectively only doing string("The current time is 12:32:54.497540"), and it’s not going to be current for very long.

The only thing that can be dynamic in an expression are the procedure calls. However, even here, you also need to be careful. If you try to build an expression such as

ExpressionBuilder.build conn do
  SpaceCenter.active_vessel() |> Vessel.flight() |> Flight.mean_altitude() < double(70_000)
end

then you might expect that it would continually monitor the altitude of the current vessel, and follow you if you change vessels. But this isn’t what’s happening!

In fact, every call except the final Flight.mean_altitude is being executed immediately, when the expression is created. A clearer version would be

flight = SpaceCenter.active_vessel() |> Vessel.flight()

ExpressionBuilder.build conn do
  Flight.mean_altitude(flight) < double(70_000)
end

since this makes it more obvious that the only dynamic call is Flight.mean_altitude(flight), and the flight parameter is baked in to the expression and cannot change.

Link to this section Summary

Functions

Builds a SpaceEx.KRPC.Expression.add/3 expression

Builds a SpaceEx.KRPC.Expression.constant_double/3 expression

Builds a SpaceEx.KRPC.Expression.constant_float/3 expression

Builds a SpaceEx.KRPC.Expression.constant_int/3 expression

Builds a SpaceEx.KRPC.Expression.constant_string/3 expression

Pipe operator

Builds a SpaceEx.KRPC.Expression.or/3 expression

Link to this section Functions

Builds a SpaceEx.KRPC.Expression.left_shift/3 expression.

The Elixir equivalent would be Bitwise.<<</2.

Builds a SpaceEx.KRPC.Expression.right_shift/3 expression.

The Elixir equivalent would be Bitwise.>>>/2.

Builds a SpaceEx.KRPC.Expression.constant_double/3 expression.

Use this when comparing against “high precision” decimals.

Remember that, although value will accept arbitrary Elixir code, it will be turned into a constant value when the expression is built. See “Expressions are constant” in the module documentation.

Builds a SpaceEx.KRPC.Expression.constant_float/3 expression.

Use this when comparing against “low precision” decimals.

Remember that, although value will accept arbitrary Elixir code, it will be turned into a constant value when the expression is built. See “Expressions are constant” in the module documentation.

Builds a SpaceEx.KRPC.Expression.constant_int/3 expression.

Remember that, although value will accept arbitrary Elixir code, it will be turned into a constant value when the expression is built. See “Expressions are constant” in the module documentation.

Builds a SpaceEx.KRPC.Expression.power/3 expression.

Technically, the corresponding Elixir syntax would be :math.power(a, b), but that would be detected as a procedure call, so our syntax drops the module component.

Builds a SpaceEx.KRPC.Expression.modulo/3 expression.

This uses the Elixir Kernel.rem/2 syntax, instead of % or mod like other languages.

Builds a SpaceEx.KRPC.Expression.constant_string/3 expression.

Remember that, although value will accept arbitrary Elixir code, it will be turned into a constant value when the expression is built. See “Expressions are constant” in the module documentation. (This includes interpolated values within the string.)

Link to this function to_double(value)

Builds a SpaceEx.KRPC.Expression.to_double/2 expression.

Link to this function to_float(value)

Builds a SpaceEx.KRPC.Expression.to_float/2 expression.

Builds a SpaceEx.KRPC.Expression.exclusive_or/3 expression.

This uses a function call, since there’s no Elixir equivalent.

Pipe operator.

Works identically to the standard Elixir syntax, except within the context of the expression builder. Thus, int(99) |> rem(86) is the same as rem(int(99), 86), and SpaceCenter.ut(conn) |> to_int is the same as to_int(SpaceCenter.ut(conn)).

Note that kRPC calls can be pipelined together, but only the final call will be included in the expression. See “Expressions are constant” in the module documentation.

See Kernel.|>/2.