Patch title: Release 94 bulk changes
Abstract:
File: /pliant/language/debug/errors.page
Key:
    Removed line
    Added line
title "Errors handling"

header "The problem to be solved"

  [At some point in the program, something unexpected appends (the remote side of the stream closes, the integer computation overflows, the array index is outside the bounds, ...).[lf]]
  [So the possible behaviors are (historical review):]
  list
    item
      [display an error message and quit: this is what we could talk no error handling, and what Pliant did until release 12.]
    item [pop the execution stack up to an error handler that can handle the new error: this is what C++ and most others do.]
    item [record the error and continue: this is what Pliant does now.]

  para
    [This first behavior could be called no error handling, and the trouble with it is that it does not give any opportunity to recover from some minor errors. As a result, in early Pliant release, there was a kind of error handling in the compiler so that we can recover from failed to compile expression, but it was not a general mechanism.]

  para
    [The second behavior could be called the optimistic one: it assumes that poping the stack and cleanly destroying local variables on it will enable to safely 'jump' to the error handler.[lf]]
    [If you pop the stack and do nothing else, the most visible result is a memory leak because complex local variables (strings, arrays) will not be freed, so in object oriented languages, since we have a destruction function for each data type, compiler designers said: we are going to generate a special task with a list of all objects have a destruction routine, so when poping up the stack to get back to the error handler, we'll do the garbage.[lf]]
    [That's dealing with the most visible part of the trouble, but not with the hardest one, which is that poping the stack up to the error handler means exiting from all the functions in the middle, so what about:]
    listing
      var Sem s
      function trouble
        s request
        foo
        s release
    [If the error append in 'foo', we are in great troubles because the semaphore 's' will not be released.[lf]]
    [The answer will be: you must not lock the semaphore directly but rather do it through calling a method on an object that we'll call a semaphore locker and that will release the semaphore when it is destroyed.[lf]]
    [So you have to use a pure object language, which means much no use interface code and slow programs.[lf]]
    [Now imagine that we use the 'parallel' control which enables several pieces of code to be executed in parallel on an SMP computer, then if you want a consistent semantic for the 'parallel' control, you would have to assume that when the error appends, all the running threads are stopped, and assuming this is even a bigger constaint because in low level classes, the program must use 'enter must complete' sections of code for many objects transforms.[lf]]
    [At the end, this error handling mechanism simply lead to programs that work most time, but will remain buggy foreever ...]

  para
    [Now the Pliant behavior: when the error occurs, we record it ... and continue, so each function in the middle between the current point and the error handler will continue to execute ... until either it reaches the normal end or gets to a point where the programmer said: if we have a recorded error, you can safely return from this point.[lf]]
    [The draw back of it is that jumping from the error point to the error handler will take time, but since an error is an exceptional event, who cares ?[lf]]
    [On the other hand, we'll have less buggy programs.]

  para
    [In Pliant, some of the errors will be notified as fatal, which mean that we get back to no error handling for these ones. ]
    [The most important one of these being [dq]out of memory[dq]. ]
    [I decided not to allow to recover from [dq]out of memory[dq] for the following reasons:]
    list
      item [If [dq]out of memory[dq] may generate a recoverable error, then most middle level instructions (such as adding two strings) can generate an error whereas i expect the error handling to be rather a high level feature, because it has a time cost.]
      item [We cannot safely run the error handler because this one might also require to allocate memory for temporary objects.]
      item [On modern operating systems, the virtual memory will turn your machine to ususable, because spending all it's time waiting for swapped out pages, before it cannot allocate more memory pages.]
    
  para
    [As a summary for this introduction on Pliant error handling, i'd say that, in fact, various Pliant programs can use all the three mechanisms described at the beginning, none being perfect.]


header "Pliant high level functions"

  para
    listing
      ¤comment `message
        `body
    [The 'message' will be pushed on the actions stack while the body is executed and poped at end. ]
    [The 'message' can be a serveral values that will all be casted to strings and concatenated. ]
    [Should an error occur while 'body' is executed and not be cleared by any error handler, then the 'message' will be part of the final error message.]

  para
    listing
      `an_expression ¤?
    [Means, evaluate the expression, then return if an error is recorded. ]
    [The overall result is the same as the expression result, so you can write things like:]
    listing
      while (`condition ?)
        `body
  
  para
    listing
      ¤shy
        `body
    [Means execute the body, but should an error occur in the middle, you can safely return from the 'shy' control (not from the all function).[lf]]
    [So if you want to mimic the C++ or others error handling mechanism, you just write for each of your functions:]
    listing
      function foo ...
        shy
          ...

  para
    listing
      ¤safe
        `body
      [¤success
        `body2]
      [¤failure
        `body3]
      ¤success
        `body2
      ¤failure
        `body3
    [Means executes the body, but all recoverable errors will be trapped, and if no error occurred, then the second body will also be executed, else the third one will be executed.[lf]]
    [The error is masked while the third body is executed.]

  para
    [Now, you still can create your own high level error handling functions using the low level mechanism described below, which has much more capabilities than the few high level functions that i have already written, and use meta programming in order to create nice new controls and apply changes to the program on the fly.]


header "Pliant low level error handling mechanism"

  header "Overview of the low level error handling mechanism"

    [There are three stacks in Pliant:]
    list
      item [the processor stack contains, just like with any other language, the local variables of the called functions.]
      item [the actions stack contains a set of 'ActionRecord' datas that describe what the program is currently doing.]
      item [the errors stack contains a set of 'ErrorRecord' datas that stores both the errors and the error handlers.]
  
    [The compiler part of the low level error handling is in ] ; link "error.c" "error.c"

  header "Managing the actions stack"

    [If you have written programs for a while, you know that whatever good debuggers and error handling mechanisms can be, sometime you have to get back to 'printf' ('console' in Pliant) to trace your program and find what's wrong with it. The actions stack is just an improvement of this mechanism. Each time you push a record on the stack, the message is not displayed, but rather stored, so that, should an error occur, only the usefull messages will be printed.]

    para
      listing
        type ¤ActionRecord
          field Str action
          field Pointer:ActionRecord next

    para
      listing
        function ¤action_push_record `r `msg
          arg_w ActionRecord `r ; arg Str `msg
      [It will had a record on the actions stack and this record will contain the provided message so that, should an error occur, the message will be adder to the error message.]

    para
      listing
        function ¤action_pull_record `r
          arg_rw ActionRecord `r
      [This call must be properly nested with the previous one and means. Ok, the action i recorded is now finished.]

    para
      listing
        function ¤action_top_record -> `r
          arg_RW ActionRecord `r
      [If you want to travel the actions stack, this function will return a pointer to the first item, and the 'next' field in each record will get you to the next one, until you get a record at 'null' address.]

  header "Managing the errors stack"

    para
      listing
        type ¤ErrorRecord
          field Int id
          field Arrow context
          field Str message
          field Int filter
          field Pointer:ErrorRecord next

      []

    para
      listing
        function ¤error_push_record `r `filter
          arg_w ErrorRecord `r ; arg ErrorID `filter

    [We add a record on the errors stack.[lf]]
    [An 'ErrorRecord' is both an error handler and a data that will store the error when it appends.[lf]]
    [The 'filter' parameter describes which errors should be stopped by this error handler. It can be either and error identifier (the predefined ones are listed in ] ; link "error1.pli" "error1.pli" ; [) if you want to filter a single kind of error, or 'error_filter_none' if you don't want to filter any error, or 'error_filter_all' if you want to filter all errors.[lf]]
    [As a result, should an error occur that is filtered by the error handler, it will not propagate deeper until we remove the handler.[lf]]
    [Moreover, adding an error record on the stack will hide the current error if one was already notified ... until you pop the error handler.]

    para
      listing
        function ¤error_pull_record `r
          arg_rw ErrorRecord `r

    [Removes the error record from the stack. If it contains an error, and the underlying error record does not, the error will be propagated.[lf]]
    [If you don't want the error to be propagated, then just clear it by setting it's 'id' field to 'error_id_noerror' before pulling the record.]

    para
      listing
        function ¤error_top_record -> `r
          arg_RW ErrorRecord `r

      [If you want to travel the errors stack, this function will return a pointer to the first item, and the 'next' field in each record will get you to the next one, until you get a record at 'null' address.]

    para
      listing
        function ¤error_notify_fatal `id `msg
          arg ErrorID `id ; arg Str `msg

      [notifies a fatal error that cannot be recovered, so the error message will be displayed, then the process will exit. The return code of the process will be the error id if it was one of the predefined one.]

    para
      listing
        function ¤error_notify `id `context `msg
          arg ErrorID `id ; arg Address `context ; arg Str `msg

      [The error will be stored in the top most error record if it does not contain any error yet. Moreover, if no filter can catch this error, it will be reported to the user and the program will stop (see 'error_report' below)]

    para
      listing
        function ¤error_notified -> `e
          arg CBool `e

      [Tests if an error is currently recorded on the top of the errors stack.]

    para
      listing
        function ¤error_propagate `src `dest
          arg ErrorRecord `src `dest
          
      [Simply copyes the error report from 'src' to 'dest' if 'src' contains an error, and 'dest' does not yet.]

    para
      listing
        function ¤error_report

      [Reports the error stored on the top record if no underlying filter is expected to catch it.]

    para
      listing
        function ¤error_allocate_id -> `id
          arg ErrorID `id

      [Gets a new error id for private use.]