OTPCL

Open Telecom Platform Command Language

OTPCL Version 0.2.0: Pipes and Loops and Docs, Oh My!

These release notes got a bit delayed, but future release notes will either coincide with or immediately precede releases on Hex.

This release ended up being one giant commit that introduced two pretty big things:

  1. A major refactoring of the OTPCL module structure (such that most of OTPCL’s Erlang modules are importable as OTPCL modules)
  2. An expansion of OTPCL’s standard library in accordance with the above refactoring
  3. Pipe commands

Refactoring

Not a whole lot exciting here, other than some modules being renamed, others being rewritten, and a bunch of functions being moved around. Not that there were any docs in the first place, so it ain’t like there was much of a reference point, but on that note…

Docs! They actually exist now. Quite a few functions have typespecs, but I gave up midway and am probably going to rework how I’m doing them (right now the types themselves are all defined in otpcl.hrl, which is kinda sloppy; I should move them into - and export them from - various modules). Nearly every public function, however, does have at least some degree of documentation.

Enhancements

OTPCL’s various modules (or at least the ones intended to be used/imported by OTPCL scripts/programs), in addition to having been majorly refactored, now also include quite a bit more functionality. The whirlwhind tour:

otpcl_control: OTPCL’s control structures

OTPCL now has more control structures than just if:

truthy

I lied; this ain’t a control structure. It does, however, underpin most of the control structures. I could write out a description, but the Erlang code is pretty self-explanatory:

truthy([]) ->
    false;
truthy(false) ->
    false;
truthy(error) ->
    false;
truthy(0) ->
    false;
truthy(0.0) ->
    false;
truthy(<<>>) ->
    false;
truthy({}) ->
    false;
truthy(T) when is_tuple(T) andalso element(1, T) =:= error ->
    false;  % in English: any tuple where the first element is 'error'
truthy(_) ->
    true.

if $predicate $then $else / unless $predicate $then

Your standard if/then/else statement. If $predicate is true, then it’ll evaluate the string in $then as an OTPCL script (with the same context, so it’ll know about tand be able to alter hings in the outer scope), or else (if $predicate is false) it’ll evaluate the string in $else. Quick example:

use io

if true {
    io format "This will always print out~n"
} else {
    io format "This will never print out~n"
}

unless is effectively the same as if, but reversed. There’s also no $else for unless, since “unless/else” doesn’t really make much sense; if you love Perl (like I do) and really want that for some reason, then here’s a two-liner you’re welcome to stick in your own OTPCL-aware module:

unless([Test, Then], State) -> 'if'([Test, "", Then], State);
unless([Test, Then, Else], State) -> 'if'([Test, Else, Then], State).

for $var-name in $list $action

Pretty self-explanatory. For each member of the list (TODO: expand this to other list-like things somehow), assign it to the variable named in $var-name, then evaluate $action as an OTPCL script (in the same context as the outer scope). For example:

use io
set senses ("one" "two" "three" "four" "five")
for sense in $senses { io format $sense; io format " " }
io format "senses working overtime~n"

while $predicate $action

Also pretty self-explanatory. Unlike with if, $predicate here is a string which while will repeatedly evaluate; as long as that OTPCL script evaluates to a “truthy” value, while will keep on evaluating $action over and over again.

otpcl_core

This module defines the two commands that are generally necessary for OTPCL’s interpreter to function reasonably-correctly. return just returns its argument(s) literally (if there are multiple arguments, it’ll return them as a list; I might change this to a tuple if that’s more intuitive). We’ll get to | shortly.

otpcl_eval

OTPCL’s interpreter got a slight refactor to be able to be imported/used within an OTPCL script (previously, there was a separate wrapper function to make it behave like an OTPCL command).

otpcl_meta

This is one of the two “dangerous” modules currently in OTPCL (i.e. modules that allow manipulating things outside the confines of the OTPCL interpreter state), and specifically have to do with getting/setting both variables and commands. Some highlights:

import v. use

There’s now a use command in addition to import. import will (still) register each exported function (or each command named in the module’s -otpcl_cmds attribute, if that’s set) directly into the interpreter’s global namespace. use, on the other hand, will instead define a new command which treats its first argument as a subcommand, that subcommand corresponding to the exported function/command name. So, to illustrate:

use io  # This creates an 'io' command, like we've seen above
io format "Erlang's io:format is now a subcommand"

import io  # This creates one command for each exported function in 'io'
format "Erlang's io:format is now its own command"

subcmd

This underpins the use command, and is available should you want to use it to make your own subcommands (though you can easily do this via pattern-matching / destructuring, too; subcmd is good for dynamic generation of subcommands, while pattern matching is probably the better choice if you have a fixed list of subcommands that need implemented).

cmd

Replaces def in prior versions, but otherwise behaves similarly. Now it accepts an Erlang function instead of a list of argument/body clause pairs (there’s temporarily no way to create an Erlang function from within OTPCL right now, but I’ll add that back in pretty soon; alternately, you can surely use Erlang’s own modules to that effect and do it that way). If called with just a command name (e.g. cmd foo), it’ll return the Erlang function backing that command (including the generated one for commands defined entirely within OTPCL).

get / set

No change here, other than them getting moved into otpcl_meta.

otpcl_pipes

We’ll get to this in a sec.

otpcl_shell

The other “dangerous” OTPCL module; it specifically provides a bunch of more-or-less self-explanatory commands to make the OTPCL REPL (bin/otpcl in the source tree) halfway-usable as a system shell.

Pipes

OTPCL’s new pipe commands should be very familiar to Unix afficionados and Elixir users (alchemists?) alike. The basic pipe command (|), in fact, behaves identically to Elixir’s own pipe operator (|>) in the sense that it takes the return value from the previous command and inserts it as the first argument of the next command (that is: foo | bar baz is equivalent to bar [foo] baz).

There are bunch of other cool pipe commands in the otpcl_pipes module, though (with many more to come, surely). The current lineup:

|! $pid: Send a message

Like Erlang’s ! operator, but backwards; it’ll take the output of the previous command and send it to the process identified by $pid (i.e. your standard Erlang PID tuple or what have you).

|# $pos foo bar baz: Insert into the argument list

This is a generalized version of |, and definitionally more powerful than Elixir’s own pipe operator: instead of being limited to the first argument, this will let you insert into any argument position (indicated by $pos).

|* foo bar baz: Splat

This will insert the previous command’s output (assuming it returned a list) as additional arguments at the beginning of the next command’s argument list (i.e. return (1 2 3 4 5) |* foo bar baz is equivalent to foo 1 2 3 4 5 bar baz). If there’s no command afterward on that line, it’ll instead run the previous command’s output as a command (so return (foo bar baz) |* would be equivalent to just running foo bar baz).

|#* $pos foo bar baz: Splat and arbitrarily insert

Combination of |* and |#, allowing you to expand the output of the previous command anywhere into the next command.

|| and |&

These behave like the || and && operators in your typical shell language, conditionally evaluating the subsequent command if the preceding command returned a “falsey” or “truthy” value (respectively).