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:
truthyI 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 $thenYour 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 $actionPretty 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 $actionAlso 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_coreThis 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_evalOTPCL’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_metaThis 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. useThere’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"
subcmdThis 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).
cmdReplaces 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 / setNo change here, other than them getting moved into otpcl_meta.
otpcl_pipesWe’ll get to this in a sec.
otpcl_shellThe 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).