Open Telecom Platform Command Language
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:
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.
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 structuresOTPCL 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.
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 messageLike 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 listThis 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
: SplatThis 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 insertCombination 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).