 |
AutoHotkey Community Let's help each other out
|
| View previous topic :: View next topic |
| Author |
Message |
bmcclure
Joined: 24 Nov 2007 Posts: 766
|
Posted: Thu Mar 05, 2009 8:38 am Post subject: |
|
|
Maybe instead of "callingFunction" as a parameter in the throw() function (which would be redundant for the coder), it could be "callingClass".
Then Class could, as a last resort, try sending the exception back to the class that called it with %callingClass%_catch() (dynamic so if it doesn't exist, the exception is ignored)
Additionally, maybe the registered "exceptionHandler" could be an object instead of a function string. The object could be of type ExceptionHandler and would be required to have a _catch(ExceptionObject, callingClass) function which would handle the exception that Class throws at it.
That seems more object-oriented.
I don't know if exceptions are something you're interested in implementing in the Class library, but I think it would be a great, and potentially powerful feature for large scripts.
My DOM library would also be able to work as intended, I think, if it were implemented like the above. The user could register a DOMExceptionHandler to handle a DOMException, and then throw a DOMException at Class, which will pass it back to Handler.
The main benefit would be that classes that the user never touches the code of could throw documented Exceptions, which the user can catch with a custom handler and handle however they want.
You could even go so far as to include some basic Throwable classes, like Java does: Exception, and Error, which could then be used in any class utilizing the Class library as generic exception objects. That would be very cool!
Additionally, as templates to help users get started, generic ErrorHandler and ExceptionHandler classes could be created, in similar fashion to the generic Node class, demonstrating the catch() method.
Update whoknows: I previously proposed the the throw() method to work like this: Class_throw(ExceptionObject, callingClass).
I think we could take that one step further and make it Class_throw(ExceptionObject, callingObject). Then , Class will be able to pass on the specific object that threw the exception. I think this would be the best approach yet.
I'm sure all, or most, of this functionality could be implemented by a separate add-on class, but I think it would be a nice core feature to have, and would be good to take the work out of the user's hands. _________________ Ben
My Trac projects
My Wiki
[Broken] - My music |
|
| Back to top |
|
 |
animeaime
Joined: 04 Nov 2008 Posts: 1046
|
Posted: Thu Mar 05, 2009 4:45 pm Post subject: |
|
|
Well, lucky for us there are built-in variables that will make throwable make more sense. Also, luckily, Java is my native language (never really got into C++), so I know about throwable.
I'm going to sit on this for a little bit. I'm not seeing any obvious problems with the idea, but I don't want it to be limiting either, so I'm going to let it bounce in my head until I have a solid way to implement this.
For example, a division function might work like this. Don't get any ideas, I'm not implementing a BigInteger class, you can if you want...
| Code: | /*
Returns the BigInteger Num1 // Num2 (floor division)
Throws:
ArithmeticException if Num2 = 0
*/
BigInteger_div(Num1, Num2)
{
;BigInteger_ZERO() would return the BigInteger zero - a constant
if BigInteger_equals(Num2, BigInteger_ZERO())
{
return Class_throw(ArithmeticException_new("Division by zero"
, A_LineFile, A_LineNumber, A_ThisFunc))
}
;Your code goes here
}
|
This is the only method I could think of. Class_throw would return the empty string (since the return value might never be stored); the return statement is just to put the creation and return on one line instead of two. Then, there would be a function like, Class_getException which would return the current exception object (or 0 if none) - allowing it to be used as a quasi boolean to "catch" the error.
Also, depending on how I implement it, creating the call stack "artifically" might be possible.
For example, your code might be this.
| Code: | MyClass_myFunction()
{
;Code before
Num3 := BigInteger_div(Num1, Num2)
if e := Class_getException()
{
;store the line number of the start of the "catch" block, and use it instead of A_LineNumber. This way, the actual line of the error and the line number stored are relatively close.
LineNumber := A_LineNumber
;either "catch" the exception here and handle it
;Call Class_catch passing the exception object to instruct the library that the exception was caught (and thus, the exception object can be destroyed)
;or throw it again (and add the location data - to create the stack
return Class_throwA(e, A_LineFile, LineNumber, A_ThisFunc)
}
;Code after
} |
Throw would be implemented in such a way that it would allow creation of the call stack. Using the built-in vars allows the file, line, and function name to be stored, and a "call stack" can be artifically created. A_LineNumber won't be the actual line the error was on, but the actual error will be a few lines above. Also, this way, the file and line number don't need to be updated when the code moves around. Of coure, when the code is compiled, the line numbers may not match (because the code is "squished together"), but then again, you shouldn't have uncaught exceptions in a compiled script - caught ones sure, but not uncaught.
ThrowA (throw again) should be called when throwing an existing object (in a "catch" block) - throw and throw will be merged if I find out they function identically. Since AHK doesn't actually support exceptions, a try-catch isn't possible, nor will it automatically return if the exception is not caught. Since only one exception should be active at a time (if done correctly), this method should work. Use Class_Throw to create the thrown object, Class_throwA to throw the existing object, and Class_catch to signify the exception was "caught" (and the current exception will be destroyed).
Like I said, this is a rough draft of the implementation. I think it works, but I'll gladly take feedback. Also, I'm pretty sure I can whip up a function that would allow multiple "catch" branches (like java has). Since the object stores a class, this shouldn't be too hard.
Probably something like this would surfice:
| Code: | MyClass_myFunction()
{
;Code before
Num3 := BigInteger_div(Num1, Num2)
if e := Class_getException()
{
LineNumber := A_LineNumber
if (Class_getClassName(e) = "ArithmeticException")
{
;either "catch" the exception here and handle it
;Call Class_catch with zero parameters to instruct the library that the exception was caught (and thus, the exception object can be destroyed)
;or throw it again (and add the location data - to create the stack
return Class_throwA(A_LineFile, LineNumber, A_ThisFunc)
}
else if (Class_getClassName(e) = "AnotherException")
{
;your code here
}
}
;Code after
} |
I'm going to start implementing this. First I'm going to double check the current stuff and upload it. Also, going to add a Class option Comparable along with this throwable ability. As a note, you would specify the exception class as Throwable (as a class option). There would be no changes to existing code, except to add throw statements for errors. In interest of backward-compatability and simplicity of code, I'm going to continue to use the empty string as a return to indicate an error instead of throwing an object. However, I will update the Rectangle class to throw an exception when setting height or width to zero or a negative. If such values are passed to the Rectangle_new function, the object won't be created and the empty string will be returned to indicate the error (and Class_getException() can be used to see if the error is exception related).
Like I said, this will be on hold for a few days to give me time to sort everything out and make sure I didn't overlook anything, but it definitely will be added shortly, thanks for the idea. _________________ As always, if you have any further questions, don't hesitate to ask.
Add OOP to your scripts via the Class Library. Check out my scripts. |
|
| Back to top |
|
 |
bmcclure
Joined: 24 Nov 2007 Posts: 766
|
Posted: Thu Mar 05, 2009 5:06 pm Post subject: |
|
|
So to see if I'm understanding this correctly (I'm not a native java developer, but just trying to understand the java concepts and apply it to oop in general):
Would it be possible to handle an exception further up the stack than the function which threw the exception (toward the base), or would you have to wait until the further-down function (the one that threw it) finished executing?
Additionally, with your method, would it still be possible at some point to implement the ability to "register" an exception handler against a particular object? And how about against a whole class? I think both of those features could be very powerful.
Also, thank you for working to implement exceptions. I think it will go one step toward making it easy to implement large class libraries, if they are able to throw documented exceptions that the user can choose how to handle. _________________ Ben
My Trac projects
My Wiki
[Broken] - My music |
|
| Back to top |
|
 |
animeaime
Joined: 04 Nov 2008 Posts: 1046
|
Posted: Thu Mar 05, 2009 5:20 pm Post subject: |
|
|
| bmcclure wrote: | | Would it be possible to handle an exception further up the stack than the function which threw the exception, or would you have to wait until the further-down function finished executing? |
I'm not sure I understand the question. "Further up the stack" would imply before the exception is thrown. The first element in the stack trace would be where the exception was thrown. Each following element would be where the exception was thrown again. Could you clarify your question? An example might help me (or it might not )
| bmcclure wrote: | | would it still be possible at some point to implement the ability to "register" an exception handler against a particular object? And how about against a whole class? |
Not sure what you mean by "register". This method would have the stack and an error message (set on creation) stored in the Exception (might add more - checking what java stores). So, it would contain the file name, line number, and function name for each thrown step (i.e. a call to Class_throw or Class_throwA). What other data should it store? I can always have it store more information if required. _________________ As always, if you have any further questions, don't hesitate to ask.
Add OOP to your scripts via the Class Library. Check out my scripts. |
|
| Back to top |
|
 |
bmcclure
Joined: 24 Nov 2007 Posts: 766
|
Posted: Thu Mar 05, 2009 5:48 pm Post subject: |
|
|
I don't understand Java enough to fully understand the concept yet, but supposedly when an exception in java occurs, it starts looking back through the call stack for a handler like:
So, I think, the exception handling is done before the throw() function returns to the calling function, because Java dynamically finds and calls the appropriate exception handler internally.
I got the impression from your notes that, rather than doing this, it would just register the exception and return, and then the script would be responsible for checking for an exception immediately after if it should be caught? Maybe I misunderstood that.
--
Regarding "registering" an exception handler, I guess it would depend on the answer to the above topic, if it makes any more sense now. Sorry if it does not
So I'll revisit that after clearing the first issue up.
My initial idea was essentially to have a class option (or maybe not, I don't know), called ExceptionHandler, that is required to have a _catch() function.
Then Class can look back through the call stack for an ExceptionHandler, and pass the exception to its catch() method for processing, if one exists. In that case, Class would return to the calling function that the exception was caught.
If no ExceptionHandler was in the stack, Class could return the exception as uncaught, and the script can handle it appropriately.
That would essentially allow exception catching to occur outside of the scope of the code which called it, so that the exception can either be caught or uncaught by the time it's handed back to the function which called it.
--
If class could work like that, then I think a class could use the _catch() method to send the exception off to another handler, or to generate a pseudo-"chained" exception by throwing something else.
However I emphatically admit that I don't understand exceptions nearly as well as you, so this is probably like a blind man trying to explain the sunset to someone looking at it There might be better ways to do what I mean.
Note: Sometimes I mistakenly use the term error instead of exception--still getting used to it, heh; please excuse the lingo. _________________ Ben
My Trac projects
My Wiki
[Broken] - My music |
|
| Back to top |
|
 |
bmcclure
Joined: 24 Nov 2007 Posts: 766
|
Posted: Thu Mar 05, 2009 5:55 pm Post subject: |
|
|
Additional thought about the above:
If class was able to call a catch() method in the call stack, then certain ExceptionHandlers could be made to handle certain types of exceptions easily.
If the exception is handled, catch() could return true, and Class would pass the exception back to the caller as caught.
If the exception is not handled, catch() could return false, and Class could continue looking back through the call stack, or return the exception as uncaught to the calling function.
Update
Sorry, forgot an example:
1. I have a script which calls a method in DOMNode
2. The method in DOMNode calls a method in another class
3. The other class calls a method in yet another class, which throws an exception.
With the bahavior I'm talking about, when the exception was thrown by the last class, Class does the following:
1. Check if the class that threw the exception is an event handler. No? Move on.
2. Check if the next class back in the call stack is an event handler. Yes? Pass the exception to catch(). if the return is true, then the throw() function returns the exception as caught. if the return is false, move on.
3. Check if the next class back in the call stack (DOMNode) is an event handler. No? Move on.
4. That's the last call in the stack. Now Class would return the exception as uncaught, so that it could be handled by the script which called the original function if so desired. _________________ Ben
My Trac projects
My Wiki
[Broken] - My music |
|
| Back to top |
|
 |
animeaime
Joined: 04 Nov 2008 Posts: 1046
|
Posted: Thu Mar 05, 2009 6:19 pm Post subject: |
|
|
A blind man describing the sunset might be a bad analogy. It's more like an inventor describing their creation to a common folk. I mean the idea sounds great, but I think you're crazy for thinking its possible (no offense). I mean, I'll let it bounce around, but no promises.
Since AHK doesn't have anyway to automatically "catch" the error, how is it that you envision this? So, far, this is the image I see.
1) FunctionA throws an Excption.
FunctionA creates an exception object, or somehow sets a "signal" that says "hey, I threw an exception", and then the function returns.
2) FunctionB calls FunctionA. Suppose FunctionA throws an exception.
a) The exception is caught (and dealt with or thrown again)
b) The exception is NOT caught...
This is where your "crazy invention" comes in. I can't think of anyway to force the function to return. If the functions throws an exception (which is not caught), then the function should IMMEDIATELY return (and add the file, line, and function info to the "call stack"). Then, eventually the exception should be caught. If not, then in Java style, the program would immediately exit (ExitApp - which if it had an OnExit would do cleanup). I mean, the idea is great, crazy, but great. I don't know any way to add this, though.
This would require a "try-catch" block syntax. Then, if an exception occured outside of a try-catch block, then the function would immediately return until the exception is caught. If it never is caught, then the script would exit. I'm open to suggestions - I think the idea is great. But, it sounds like Da Vinci's flying machine, it's a great idea, and it works, but it's a few hundred years early. When/if AHK gets built-in error handling, this would be easy, but as it is, I don't know how to do it. I'll see what I can figure out, but I can't think of anything. I mean, if you didn't want to "catch" the exception, you could just throw it again (until it is dealt with).
| Code: | MyClass_myFunction()
{
;Code before
Num3 := BigInteger_div(Num1, Num2)
if e := Class_getException()
{
LineNumber := A_LineNumber
;don't catch the exception
;throw it again (and add the location data - to create the stack
return Class_throwA(e, A_LineFile, LineNumber, A_ThisFunc)
}
;Code after
} |
I could wrap this into a single function like so.
| Code: | MyClass_myFunction()
{
;Code before
Num3 := BigInteger_div(Num1, Num2)
if Class_hasException(A_LineFile, A_LineNumber, A_ThisFunc)
return
;Code after
} |
However, I find a flaw with my idea. It won't work as exceptions should, because anything that calls MyClass_myFunction would have to check for an exception after calling MyClass_myFunction - which causes immense problems because if MyClass_myFunction calls ANY function which MIGHT return an exception, you have to add the above two line check after calling MyClass_myFunction. Also, just because the function doesn't return an exception now, doesn't mean it won't later. So, to be safe, you would need to have that two line check after EVERY CLASS FUNCTION CALL!
So, that's a big no-no. So, it might not be possible to implement exception handling - at least not true execption handling. Unless you don't mind adding this check in after every class function call... _________________ As always, if you have any further questions, don't hesitate to ask.
Add OOP to your scripts via the Class Library. Check out my scripts. |
|
| Back to top |
|
 |
bmcclure
Joined: 24 Nov 2007 Posts: 766
|
Posted: Thu Mar 05, 2009 6:43 pm Post subject: |
|
|
Sounds good, I'd prefer to be a crazy inventor than a blind man
I think the only thing outside of the scope of what can be done right now is the try {} block, and other related blocks. But maybe there's a way around that. Although maybe it's too much for the Class library to take on right now, trying to stay light-weight.
1. I call a function in DOMElement class. DOMElement class starts by registering itself in the stack with Class_stack() or something.
2. DOMElement calls a method in another class, DOMNode. DOMNode starts by registering itself in the stack, which adds it after DOMElement.
3. DOMNode throws an exception.
4. Class puts the stack info in the exception, and then starts working backwards down the stack, checking if the class is an ExceptionHandler type starting with DOMNode.
5. If so, it passes the exception to the class's _catch() method to process and return true or false. Depending on the return, it either returns the object as caught or works further down the stack.
6. When it reaches the bottom of the stack, if no catch() methods were found or they all returned false, Class could store the unhandled exception so that it could be retrieved by the user script.
A few issues:
I don't know where to store the stack, if there is no exception thrown yet.
I don't know how to reset the stack if no exception occurs, without explicitely calling a stackReset() or something. So that might be an issue too.
Of course, this means each method which can throw or handle an exception would need to register itself in the stack, requiring extra work. But that's not much more work than adding a try{} block to every method, right?
--
I think this would also only require the user to check if an exception exists in the original script call, since any caught exceptions would be handled without having to check manually if an exception occurred.
--
Maybe a little too "flying machine"-ish, but just throwing something out there
I'm good with the original method you've suggested, because it's better than no exceptions at all. Just trying to think of how it could be made as close to the real thing as possible. _________________ Ben
My Trac projects
My Wiki
[Broken] - My music |
|
| Back to top |
|
 |
animeaime
Joined: 04 Nov 2008 Posts: 1046
|
Posted: Thu Mar 05, 2009 7:01 pm Post subject: |
|
|
| bmcclure wrote: | | Of course, this means each method which can throw or handle an exception would need to register itself in the stack, requiring extra work. But that's pretty much the same thing that's required when using a try {} block, right? |
Right, it sounds like your idea is the first call in the function would be a call that says "started this function".
Then, in the case an exception is throw in SomeFunction, then it would backtrace through this stack and find a class to handle the error. However, usually, exceptions (and errors in general) are handled not by the class but by the function. Also, the same function might handle the same exception (or a different one) multiple times in the same function. Also, whenever the exception is caught, that function would continue as normal. So, if FunctionA throws an exception, FunctionB calls functionA and catches the exception (if any) after calling FunctionA, then functionB would continue. If functionB didn't catch the exception, then functionB would return immediately.
Even if the functions tracked the stack, and even if there was a way to dynamically call something to handle the exception, there remains one problem - how to continue that function. As the example above, after the exception is caught in a function, that function would continue as if nothing happened (presumably it would handle the exception in some way). However, the problem is how to "return" enough to get to that point.
Ex.
FunctionA line 1 calls FunctionB
FunctionB line 2 calls FunctionC
FunctionC line 3 has an exception.
It would then start to backtrace until the exception is caught. Let's say FunctionA called FunctionB in a "try-catch block", then since the exception is caught, FunctionA would continue after the catch block. I just thought of a way which MIGHT be possible to implement a try-catch block, programically. Use messages, a goto, and a label. I'm going to test the idea and report back, but this might work. If it does, I should be able to use the EXACT same method to jump to the end of a function. Put a label right before the end of the function.
Hold on a sec....I'm going to test this. I might be able to make your crazy invention work... _________________ As always, if you have any further questions, don't hesitate to ask.
Add OOP to your scripts via the Class Library. Check out my scripts. |
|
| Back to top |
|
 |
bmcclure
Joined: 24 Nov 2007 Posts: 766
|
Posted: Thu Mar 05, 2009 7:07 pm Post subject: |
|
|
Hey a label within a function for exceptions seems like a great idea--if it will work. Anxiously awaiting your test  _________________ Ben
My Trac projects
My Wiki
[Broken] - My music |
|
| Back to top |
|
 |
animeaime
Joined: 04 Nov 2008 Posts: 1046
|
Posted: Thu Mar 05, 2009 8:15 pm Post subject: |
|
|
Ok. Did some testing. Sadly, I can't figure it out. It seems you can't gosub to a label in another function. Up to that, it seems possible. I'm going to put this one on the back burner. I'm going to start working on my projects and I could see this taking all my time and getting no where. I'm not saying no. I'm saying I'd be glad to do it, when I figure it out. _________________ As always, if you have any further questions, don't hesitate to ask.
Add OOP to your scripts via the Class Library. Check out my scripts. |
|
| Back to top |
|
 |
bmcclure
Joined: 24 Nov 2007 Posts: 766
|
Posted: Thu Mar 05, 2009 8:25 pm Post subject: |
|
|
no problem, that's all i could hope for
Thanks a lot for taking a look.
Off the cuff, what about the possibility of, since there's trouble figuring out how to include it in each function, including it per class, and passing the calling function name as one of the parameters?
Then the catch() function could have a case for every function that it knows how to catch.
But that may not be very intuitive.
Anyway, thanks a lot for putting the thought you did into this, and I look forward to the changes previously discussed nonetheless!
Thanks,
Ben _________________ Ben
My Trac projects
My Wiki
[Broken] - My music |
|
| Back to top |
|
 |
animeaime
Joined: 04 Nov 2008 Posts: 1046
|
Posted: Thu Mar 05, 2009 11:21 pm Post subject: |
|
|
Ok, here is the latest update - download the zip. It's cleaned, polished, and really sparkles - you BETTER ooh and ah. GOT IT?!
Ok, anyway. This release comes at a weird time. Just as I was about to have the website up to date (a little more to go), this update comes along. So, sadly, it will still be some time before the site is updated, but the site looks amazinig (cleaner and more organized), and these features will make a great addition.
I'm calling this Class V2, however, it's backward compatable - its like USB 2.0 - still can plug into a 1.1 world, but works MUCH better when 2.0 compliant. You can use the new design, or the old. I would, however, recommend the new, and here's why...
This new releas comes with a newly organized Node.ahk to start making your own classes. The template is setup with all the bells and whistles that come with Class V2. The code has commented remarks to help get you started and tell you what is optional, what is not, and what to do if you don't want user-defined values. Also, Rectangle has been updated with the V2 layout, so check it out as a model.
I used bmcclure's layout from their IniFile code to format Node.ahk. I really like how it was neat and organized.
New in this release:
1) static values - via %ClassName%_constant. This function is an optional function that allows storing static values (shared by all members of the class). You set the values inside the function (via static values), and extract them using a function call.
Syntax:
| Code: | | Node_constant(variable, setValue = "") |
Returns the value associated with the specified variable (or the empty string, if the variable isn't one of the variables stored). Passing a value for setValue will set the value associated with the specified variable (returning the previous). There is no way to "clear" (store the empty string) in the internal variables.
Also, there is a Class function which can be used to dynamically retrieve these constants.
Syntax:
| Code: | | Class_constant(ClassNameOrObject, variable, setValue = "") |
If ClassNameOrObject is an integer, it is interpreted as a class object (whose class name is retieved and used). Else, ClassNameOrObject is interpreted as a class name. The class' constant function is dynamically called using the class name. If the function doesn't exist, the empty string is returned. As a note, this is NOT added as a Class Option. This means that the class' "constant" function is reserved for this use. Declaring a "constant" function for another use may have negative side-effects. I could have it as a class option (if required), but for performance reasons, I opted against this. I'll fix it if yal want it to be a class option.
2) Functions that are used to store the structure (types) of the built-in values and another for user-defined values. These functions are dynamically called when destroying and cloning the object. This is part of the new V2 design. See Node.ahk for use and Rectangle/Rectangle_test.ahk to see how to use it with user-defined values. Since the call is dynamic, if there are no user-defined values, then nothing needs to be done. Also, you don't need to change the destroy function or clone function when you change your structure. Only change the functions getBuiltInValues() and getUserDefinedValues() to reflect the changes.
3) Ability to clone objects. All V2 objects are setup to be cloneable by default. This new structure makes it very easy to clone and destroy objects (along with user-defined values)
There is a three-prong clone method.
Syntax:
| Code: | | Node_clone(NodeObject, DeepCopy = true) |
If DeepCopy = 0 (or the empty string), a shallow copy is done; if DeepCopy = -1, a "settings only" copy is done; else, a deep copy is done (default)
All three clone built-in types (string, int, uint, etc.). The difference is what it does for "obj" types (including wrappers). A shallow copy copies the address directly. A deep copy performs a recursive clone (dynamically cloning objects). A "settings only" clone does not set an "obj" type (leaves it 0). It is up to the Class' clone function to decide what to do. Node.ahk is formatted to allow easily doing this.
As a note, Vector, Array, and the wrappers are cloneable. Wrappers will clone the exact same way regardless the type of clone (DeepCopy is a parameter for consistency only). Vector and Array only have Deep copy added. A "settings only" copy doesn't apply, and I'm not sure what should be done for a shallow copy, so it is not yet added yet (taking suggestions).
4) Int64 and UInt64 wrappers make their debut. UInt64 is supported in v1.0.48+ , see NumGet/NumPut for details. You can also use Int64 or UInt64 as a value's type.
As always, any questions can be asked on the forum. _________________ As always, if you have any further questions, don't hesitate to ask.
Add OOP to your scripts via the Class Library. Check out my scripts. |
|
| Back to top |
|
 |
bmcclure
Joined: 24 Nov 2007 Posts: 766
|
Posted: Thu Mar 05, 2009 11:38 pm Post subject: |
|
|
Well, it certainly puts a smile on my face
Now, to start converting my classes to utilize the new features! _________________ Ben
My Trac projects
My Wiki
[Broken] - My music |
|
| Back to top |
|
 |
bmcclure
Joined: 24 Nov 2007 Posts: 766
|
Posted: Fri Mar 06, 2009 12:01 am Post subject: |
|
|
Note, you did include a (Constants) option in the Node.ahk template file. Is that something left in from testing, or just there in case it is turned into a class option? _________________ Ben
My Trac projects
My Wiki
[Broken] - My music |
|
| Back to top |
|
 |
|
|
You can post new topics in this forum You can reply to topics in this forum
|
Powered by phpBB © 2001, 2005 phpBB Group
|