My First View on FuncScript CLI
What is FuncScript?
FuncScript is a superset of JSON that lets you promote property values into expressions. Instead of static literals, { x: 1 + 2; } is perfectly legal. It keeps JSON's predictability while gaining a concise expression language. The fs-cli is a lightweight Node.js command line interface that wraps the FuncScript runtime. There's also FuncDraw, a UI tool built using FuncScript.
This article documents my first experience with fs-cli. Note that these observations were made on a specific version of the tool and may not apply to future updates. Think of this as field notes from a curious explorer.
What is the lesson I took
Well most of the issues seem to have a workaround: using "" is the preferred way of doing FuncScript. Instead of:
npx fs-cli 3 + 'cool'
I will do:
npx fs-cli "3 + 'cool'"
And also:
npx fs-cli "'cool' + 4"
I should not be confused with things in quotes
When we come to most programming languages, if not all, things in "" means they are string literals. But here fs-cli expects one argument and if we have more than that, either that is going to be rejected or fs-cli panics.
So if we have npx fs-cli "4 + 3", the "4 + 3" is not a string. It is the expression 4 + 3. Here " " is being used to define the expression. It is like entering in Python REPL1 and executing 4 + 4:
python3
Python 3.13.7 (main, Aug 14 2025, 11:12:11) [Clang 17.0.0 (clang-1700.0.13.3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> 4 + 3
7
>>>
As for other languages like Python, string concatenation works naturally:
>>> "Hello" + "Hello"
'HelloHello'
>>>
We can do this in fs-cli but a little bit differently. Instead of separately treating the operands, we quote everything in "":
npx fs-cli "'Hello' + 'Hello'"
Type: String
Value:
"HelloHello"
The reason we are not doing something like Python is because in fs-cli we are talking with the shell, not only with fs-cli. In Python, in the above example, we are in Python’s world where everything is in Python’s environment.
If we try to do what we are doing in Python, we fail:
python3 "3 + 3"
/opt/homebrew/Cellar/python@3.13/3.13.7/Frameworks/Python.framework/Versions/3.13/Resources/Python.app/Contents/MacOS/Python: can't open file '/Users/esubalew/Downloads/testing/3 + 3': [Errno 2] No such file or directory
What happened is that fs-cli expects expression mostly in "" or numbers, or some variable. Python expects a file name, so "3 + 3" was not a valid file name on my machine, and it failed.
Fs-cli is now more like run2 but not entirely. For example:
run "print(4 + 34)"
38
Here run expects file path (like Python args) or code (much like fs-cli). If we do run 4 + 4 or even "3 + 3" that is wrong because these are neither correct file path nor correct code.
Actually run is also REPL which means it can maintain its own environment:
run py
run universal REPL. Type :help for commands.
python>>> print((lambda x: [i*i for i in x])([1,2,3,4,5]))
[1, 4, 9, 16, 25]
python>>>
Fs-cli is a one argument tool
fs-cli expects one argument. If we don’t respect that, results are going to be unexpected. As I mentioned in the section above, it is recommended that we use quotes when passing that argument.
It first looks like we can ignore quotes if we are passing a valid data type, and this works fine for most of the types. The snippets below show that Integer, Dictionary (KeyValueCollection), Float, and Boolean work fine without using quotes. This means npx fs-cli 3 is the same as npx fs-cli "3":
npx fs-cli 3
Type: Integer
Value:
3
npx fs-cli 3.3
Type: Float
Value:
3.3
npx fs-cli true
Type: Boolean
Value:
true
npx fs-cli "{}"
Type: KeyValueCollection
Value:
{}
npx fs-cli {}
Type: KeyValueCollection
Value:
{}
But the issue I discovered is this breaks sometimes or on some Types:
npx fs-cli []
zsh: no matches found: []
Wrapping this in quotes solved the issue:
npx fs-cli "[]"
Type: List
Value:
[]
Data type String is wronged (ተበድሏል)
Because of the fact that things are naturally expected to be defined in "", strings are hard to appear.
npx fs-cli "3" is interpreted as Integer and npx fs-cli "true" is interpreted as a Boolean and npx fs-cli "trued" as Null Type.
But fortunately this is more like a joke than a real issue. If we want to get a string, we need to define a string like "true". We can do that by defining it like npx fs-cli "'true'" or we can escape the character we want to use with double quotes. This way we can also make numbers into strings:
npx fs-cli "'38934'"
Type: String
Value:
"38934"
I can even do:
npx fs-cli "{ eval f''; }"
Type: String
Value:
"{ \"name\":[\"Abebe\", \"Alemu\"] }"
But the output is a bit scary. The escape character comes in the output.
Numbers are limited so take care
If I try an extremely large number it will break or maybe panic. This means if we are not careful, mathematical operations may lead to the FuncScript limit and cause hidden bugs.
It is true that unlike the reality of the world which says numbers are unlimited (since the term infinity), they are limited when it comes to the programming world. We have two types of limits about how large the number we can write in a computer: physical limit and implementation limit. The former is the limit of our machine’s RAM and the latter is the limit set by the implementation of the underlying programming languages we use.
Programming languages like JavaScript have this limit. In Python, on the other hand, types like BigInteger are said to have “unlimited” numbers we can write - but that doesn’t really mean we should write extremely large ones, as arithmetic might become slow and something wrong might happen.
So this section is not really about fs-cli specifically. FuncScript itself shouldn’t have these limits as it’s a limit on the underlying runtime. But I’m afraid it hits the limit earlier than it should.
run py "print(1000000000000000000 * 1000)"
1000000000000000000000
As we can see, Python is happy with this. It’s a very large number but Python handles it fine. Let’s compare:
npx fs-cli "1000000000000000000 * 1000"
Type: BigInteger
Value:
"1000000000000000000000"
npx fs-cli "1000000000000000000 \* 1000000"
Type: BigInteger
Value:
"1000000000000000000000000"
npx fs-cli "1000000000000000000 \* 1000000000000"
Type: BigInteger
Value:
"1000000000000000000000000000000"
So far so good. But then:
npx fs-cli "1000000000000000000 * 10000000000000000000000000"
Failed to parse expression
run py "print(1000000000000000000 \* 1000000000000000000000000000000)"
1000000000000000000000000000000000000000000000000
Python handles it fine while fs-cli fails. This seems normal to be honest because we’re at least computing as much as we really do in real life. But compared to other languages, it hits the limit too early.
Range limits
The highest limit of using Range is around Range(0, 10000000). To be more specific, it’s Range(0, 10000000*5):
npx fs-cli 'Range(0, 10000000)'
[works fine]
npx fs-cli 'Range(0, 100000000)'
[memory allocation fails]
If we add a single zero, it will break and memory allocation fails. That means Range(0, 10000000*6) will error out.
Memory allocation limits may seem machine-specific, but it’s not the case here. It seems more like FuncScript is struggling to handle this better.
Number literal limits
When it comes to just calling a number, this is the nearest highest limit:
npx fs-cli "1000000000000000000"
Type: BigInteger
Value:
"1000000000000000000"
npx fs-cli "10000000000000000000"
Failed to parse expression
The error message is also misleading to be honest. “Failed to parse expression” doesn’t tell you that you’ve hit a number limit.
1 A read–eval–print loop (REPL), also termed an interactive toplevel or language shell, is a simple interactive computer programming environment that takes single user inputs, executes them, and returns the result to the user. Read–eval–print loop
2 run is a universal multi-language runner and smart REPL written in Rust that lets you execute code in 25+ languages from the command line. run