A Command Line Interpreter in Elm

Hi All, I’d like to report on building and using command-line interpreter (CLI) that talks to Elm code. The bottom line is that is that it is quite easy to do using Platform.Worker. One writes a headless Elm
program that communicates via ports with a 17-line Javascript program cli.js. The javascript program uses the Node repl module to provide a convenient user interface. To run the CLI, just say node cli.js. The code is on GitHub.

(This post expands on an earlier article on the subject Platform.Worker.)

A Skeleton App

What I will describe here is a skeleton CLI that I derived from something I am using to do real work on another project. It has a simple structure that can be easily bent to other ends. Below is the help screen, which says pretty much all there is to say about how to use the program.

And here is a short session:

> add 2 3   --  the result is stored register M
5
> mul 4     --  only one operand given, so we use the contents of M for the other
20
> pow 1.1 10 -- exponential growth -- beware!
2.5937
> sto a      --  store the contents of register M in register A
M > A
> r a        -- read the contents of A
A: 2.5937
> log 10 23  -- compute the base-10 logarithm of 23
1.3617
> rcl a      --  recall the contents of A, store in M
A > M
> r m.       -- yes, it is there
M: 2.5937
ETC.

RPN Version (Note added)

There is now a stack-based version of the calculator. This screenshot explains it:

The Github Repo now has three branches: master, original, and stack, so you can look at the code that interests you. Generally speaking, master = stack.

Code

The organization of the code is pretty well described in the README so I won’t repeat it here. Better just to look at it if you are interested. Here is a birds-eye-view:

-------------------------------------------------------------------------------
File                             blank        comment           code
-------------------------------------------------------------------------------
src/Command.elm                     99             10            212
src/Main.elm                        27              3             71
src/Model.elm                        8              0             24
src/ArgList.elm                     14              1             19
src/cli.js                           6              3             17
-------------------------------------------------------------------------------
SUM:                               154             17            343
-------------------------------------------------------------------------------

To customize things, you will need to do two things. First is to
adapt the dispatcher function in Main.elm:

executeCommand : Model -> String -> ArgList -> String -> ( Model, Cmd Msg )
executeCommand model cmd args input =
    case cmd of
        "add" ->
            Command.op model (+) args

        "mul" ->
            Command.op model (*) args

        ...

        "exp" ->
            Command.f1 model (\x -> e ^ x) args

        "ln" ->
            Command.f1 model (logBase e) args

        "pow" ->
            Command.f2 model (\a b -> a ^ b) args

      ...

The second is to implement code in Command.elm that carries out your commands. This is the biggest file in project and is mostly specific to the particular functions of the program.

Addendum

The skeleton app described above is a distillation of work on another project. The CLI can read source text in some mythical markup language like this:

$ run cli
> .load source/t1    -- load source into register M
>  d                 -- display it

Now parse it and display a representation of the parse tree:

parsetree

A node in the tree takes up a single line, with the symbol • standing for newlines. The depth of a node in the parse tree is indicated both by the displayed integer and by indentation,

8 Likes

how is it different from elm repl? im a bit confused as to what this even does

Hi, the Elm repl gives you a way of running small bits of Elm code without a web browser. A command-line interpreter (CLI) as in the example in this article doesn’t run elm code directly, and what you type into it is not Elm code. For example, sto a means store the contents of register M in register A. The phrase sto a is not part of the Elm language, even though it causes Elm code to do something.

3 Likes

Thanks a lot for taking the time out to extract code from your real world project, and sharing it with us.

The knowledge gained from looking at the source code is invaluable.

Most of the time I keep wondering how others would model a problem domain that I am struggling with. And what alternatives would look like.

Please do share any content that has been practically useful. Your efforts are really appreciated.

2 Likes

I’ve added a stack-based RPN version of the calculator in the stack branch of the repo. The register-based version is the original branch:

Here is the help screen:

1 Like

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.