rxi / fe

A tiny, embeddable language implemented in ANSI C
MIT License
1.31k stars 82 forks source link

how to make an eval function? #11

Open philanc opened 3 years ago

philanc commented 3 years ago

Thanks for fe -- a very nice tiny interpreter.

How could I make an eval function (or macro)? i.e. such that

(= a '(+ 1 2)) (eval a) => 3

I tried using mac and fn but couldn't make it. Did I miss something obvious?

jeffpanici75 commented 3 years ago

Here's how I did it:

  1. In fe.c, at line 44, add a new primitive type P_EVAL to the enum. You can insert it anywhere before the final field, P_MAX.
  2. In fe.c, at line 50, add the string name for P_EVAL as "eval". N.B. you need to insert the string at the matching index based on the value of the enum field you just added.
  3. In fe.c, at line 634, add this code:
    case P_EVAL:
    va = checktype(ctx, evalarg(), FE_TPAIR);
    res = eval(ctx, va, env, NULL);
    break;

You now have a functional eval primitive. The following scheme code should work:

(print "(eval '(+ 100 200)) =" (eval '(+ 100 200)))

(do
    (let expr '(for x (list 1 2 3 4 5)
                       (print "x =" x)))
    (print "(eval expr) =" (eval expr)))

Keep in mind that fe doesn't support tail call optimization. This implementation of the eval primitive invokes two peer recursive calls to the C eval function.

philanc commented 3 years ago

Thanks for your solution!

Any chance the original author (rxi) would include it in 'fe'?

jeffpanici75 commented 3 years ago

I realized this well after my last post, but I sort of over fitted my implementation to your example. You can simplify the case statement I provided:

case P_EVAL:
    res = eval(ctx, EVAL_ARG(), env, NULL);
    break;

The prior version would only evaluate function application. This version will dynamically evaluate any legitimate s-expression. For example:

        (do
            (let a 42)
            (print "(eval 'a) =" (eval 'a)))

Now works, evaluating the variable. Either version works, depending on your needs. Just thought I'd mention it.

I don't want to speak for @rxi but I think the idea is that fe is so simple others can take it and mold it to their specific requirements. I've heavily customized fe for my use cases.

@rxi If you like the idea of officially adding an eval primitive to the interpreter, let me know. I'd be happy to prepare a pull request.

j0euk commented 3 months ago

@jeffpanici75 What is EVAL_ARG()? There is no mention of it at all in fe.c or fe.h.