rxi / aria

A tiny, embeddable lisp-shaped language implemented in C89
MIT License
167 stars 18 forks source link

f_exit kills the host app instead of returning control. #2

Closed jason-c-daniels closed 7 years ago

jason-c-daniels commented 7 years ago

Problem: Calling (exit) in the aria code kills the host application. This happens because (exit) calls the c-language exit function.

Proposed Solution

  1. Adding an int in_shutdown flag and int exit_code member to ar_State will allow the hosting application to examine the result. (For more complex results consider using ar_Value* for the result code instead.)
  2. To do this you'll need to alter any loops that could execute infinitely as the result of an aria-script.
  3. I can make the fix for you if you would like. (I've got 4 alterations for my needs going right now. I'll separate them out if you agree.)
rxi commented 7 years ago

This is the intended behaviour, for simplicity's sake (one of the project's goal is that of minimalism and simplicity) I'm not keen for any additional complexity to be added to the function.

If you want to exit immediately out of aria and back to the caller you can use the error() function to raise an error, the value of which will be propagated up the stack -- any value type can be used as an error, allowing you to choose something easily-identifiable for this purpose. The error value can then be handled using the ar_try() macro in C. If you use pcall anywhere in the code it will also have to propagate this value in its error handler.

Additionally you can overwrite aria's exit function to inherit whatever behaviour you want -- for example, calling a clean-up callback then calling C's exit.

jason-c-daniels commented 7 years ago

Perhaps I'm not following you all that well. I just tried using (error "bleh") in the standalone/interactive app provided with aria.c. It didn't exit the main loop.

So let me rephrase this, what I'm looking for is a way for the aria subsystem to terminate operation and to provide some result code...etc. to the host application, even from within an infinite loop in the script. The host app may or may not continue running after the aria subsystem terminates.

Without changing aria, is this currently possible? (i.e. is there something I can type, apart from (exit) in the interactive mode, which will cause it to terminate?)

rxi commented 7 years ago
(let (exit-val (cons))

  (= exit2 (fn (val)
    (setcar exit-val t)
    (setcdr exit-val val)
    (error exit-val)))

  (while (is nil (car exit-val))
    (pcall
      (fn () (print (eval (parse (readline)) global)))
      (fn (err tr)
        (if (is nil (is err exit-val)) (do
          (print \"error:" err)
          (print "traceback:")
          (while tr
            (print (string "  [" (dbgloc (car tr)) "] "
                           (substr (string (car tr)) 0 50)))
            (= tr (cdr tr))))))))

  (print "exiting..." (cdr exit-val)))

Here's an example of what I meant -- calling (exit2 [val]) sets the pair exit-val's car to t and sets its cdr to val (which you can use for whatever purpose you want). It then raises an error which causes the program to jump to the error handler, which, seeing the exit-val was raised, doesn't print the error/traceback. while then exits its loop because exit-val's car is no-longer nil.

jason-c-daniels commented 7 years ago

I gotcha now. The check has to be built into each loop scripted in aria. Therefore, not a true infinite loop that's being interrupted. BUT, this should be an acceptable workaround.

rxi commented 7 years ago

The check has to be built into each loop scripted in aria

exit-val only has to be propagated in every error handler -- in this case the error handler exists in that loop and sets its outer loop to exit, so the loop can continue to handle regular errors as it normally would. For example, typing:

(while 1 (print "hello") (exit2 "xyz"))

Would exit as expected