If input is from untrusted users, then follow this guide.
Use Code.string_to_quoted/2
to parse code. After that, you’d have to traverse an AST and ensure there are only allowed expressions. If everything is ok, then you can evaluate this AST using Code.eval_quoted/1
.
Here is an example:
> ast_from_input = fn input -> ~s|"| <> String.replace(input, ~s|"|, ~s|\\"|) <> ~s|"| |> Code.string_to_quoted() end
#Function<7.126501267/1 in :erl_eval.expr/5>
> ~S|Today’s date is "#{Date.utc_today}"| |> ast_from_input.()
{:ok,
{:<<>>, [line: 1],
[
"Today’s date is \"",
{:"::", [line: 1],
[
{{:., [line: 1], [Kernel, :to_string]}, [line: 1],
[
{{:., [line: 1], [{:__aliases__, [line: 1], [:Date]}, :utc_today]},
[no_parens: true, line: 1], []}
]},
{:binary, [line: 1], nil}
]},
"\""
]}}
in case of syntax error, a nice error is returned:
> ~S|Today’s date is "#{Date.utc_today/}"| |> ast_from_input.()
{:error, {[line: 1, column: 36], "syntax error before: ", ""}}
and finally, evaluated string:
> ~S|Today’s date is "#{Date.utc_today}"| |> ast_from_input.() |> elem(1) |> Code.eval_quoted()
{"Today’s date is \"2021-06-09\"", []}
The only thing that is left here is to implement AST validator. Macro.traverse/4
can help with this task.