PageRenderTime 39ms CodeModel.GetById 0ms RepoModel.GetById 0ms app.codeStats 0ms

/docs/sg/Error recovery.wiki

https://bitbucket.org/kib2/wikiparser
Unknown | 174 lines | 119 code | 55 blank | 0 comment | 0 complexity | 31d321ecf65c2b84febe8d6658039438 MD5 | raw file
  1. {{toppage: Falcon's Survival Guide}}
  2. = Error recovery
  3. You'll remember our first interactive Falcon script: that was the one asking you for your age and then entering a loop congratulating with you many times for your past birthdays.
  4. The int() function tried to convert a string (what you typed) into an integer (your age), but if this was not possible for some reason, a runtime error appeared instead, and the program was terminated. Here follows a reduced version of that script that will serve our needs:
  5. {{{ fal
  6. print( "Enter your age: > " )
  7. age = int( input() )
  8. count = 0
  9. while count < age
  10. count += 1
  11. printl( "Happy belated birthday for your ", count, "." )
  12. end
  13. }}}
  14. Falcon provides a mechanism to handle unexpected situations that may arise in a program. Many library functions and language constructs use this mechanism to communicate with the controlling script about unexpected situations, but this system is also available to the script itself, so that script writers can take advantage of this. It's called exception raising .
  15. Every time the Virtual Machine, one of the library functions or even other script parts run into a potentially dangerous situation, they raise an exception. If this exception is not handled somehow by the script, it is handed back to the system; the Falcon interpreter will print an error message and exit.
  16. [~If the Falcon Virtual Machine is used by an embedding application to run some scripts, the embedder has the ability to set a top level exception handler. This will usually grant the embedding application the ability to know about fatal errors in the scripts, and take sensible actions (as i.e. mailing the administrators). ~]
  17. There are a set of exceptions that are called unstoppable . These exceptions are raised by library functions or by the Virtual Machine itself if it finds some critical condition that may prevent scripts from working, as for example script bytecode corruptions. In those situations, letting the scripts intercept the exceptions would not be wise, hence the need of unstoppable exceptions.
  18. Exceptions can be handled by the script by using the try - catch control block:
  19. {{{ fal
  20. try
  21. [try statements]
  22. [ catch [object_type] [ in error_variable] ]
  23. [ catch statements ]
  24. end
  25. }}}
  26. Each catch block can intercept a certain kind of variable. The working principle is the same as the {{select}} statement; a type can be one of the type names, or it can be the name of a symbol declared somewhere in the program.
  27. Try-catch blocks can be nested (put one into another) or combined with any other Falcon block statement ( {{if}}, {{while}}, {{for}}, {{function}} and so on). The try-catch block functionality is as follows: whenever an instruction inside the try (try-statements) causes an exception to be raised, the control flow is immediately broken. If a catch block is present, the type of the raised object is matched against the type specifiers of the catch blocks. Overall types (as i.e. StringType or ObjectType) get precedence, then the specific symbols used as specifiers are considered in the order they are declared in the catch clauses. For this reason, catch blocks intercepting subclasses should be declared before the ones intercepting parent classes. Finally, if none of the typed catch blocks matches the raised exception, the raised error is passed to a catch hander without type declaration, if present. If a typeless catch clause is not present, the error is then raised to the application level and this usually terminates the script.
  28. The following example ensures that the user will write a numeric entry:
  29. {{{ fal
  30. age = 0
  31. while age == 0
  32. print( "Enter your age: > " )
  33. try
  34. age = int( input() )
  35. catch
  36. printl( "Please, enter a numeric value" )
  37. end
  38. end
  39. }}}
  40. A catch clause may have an optional variable that will be filled with the exception that has been raised in the try block. The exception can be any Falcon item (including numbers, strings and objects) that describes what exactly was the error condition. By convention, the Virtual Machine and all the library functions will only raise an object of class {{Error}} , or one of its subclasses. However, scripts and other extensions libraries may raise any kind of item.
  41. The Error class provides a series of accessors, that is, methods that are specifically used to access data in the inner object. Normally, scripts are not very interested in peeking the data inside an Error instance; usually, the embedding application is the entity that is meant to intercept errors and deal with them. For this reason, the embedding API puts at library disposal a C++ class called Falcon::Error; in case the script wants to intercept it, and only in that case, the C++ object is wrapped in Falcon object, and methods are used to query the internal Falcon::Error C++ instance. This is because intercepting and analyzing Error instances from scripts is considered an extraordinary operation; the overhead introduced by using methods instead of plain properties to retrieve Error values is marginal with respect to the advantage the embedding application receives by being able to use directly C++ objects in its code when a forbidding error condition is encountered by the script.
  42. The content of an Error Objects is enumerated in the Function Reference manual. Please, refer to that guide for the details.
  43. Now we can print a more descriptive error message about what the user should do in our test program:
  44. {{{ fal
  45. age = 0
  46. while age == 0
  47. print( "Enter your age: > " )
  48. try
  49. age = int( input() )
  50. catch in error // any variable name is ok here
  51. printl( "Oops, you caused the error number ", error.getCode(),
  52. "\nwhich means that: ", error.getMessage() )
  53. printl( "Please, enter a numeric value" )
  54. end
  55. end
  56. }}}
  57. Do not confuse the {{Error}} class with the above {{error}} variable: Falcon is fully case-sensitive, so the variable we named {{error}} in the above code is just a normal variable receiving an {{Error}} class instance.
  58. Notice that the catch block is not immune to error raising. If an exception is raised inside a catch block, it will have exactly the same effect as if it were raised in any other part of the program: it may be caught again with another try/catch block, or it may be left to handle to the above handlers, or finally to the Virtual Machine. We'll see in a moment how this fact can be useful.
  59. The {{try}} instruction can be abbreviated with the : operator; it wont be possible to catch any error in this case, but this may be useful in case any possible error must simply be discarded:
  60. {{{ fal
  61. try
  62. age = int( input() )
  63. end
  64. // is equivalent to
  65. try: age = int( input() )
  66. }}}
  67. == Raising errors
  68. It is interesting to be able to raise errors; the execution flow is immediately interrupted and a possible error manager is invoked, so raising errors inside the scripts may often obviate the need for "if" sequences, each of them checking for the right things to be done at each step. The keyword {{raise}} makes an item to be thrown and treats it as an exception.
  69. The script may choose two different approaches to raise errors: one is that of creating an instance of the Error class using the Error() constructor, which accepts the following parameters:
  70. {{{ fal
  71. Error( code, message, comment )
  72. }}}
  73. However, sometimes it is useful to throw a lighter object. Suppose that we want to set a maximum and minimum age in our example, and that we cause an error to be raised when those limits are not respected. In this case, that we may call flow control exception raising , having a full error to be raised may be an overkill. Follow this example:
  74. {{{ fal
  75. age = 0
  76. loop
  77. print( "Enter your age: > " )
  78. try
  79. age = int( input() )
  80. if age < 3: raise "Sorry, you are too young to type."
  81. if age > 150: raise "Sorry, age limit for humans is 150."
  82. catch StringType in error
  83. printl( error )
  84. // age has been correctly assigned. Change it:
  85. age = 0
  86. catch Error in error
  87. // it's a standard error of Error class, manage it normally
  88. printl( "Oops, you caused the error number ", error.code,
  89. "\nwhich means that: ", error.description )
  90. printl( "Please, enter a numeric value" )
  91. catch in error
  92. printl( "Something else was raised... but I don't know what..." )
  93. printl( "So I raise it again and the app will die." )
  94. raise error
  95. end
  96. end age != 0
  97. }}}
  98. In this way, we have a controlled interruption of the normal code flow which is passed to the StringType catch branch, with a minimal overhead with respect to the equivalent code performed with a series of branches. If the weight of those branches becomes relevant, the exception code flow control may be even more efficient (the virtual machine management of try-catch blocks is comparatively light with respect to any other kind of operation), while it may be more elegant, and possibly more readable.
  99. It is also to be noticed that the caught variable may be parsed through a select statement. This may or may be an interesting opportunity, depending on the needed flexibility. The above code is equivalent to the following:
  100. {{{ fal
  101. // the rest as before...
  102. try
  103. age = int( input() )
  104. if age < 3: raise "Sorry you are too young to type."
  105. if age > 150: raise "Sorry, age limit for humans is 150."
  106. catch in error
  107. select error
  108. case StringType
  109. // manage strings as before
  110. case Error
  111. // manage Error instances as before...
  112. default
  113. // print something as before...
  114. raise error
  115. end
  116. end
  117. }}}
  118. This solution is a visually a bit less compact, requiring three indent levels where the previous only needed one. Also, the VM has an opcode that manages a typed catch a bit faster than a select statement (it's one VM opcode less, actually, but the opcode that is skipped with the typed catch approach is quite fast to be executed). However, it presents two advantages: first of all, it is possible to execute some common code before or/and after any specific error management. Secondly, the select code may be delegated to a function (or to a lambda) that may be changed on the fly during program execution, actually changing the error management policy for that section. Through this kind of semantics, a common error management policy may be given to different handlers. As this doesn't prevent writing specific typed catches, each error management code may be highly customized through a combination of static typed catch statements and dynamic catch-everything statements passing the raised value to a common manager.
  119. {{toppage: Falcon's Survival Guide}}