numbat icon indicating copy to clipboard operation
numbat copied to clipboard

Add CLI option to output computation as structured format

Open aurelleb opened this issue 2 months ago • 11 comments

Hi there, I'm looking to make numbat available as a calculator backend for https://github.com/vicinaehq/vicinae and I'm hitting a roadblock trying to integrate it.

Since I'm dealing with a C++ codebase I can't use the library directly, so I thought I could just invoke numbat through its CLI every time a computation is required.

I also considered using FFI but honestly that sounds like a lot of work only to integrate a calculator backend.

The problem is, the output is not meant to be programatically parsed, making this a very hard to implement solution. Errors in particular, cannot be easily parsed at all.

So my suggestion would be to implement a flag that would output the result as a structured format, typically JSON, something like this:

numbat -e '10km to m' --color=never --json

with as an output:

{
  "value": "10000",
  "unit": "m"
}

or

{
  "error": "Incompatible units"
}

The output could be a bit more detailed, I don't know.

I think this would make integrating numbat with launchers or even scripts significantly easier.

If you think this has its place upstream then I'm willing to make a PR for this.


On a side note, it would also be great if we could override specific config options when using the CLI with the -e flag.

In order to make CLI usage a viable option for these kind of use cases we would probably need to add a new exchange rate loading mode that caches it on disk instead of fetching them anew every invocation. But I diverge, that should probably be its own issue.

aurelleb avatar Oct 12 '25 09:10 aurelleb

I think also exposing the "expanded expression" would be nice, as it helps the user to be sure numbat is understanding its intention. For the same query as op, something like:

{
   "expanded" : "10 kilometre ➞ metre",
   "value": "1000",
   "unit": "m",
  "dimension": "Length"
}

leiserfg avatar Oct 12 '25 09:10 leiserfg

I think this would be a good addition to numbat. Feel free to open a PR for it.

These two issues seem partially related (although parsing focused): #470 and #262
The discussions there may provide some inspiration.

Goju-Ryu avatar Oct 12 '25 10:10 Goju-Ryu

@aurelien-brabant for vicinae wouldn't it work as well if instead of running the cli every time, a session of numbat was kept running and it would be possible to talk to it with a JSON-RPC over stdio? Given that numbat has a repl, that does not seem very difficult to implement (in this side). That should lead to quicker responses and maybe even remove the need for caching the exchange rates.

leiserfg avatar Oct 12 '25 12:10 leiserfg

that's another option but having to implement a json-rpc interface is more complex, not sure numbat maintainers want that?

aurelleb avatar Oct 12 '25 12:10 aurelleb

You could probably start experimenting with this by sending something like the following to the numbat CLI, instead of just the user-expression (10km to m):

let user_expr = # actual user input here

let out_value = value_of(user_expr)
let out_unit = unit_name(user_expr)

print("{{\"value\": \"{out_value}\", \"unit\": \"{out_unit}\"}}")

(try here)

I think also exposing the "expanded expression" would be nice, as it helps the user to be sure numbat is understanding its intention. For the same query as op, something like:

{ "expanded" : "10 kilometre ➞ metre", "value": "1000", "unit": "m", "dimension": "Length" }

I agree, this would certainly be more valuable. I'm not opposed to adding a JSON output option, but it would certainly need some design work first. For example: What would this mode do for more complex (non-single-expression) Numbat programs? What happens if there are print statements in the Numbat program?

sharkdp avatar Oct 12 '25 16:10 sharkdp

@sharkdp I think it should not print and return only the json result for the last expression.

leiserfg avatar Oct 13 '25 20:10 leiserfg

@sharkdp I think it should not print and return only the json result for the last expression.

I think returning a list of results may be a good idea. In cases where you give a script with multiple outputs, it could be valuable to be able to get all of the results. That way it is also possible to get no outputs if a script for example consists only of print statements (assuming we went with their output not being included).

Goju-Ryu avatar Oct 14 '25 15:10 Goju-Ryu

Guys any output about doing JSON-RPC over stdio? I'm thinking of one with two methods: pseudo-code

eval("string") -> vec<Output> reloadCurrencies() -> bool

where Output has a kind to identify it and can be either an error, an expression result and a procedure call (like for print).

leiserfg avatar Nov 04 '25 17:11 leiserfg

I just noticed @irevoire is working in a lsp, that might do it 🤞

leiserfg avatar Nov 04 '25 19:11 leiserfg

Huuum I don't think so, the lsp is not interpreting your code and doesn't load the currency at all 😬

irevoire avatar Nov 05 '25 10:11 irevoire

But it could, it could have a workspace/executeCommand to load currency and a codeLens to get the evaluation of a chunk, for instance.

leiserfg avatar Nov 05 '25 12:11 leiserfg