PageRenderTime 50ms CodeModel.GetById 38ms app.highlight 5ms RepoModel.GetById 1ms app.codeStats 0ms

Unknown | 174 lines | 119 code | 55 blank | 0 comment | 0 complexity | 31d321ecf65c2b84febe8d6658039438 MD5 | raw file
  1{{toppage: Falcon's Survival Guide}}
  3= Error recovery
  6You'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. 
  8The 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:
 11{{{ fal
 12print( "Enter your age: > " ) 
 13age = int( input() ) 
 15count = 0 
 16while count < age
 17   count += 1
 18   printl( "Happy belated birthday for your ", count, "." )
 22Falcon 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 .
 24Every 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.
 26[~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).  ~]
 28There 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.
 30Exceptions can be handled by the script by using the try  -  catch control block:
 33{{{ fal
 35   [try statements]
 36[ catch [object_type] [ in error_variable] ]
 37   [ catch statements ]
 41Each 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.
 43Try-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.
 45The following example ensures that the user will write a numeric entry: 
 48{{{ fal
 49age = 0 
 50while age == 0
 51   print( "Enter your age: > " )
 53   try 
 54      age = int( input() )
 55   catch 
 56      printl( "Please, enter a numeric value" )
 57   end 
 61A 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.
 63The 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. 
 65The content of an Error Objects is enumerated in the Function Reference manual. Please, refer to that guide for the details. 
 67Now we can print a more descriptive error message about what the user should do in our test program: 
 70{{{ fal
 71age = 0 
 72while age == 0
 73   print( "Enter your age: > " )
 75   try 
 76      age = int( input() )
 77   catch in error   // any variable name is ok here
 78      printl( "Oops, you caused the error number ", error.getCode(),
 79            "\nwhich means that: ", error.getMessage() )
 80      printl( "Please, enter a numeric value" )
 81   end 
 85Do 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.
 87Notice 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.
 89The {{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:
 91{{{ fal
 92    try 
 93       age = int( input() )
 94    end 
 96   // is equivalent to
 98    try: age = int( input() )
101== Raising errors
103It 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.
105The 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:
108{{{ fal
109Error( code, message, comment )
112However, 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:
115{{{ fal
116age = 0 
118   print( "Enter your age: > " )
120   try 
121      age = int( input() )
122      if age < 3: raise "Sorry, you are too young to type."
123      if age > 150: raise "Sorry, age limit for humans is 150."
125   catch StringType in error  
126      printl( error )
127      // age has been correctly assigned. Change it:
128      age = 0
130   catch Error in error
131      // it's a standard error of Error class, manage it normally
132      printl( "Oops, you caused the error number ", error.code,
133            "\nwhich means that: ", error.description )
134      printl( "Please, enter a numeric value" )
136   catch in error
137       printl( "Something else was raised... but I don't know what..." )
138       printl( "So I raise it again and the app will die." )
139        raise error
140    end 
141end age != 0
144In 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. 
146It 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: 
149{{{ fal
150   // the rest as before...
152   try
153      age = int( input() )
154      if age < 3: raise "Sorry you are too young to type."
155      if age > 150: raise "Sorry, age limit for humans is 150."
157   catch in error  
158      select error
159          case StringType
160            // manage strings as before
162          case Error
163            // manage Error instances as before...
165          default 
166            // print something as before... 
167            raise error
168      end 
169   end 
172This 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.
174{{toppage: Falcon's Survival Guide}}