erlanglisplfe

Value from binding in LFE interpreter using Erlang


I'd like to use Lisp Flavored Erlang as a scripting extension language for an Erlang application. If you want, in a similar way GNU Emacs is configured and extended via Emacs Lisp.

I know that the argument is wide and structured; but in the specific case of this question I'd like being able to read a binding name (or variable, if you prefer) defined in LFE from Erlang code.

I'm not an expert of LFE internal architecture (which is an excellent example of software engineering and Erlang programming), but I was not able to find an answer neither in the sources nor in the documentation. Looking at sources I can see that LFE contains both a compiler that target Erlang VM and an interpreter. The latter is the one I'm trying to use.

If I start Erlang shell/REPL in LFE installation path (on my system $HOME/opt/lfe):


$ cd /path/to/LFE-install-dir
$ erl -pa ./ebin

I'm able to calculate a value:


1> {ok, Expr} = lfe_io:read_string("(+ 1 10)").
{ok,['+',1,10]}
2> Result = lfe_eval:expr(Expr).
11

This is a first step, but not exactly what I want. I'd like rather to bind a variable and read its value; that's my issue:


3> {ok, Expr2} = lfe_io:read_string("(set a 10)").
{ok,[set,a,10]}
4> lfe_eval:expr(Expr2).
** exception error: {unbound_func,{set,2}}
     in function  lfe_eval:eval_expr/2

Why set is recognized as an unbound function? In LFE REPL this expression is valid:

Erlang/OTP 17 [erts-6.4] [source] [64-bit] [smp:4:4] ...
LFE Shell V6.4 (abort with ^G)
> (set a 10)
10
> a
10

I'm obviously using the API in the wrong way. How can I read the content of a and/or properly initialize the LFE interpreter?

(If this is explained somewhere, please provide the reference).


Solution

  • I won't attempt to answer completely your broader question about the "best practices" of adding scripting. It seems to me that choosing between "hook-based" solution (in which you define hook implementations by name convention and they are automatically recognized) and "explicit api" solution (in which you use functions predefinied in the scripting enviroment to register your hooks or otherwise call configuration functions) is largely a matter of taste. Explicit calls like (set-connection-timeout-handler ...) may be more readable, easier to debug (no misspelling problems, no surprises on api changes), easier to document, and a bit more flexible, but more, well, explicit.

    Building from your simple variable definition example, here are a few ways you could get started going further the "interpreted" path:

    1> {ok, Expr} = lfe_io:read_string("'((a 10))").
    {ok,[quote,[[a,10]]]}
    2> lfe_eval:expr (Expr).
    [[a,10]]
    
    3> EvalAll = fun (Conf) -> {ok, E} = lfe_io:read_string("'(" ++ Conf ++ ")"), lfe_eval:expr(E) end.
    #Fun<erl_eval.6.90072148>
    4> EvalAll ("(a 10) (b 11)").                                                                      
    [[a,10],[b,11]]
    
    5> EvalAllL = fun (Conf) -> {ok, E} = lfe_io:read_string("(list " ++ Conf ++ ")"), lfe_eval:expr(E) end.
    #Fun<erl_eval.6.90072148>
    6> [{f, F}] = EvalAllL ("(tuple 'f (lambda (x) (+ 10 x)))").
    [{f,#Fun<lfe_eval.12.2018457>}]
    7> F (12).
    22
    
    8> G = fun (X) -> X * 2 end.
    #Fun<erl_eval.6.90072148>
    9> lfe_eval:expr (element (2, lfe_io:read_string ("(g 15)")), lfe_eval:add_lexical_func(g, 1, G, lfe_env:new ())).
    30