A simple and safe script language for the BEAM.
https://hex.pm/packages/abacus
This language was written to be so simple anyone that has seen mathematical terms before can write Abacus code.
For example, adding a
and b
is as simple as a + b
.
It was also designed to be as safe as possible, allowing you to expose the compiler to a web frontend.
A lot of features make this language easier to use and less confusing than regular Elixir (to laymen) with a whitelist:
- All common math operators are available as binary operators
- No string/atom confusion when
- accessing maps or keyword lists
- comparing 2 variables
- Since it is using the Elixir AST, a lot of the advantages apply:
- Functional
- Immutable
- Everything is an expression (even if statements)
- Last expression returns
Several measures have been or can be taken to allow safe execution of untrusted code.
- Scripts don't have access to Erlang/Elixir modules and their functions
- For variables, atoms are reused across different scripts
- Variables will be renamed into
:var0
,var1
, ...,varn
where n is the amount of distinct variable names in a script - Variable amounts can be limited so bad actors can't DoS your service by creating scripts with more variables than the VM atom limit
- Variables will be renamed into
- You can move execution off to a dedicated process to
- Prevent endless loops (set a timeout)
- Prevent heavy memory usage (set
max_heap_size: %{size: 1_000_000, kill: true}
in process options)
- If you use a worker pool you can even prevent DoS attacks that spawn thousands of parallel executions in the allowed timeout (note: phoenix already uses a worker pool so you don't have to spawn your own if you execute abacus expression as a response to a HTTP call)
While one-off executions can be evaluated using interpretation (through Code.eval_quoted
), the real power comes from the Abacus parser returning valid Elixir AST.
This means that the AST can be compiled to BEAM bytecode for performant execution. The performance should be the same as normal Elixir code.
- Write complex filters
- As a language for map/reduce operations of all kinds
- For user-supplied export scripts
- Language for rule-engines (if this then that, authorization rules)
Abacus.eval(expression, scope)
Parses and evaluates the given expression. Variables can be supplied as a map of
binaries in the scope
.
iex> Abacus.eval("1 + 1")
{:ok, 2}
iex> Abacus.eval("sin(floor(3 + 2.5)) / 3")
{:ok, -0.3196414248877128}
iex> Abacus.eval("a * b", %{"a" => 10, "b" => 10})
{:ok, 100}
Abacus.parse(expression)
Parses the expression and returns the syntax tree.
The syntax tree is in quoted elixir. This means that you can use this to compile it into BEAM bytecode for performant execution.
An incomplete list of supported and planned features
- basic operators (
+
,-
,*
,/
,^
) - factorial (
!
) - bitwise operators (
|
,&
,>>
,<<
,~
,|^
(xor)) - boolean operators (
&&
,||
,not
) - comparison operators (
==
,!=
,>
,<
,>=
,<=
) - grouping terms with parantheses
- ternary if operator (
condition ? if_true : if_false
) - different data types:
- integers
- floats
- strings
- boolean (
true
andfalse
) - nulls (
nil
)
- support for functions
- trigonometric functions(
sin
,cos
,tan
) - floating point manipulation(
ceil
,floor
,round
)
- trigonometric functions(
- lambdas (
(x, y) => {x + y}
) - do blocks (
{statement \n statement}
) - variable assignments
- enumerable API (
map
,reduce
,filter
, ...) - [-] literals
- integers
- floats
- strings
- lists
- maps
- support for variables
- Add
abacus
to your list of dependencies inmix.exs
:
def deps do
[{:abacus, "~> 0.4.2"}]
end
- Ensure
abacus
is started before your application:
def application do
[applications: [:abacus]]
end