Error handling in uLisp


#1

@johnsondavies, hi, take a look at this commit where I implemented unwind-protect, ignore-errors, and errors. Because uLisp doesn’t have multiple return values, I made ignore-errors return nothing when an error occurs within.

Also it’d be great if the special forms would signal error in all cases of malformed forms instead of failing/accessing invalid memory:

(dotimes ())
((lambda))
(let)
(let*)
(with-output-to-string)

and so on.

Perhaps, I could look into that if I find the time for that; however, contributing to this project isn’t easy, especially as you keep separate code bases for different families of boards. I understand the motivation of single ino files, well, I’ll think about this and then share my thoughts about what could be done for easier contributing and keeping different ino files in sync.


#2

Ok, I can see that mere restoration of GCStack isn’t enough and when I do:

(unwind-protect (/ 0) (format t "Blah"))

it leaks memory. Can you tell me what I’ve missed out? Probably something to do with marking.


#3

@Goheeca thanks for pointing out the problem with malformed special forms. I’ll fix that in the next release.

I’d welcome your suggestions about contributing to the project. I generate the code bases for each family from a multi-family code base, via a Lisp preprocessor program. So one option is for you to contribute to one of the families; I can then merge that into the multi-family code base so it gets applied to all families.


#4

I’ll take a look at unwind-protect.


#5

I’ve fixed it, I forgot to reset GCStack to NULL before the next longjmp, well, there were more problems and it worked deceivingly well, but now it should be fine. Feel free to incorporate it into uLisp.


#6

Thanks! I look forward to trying it out.


#7

Hi Goheeca,

That is a very welcome improvement! Thank you very much. I merged it into my repository of ulisp-esp-m5stack and it improved my little m5stack-based uLisp stand-alone computer (see http://blog.matroid.org/display/88) a lot. It is much easier to learn Lisp when errors do not freeze everything. :) Also, It will help a lot with my networking code (see http://blog.matroid.org/display/86).

Can I add your commits to my github repository m-g-r/ulisp-esp-m5stack?

To circumvent the limitation of the missing multiple-values that you mentioned with regard to ignore-errors, I have added a GlobalErrorString to hold the last error message and a function get-error to retrieve it. I consider this to be a workaround but it is good enough to show error messages in the little REPL of the Lisp handheld.

I’ll push that patch also to my repository when I can add your commits.

Cheers, Max

PS: I try to add the patch here as well for convenience and hope that it is copy-able.

diff --git a/ulisp-esp.ino b/ulisp-esp.ino
index 4174067..20e028f 100644
--- a/ulisp-esp.ino
+++ b/ulisp-esp.ino
@@ -234,6 +234,7 @@ K_INPUT, K_INPUT_PULLUP, K_OUTPUT,
 K_INPUT, K_INPUT_PULLUP, K_INPUT_PULLDOWN, K_OUTPUT,
 #endif
 USERFUNCTIONS,
+GETERROR,
 // functions of m-g-r/ulisp-esp-m5stack - begin
 MUTESPEAKER, SETUPBACKLIGHTPWM,
 #if defined(enable_ntptime)
@@ -272,6 +273,7 @@ unsigned int TraceDepth[TRACEMAX];
 object *GlobalEnv;
 object *GCStack = NULL;
 object *GlobalString;
+object *GlobalErrorString;
 int GlobalStringIndex = 0;
 uint8_t PrintCount = 0;
 uint8_t BreakLevel = 0;
@@ -300,32 +302,56 @@ object *edit (object *fun);
 
 // Error handling
 
-void errorsub (symbol_t fname, PGM_P string) {
-  pfl(pserial); pfstring(PSTR("Error: "), pserial);
+object *errorsub (symbol_t fname, PGM_P string) {
+  if (!tstflag(MUFFLEERRORS)) {
+    pfl(pserial); pfstring(PSTR("Error: "), pserial);
+    if (fname) {
+      pserial('\'');
+      pstring(symbolname(fname), pserial);
+      pserial('\''); pserial(' ');
+    }
+    pfstring(string, pserial);
+  }
+
+  // store error message in string object for GET-ERROR
+  object *obj = startstring(SP_ERROR);
   if (fname) {
-    pserial('\'');
-    pstring(symbolname(fname), pserial);
-    pserial('\''); pserial(' ');
+    pstr('\'');
+    pstring(symbolname(fname), pstr);
+    pstr('\''); pstr(' ');
   }
-  pfstring(string, pserial);
+  pfstring(string, pstr);  // copy to globalstring
+
+  return obj;
 }
 
 void error (symbol_t fname, PGM_P string, object *symbol) {
+  object *obj = errorsub(fname, string);
   if (!tstflag(MUFFLEERRORS)) {
-    errorsub(fname, string);
     pserial(':'); pserial(' ');
     printobject(symbol, pserial);
     pln(pserial);
   }
+
+  // add symbol to string object for GET-ERROR
+  pstr(':'); pstr(' ');
+  printobject(symbol, pstr);  // copy to globalstring
+  // store error message in GlobalErrorString for GET-ERROR
+  GlobalErrorString = obj;
+
   GCStack = NULL;
   longjmp(*handler, 1);
 }
 
 void error2 (symbol_t fname, PGM_P string) {
+  object *obj = errorsub(fname, string);
   if (!tstflag(MUFFLEERRORS)) {
-    errorsub(fname, string);
     pln(pserial);
   }
+
+  // store error message in GlobalErrorString for GET-ERROR
+  GlobalErrorString = obj;
+
   GCStack = NULL;
   longjmp(*handler, 1);
 }
@@ -486,6 +512,7 @@ void gc (object *form, object *env) {
   markobject(GCStack);
   markobject(form);
   markobject(env);
+  markobject(GlobalErrorString);
   sweep();
   #if defined(printgcs)
   pfl(pserial); pserial('{'); pint(Freespace - start, pserial); pserial('}');
@@ -2221,6 +2248,10 @@ object *sp_error (object *args, object *env) {
       cons(symbol(FORMAT), cons(nil, cons(arg, args))),
       env);
   }
+
+  // store error message in GlobalErrorString for GET-ERROR
+  GlobalErrorString = message;
+
   if (!tstflag(MUFFLEERRORS)) {
     char temp = Flags;
     clrflag(PRINTREADABLY);
@@ -2232,6 +2263,10 @@ object *sp_error (object *args, object *env) {
   longjmp(*handler, 1);
 }
 
+object *fn_geterror (object *args, object *env) {
+  return GlobalErrorString;
+}
+
 // Tail-recursive forms
 
 object *tf_progn (object *args, object *env) {
@@ -4995,6 +5030,7 @@ const char string223[] PROGMEM = "";
 // Insert your own function names here
 
 // Built-in symbol lookup table
+const char user06975b647a442ae56f6ee0abc8fb[] PROGMEM = "get-error";
 // functions of m-g-r/ulisp-esp-m5stack - begin
 const char user0f6db191193ec5132391e8cc3d09[] PROGMEM = "mute-speaker";
 const char user1e940820e12df008e2062d387aef[] PROGMEM = "setup-backlight-pwm";
@@ -5265,6 +5301,7 @@ const tbl_entry_t lookup_table[] PROGMEM = {
   { string222, (fn_ptr_type)OUTPUT, PINMODE },
   { string223, NULL, 0x00 },
 #endif
+  { user06975b647a442ae56f6ee0abc8fb, fn_geterror, 0x00 },
 // functions of m-g-r/ulisp-esp-m5stack - begin  
   { user0f6db191193ec5132391e8cc3d09, fn_mutespeaker, 0x00 },
   { user1e940820e12df008e2062d387aef, fn_setupbacklightpwm, 0x01 },

Catching error messages
#8

Hi Max,

yes, feel free to use it :-)


#9

Hey @Goheeca, thanks! I just, finally, saw this. Just pushed it to my GitHub repository: https://github.com/m-g-r/ulisp-esp-m5stack/tree/error-handling And also wrote an update in my weblog: http://blog.matroid.org/display/90 (nothing new in addition to what I wrote here, though).

Thanks again! I use this extensions for months now and I wouldn’t want to miss it anymore.

Do you have by any chance worked on some condition defining and catching facilities by any chance already?

Cheers, Max


#10

Hey @mgr,

I haven’t played with uLisp for a while, but to have the condition system would be nice. Perhaps, the Java implementation cafe-latte could be a good source of inspiration.