Recent

Author Topic: About compiler error messages...  (Read 1854 times)

440bx

  • Hero Member
  • *****
  • Posts: 2000
About compiler error messages...
« on: July 05, 2020, 03:51:52 am »
Hello,

Simple question: are the FPC developers interested in correcting error messages that dangerously border on being incorrect ?

For instance, consider this program:
Code: Pascal  [Select][+][-]
  1. program TestErrorMessage;
  2.  
  3. begin
  4.   while 1 > 2    do        else
  5. end.
  6.  

FPC emits the following error:
Code: Text  [Select][+][-]
  1. Compiling junk.lpr
  2. junk.lpr(4,28) Warning: unreachable code
  3. junk.lpr(4,28) Fatal: Syntax error, ";" expected but "ELSE" found
  4. Fatal: Compilation aborted
which is not right since placing a semicolon between the "do" and the "else" does not yield a syntactically valid statement.

The correct error message should state that the expected keyword in that first case is "end", not semicolon ";" as Delphi does:
Code: Text  [Select][+][-]
  1. Delphi for Win32  Version 9.0  Copyright (c) 1983,96 Borland International
  2. junk.lpr(4) Hint: FOR or WHILE loop executes zero times - deleted
  3. junk.lpr(4) Error: 'END' expected but 'ELSE' found
  4. junk.lpr(19)
which clearly indicates that the "else" simply doesn't belong there since it is followed by the expected keyword ("end" in this case.)

Obviously, a very minor thing but, it would be nice to see correct error messages whenever possible.



« Last Edit: July 05, 2020, 03:54:25 am by 440bx »
FPC v3.0.4 and Lazarus 1.8.2 on Windows 7 64bit.

ccrause

  • Sr. Member
  • ****
  • Posts: 303
Re: About compiler error messages...
« Reply #1 on: July 05, 2020, 11:00:21 am »
IMHO it is a matter of interpretation.  My interpretation is that Delphi's suggestion does not make sense.  Trying to follow Delphi's suggestion to fix the code:
Code: Pascal  [Select][+][-]
  1. program TestErrorMessage;
  2.  begin
  3.   while 1 > 2    do        end
  4. end.
This is not valid code either. So my interpretation of Delphi's suggestion is different from your interpretation which (I assume) is that the else is superfluous.

Where I do agree is that FPC's message is not that helpful either:
Code: Pascal  [Select][+][-]
  1. program TestErrorMessage;
  2.  begin
  3.   while 1 > 2    do        ; else
  4. end.
This still gives the error
Quote
project1.lpr(4,20) Fatal: Syntax error, ";" expected but "ELSE" found

My view of the original situation is that the parser is expecting a statement after do , but encounters a reserved word.  It seems that the presence of else outside a valid construct is resulting in this kind of error message in FPC. Following this line of thinking leads me to suggestion the following error message: "Statement expected but reserved word "ELSE" encountered".  So if one substitutes "empty statement" for ";" then FPC's error message makes sense (at least according to my interpretation).  But refering to ";" as substitute for "statement" is not generally valid since ";" is a statement separator in Pascal.

Thaddy

  • Hero Member
  • *****
  • Posts: 10489
Re: About compiler error messages...
« Reply #2 on: July 05, 2020, 11:12:40 am »
In my opinion FPC is more clear than Delphi. And correct.
The else is dangling and the while do needs a terminating ;
Where did Delphi get that end from? that is really wrong.
When you ask a question that is actually answered in the documentation, you are either lazy or a moron.

marcov

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 8734
  • FPC developer.
Re: About compiler error messages...
« Reply #3 on: July 05, 2020, 12:03:44 pm »


Simple question: are the FPC developers interested in correcting error messages that dangerously border on being incorrect ?
 
junk.lpr(4,28) Fatal: Syntax error, ";" expected but "ELSE" found
Fatal: Compilation aborted[/code]
which is not right since placing a semicolon between the "do" and the "else" does not yield a syntactically valid statement.

Delphi:
junk.lpr(4) Error: 'END' expected but 'ELSE' found
junk.lpr(19)which clearly indicates that the "else" simply doesn't belong there since it is followed by the expected keyword ("end" in this case.)

Both seem to indicate that the ELSE is out of place, IMHO they are equivalent, just focussing on a different expected production.

After a do, possible productions are (begin..end block) or  (statement)

Statements are separated by ; but can be empty, so

Code: [Select]
  procedure yy;
  begin
    while true>false do 
  end;

is correct but

Code: [Select]
  procedure yy;
  begin
    while true>false do  ;
   
  end;

is also correct.

So if you are pedantic, the error message should be  "begin",";" or "end" expected. If it is a main block, you could change the "end" to "end." even.

But I really wonder what that would change other that increased frustration among compiler message translators that have to translate all those extra messages.

added later: hmm, other keywords like REPEAT, FOR and WHILE are also possible, so to be complete that would be a real long list.

I agree somewhat Thaddy in that it is IMHO more logical that statements are expected to follow than that the end of the procedure is expected. So I would prefer FPC's choice over Delphi's.
« Last Edit: July 05, 2020, 12:06:35 pm by marcov »

440bx

  • Hero Member
  • *****
  • Posts: 2000
Re: About compiler error messages...
« Reply #4 on: July 05, 2020, 02:02:26 pm »
The compiler cannot state that it expects a semicolon when it is sitting on an "else" keyword because the semicolon is a statement separator and "else" is not a statement and the compiler knows that.  Therefore, semicolon is out, no can be.

The other possibility is "else" but, it can't be "else" (it's already there and the compiler knows it's wrong) because the "while" statement isn't in an "if" statement and, the compiler knows that too.  Therefore, it cannot be "else".

The only possibility left, which is the correct one, is "end".

After adding an "end" (TEST_B) the compiler will complain about it not being followed by a period ".", after adding the period (per error message - TEST_C), the program is correct.

Following Delphi's error message instructions yields a correct program.  Following FPC's error message instructions does _not_ lead to a correct program.

For fun, here is a little bit of code to play with:(NOTE: other than commenting and uncommenting the $DEFINE lines, _no_ other editing is required because the compiler should ignore the text after "end."
Code: Pascal  [Select][+][-]
  1. {$APPTYPE CONSOLE}
  2.  
  3.  
  4. { uncomment these defines below to perform each test  }
  5.  
  6. //{$DEFINE TEST_A}
  7. //{$DEFINE TEST_B}
  8. //{$DEFINE TEST_C}
  9.  
  10. {$IFDEF TEST_A}
  11. program TestWhileFOLLOW;
  12.  
  13. begin
  14.   while 1 > 2    do        else
  15. end.
  16.  
  17. output messages:
  18.  
  19. a. From Delphi
  20.  
  21. Delphi for Win32  Version 9.0  Copyright (c) 1983,96 Borland International
  22. TestWhileFOLLOW.lpr(11) Hint: FOR or WHILE loop executes zero times - deleted
  23. TestWhileFOLLOW.lpr(11) Error: 'END' expected but 'ELSE' found
  24. TestWhileFOLLOW.lpr(14)
  25.  
  26. b. From FPC
  27.  
  28.    this error message does _not_ lead to a syntactically correct program
  29.  
  30. Free Pascal Compiler version 3.0.4 [2017/10/06] for x86_64
  31. Copyright (c) 1993-2017 by Florian Klaempfl and others
  32. Target OS: Win64 for x64
  33. Compiling TestWhileFOLLOW.lpr
  34. TestWhileFOLLOW.lpr(11,28) Warning: unreachable code
  35. TestWhileFOLLOW.lpr(11,28) Fatal: Syntax error, ";" expected but "ELSE" found
  36. Fatal: Compilation aborted
  37. {$ENDIF}
  38.  
  39.  
  40. { add "end" as suggested by Delphi }
  41.  
  42.  
  43. {$IFDEF TEST_B}
  44. program TestWhileFOLLOW;
  45.  
  46. begin
  47.   while 1 > 2    do        end     else
  48. end.
  49.  
  50. output messages:
  51.  
  52. a. From Delphi
  53.  
  54. Delphi for Win32  Version 9.0  Copyright (c) 1983,96 Borland International
  55. TestWhileFOLLOW.lpr(41) Hint: FOR or WHILE loop executes zero times - deleted
  56. TestWhileFOLLOW.lpr(41) Error: '.' expected but 'ELSE' found
  57. TestWhileFOLLOW.lpr(49)
  58.  
  59. b. From FPC
  60.  
  61.    it gets this one right
  62.  
  63. Free Pascal Compiler version 3.0.4 [2017/10/06] for x86_64
  64. Copyright (c) 1993-2017 by Florian Klaempfl and others
  65. Target OS: Win64 for x64
  66. Compiling TestWhileFOLLOW.lpr
  67. TestWhileFOLLOW.lpr(41,28) Warning: unreachable code
  68. TestWhileFOLLOW.lpr(41,36) Fatal: Syntax error, "." expected but "ELSE" found
  69. Fatal: Compilation aborted
  70. {$ENDIF}
  71.  
  72.  
  73. {$IFDEF TEST_C}
  74. program TestWhileFOLLOW;
  75.  
  76. begin
  77.   while 1 > 2    do        end.     else
  78. end.
  79.  
  80. output messages:
  81.  
  82. a. From Delphi
  83.  
  84. Delphi for Win32  Version 9.0  Copyright (c) 1983,96 Borland International
  85. TestWhileFOLLOW.lpr(77) Hint: FOR or WHILE loop executes zero times - deleted
  86. TestWhileFOLLOW.lpr(77) Warning: Text after final 'END.' - ignored by compiler
  87. TestWhileFOLLOW.lpr(108)
  88. 109 lines, 0.02 seconds, 2992 bytes code, 1224 bytes data.
  89.  
  90. b. From FPC
  91.  
  92.    sort of gets it right and only because of the error message instruction from
  93.    Delphi were followed otherwise it would not lead to a correct program.
  94.  
  95.    Another interesting thing: it created an executable but because it is unhappy
  96.    about not finding the $ENDIF, it did not output the number of lines compiled,
  97.    time spent and the size of the code and data.
  98.  
  99. Free Pascal Compiler version 3.0.4 [2017/10/06] for x86_64
  100. Copyright (c) 1993-2017 by Florian Klaempfl and others
  101. Target OS: Win64 for x64
  102. Compiling TestWhileFOLLOW.lpr
  103. TestWhileFOLLOW.lpr(77,28) Warning: unreachable code
  104. Linking TestWhileFOLLOW.exe
  105. TestWhileFOLLOW.lpr(77,28) Error: $ENDIF expected for $IFDEF TEST_C defined in TestWhileFOLLOW.lpr line 73
  106.  
  107. vvvvvvvv $ENDIF is right there!
  108. {$ENDIF}
  109.  

FPC v3.0.4 and Lazarus 1.8.2 on Windows 7 64bit.

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 6631
  • Debugger - SynEdit - and more
    • wiki
Re: About compiler error messages...
« Reply #5 on: July 05, 2020, 03:20:19 pm »
Both "end" and ";" would fix the "while".

Both leave an error after it:
You say that there can not be a ";", because after the ";" there must be a statement (and "else" is not).

But after an "end" there must be valid code too. And "else" is not valid there either.
(or after "end.", there must be no code, and "else" is not)

So expecting "end" leads to all the same issues.
And statistically a ";" is more likely.


Also
   "while foo do else"
seems to be as
   "while foo do  <empty statement> [error after statement] else".
If there was no empty statement, then a statement would need to be expected.

So *if* the error message needs any change, then one could argue it should say "statement expected" (changing the compiler to assume there was no empty statement).
Or it could ask for: "statement, end or ; expected".

ccrause

  • Sr. Member
  • ****
  • Posts: 303
Re: About compiler error messages...
« Reply #6 on: July 05, 2020, 03:25:07 pm »
...
The only possibility left, which is the correct one, is "end".

After adding an "end" (TEST_B) the compiler will complain about it not being followed by a period ".", after adding the period (per error message - TEST_C), the program is correct.
This is your opinion, not fact.  Solution C relegates the original else to text that gets ignored after the end of the program.  Why is it any less correct to assume the else is out of place up front, and get the user to fix it.

Looking at the parser logic the following high level processing happens:
1. after the DO token, parse a statement block.
2. if no statement is found, it is per definition empty (OK, this bit is my interpretation to digest the parser logic).
3. an empty statement should be followed either by the end of a statement block (end; or end. or finalization), if not, more statements are expected, in which case a statement separator is expected.

So the compiler's error message makes sense in the context of the parser.  So either the compilers reports the final failure it encounters (which then depends on the order of the various checks) or a more sophisticated error needs to be generated at a higher level in the parser to indicate all the possible causes of an error.

440bx

  • Hero Member
  • *****
  • Posts: 2000
Re: About compiler error messages...
« Reply #7 on: July 05, 2020, 03:34:36 pm »
Everything you say is reasonable... but...

Both "end" and ";" would fix the "while".
the semicolon is incorrect.  I will explain, in detail why, below.

Both leave an error after it:
You say that there can not be a ";", because after the ";" there must be a statement (and "else" is not).
true but, putting a semicolon will _not_ lead to a correct program.  Putting "end" will.  That's important.

The reason why "end" is the only correct symbol is because, the compiler is parsing a compound statement, i.e, "begin"/"end".  If the compiler finds a keyword, it must be either, a keyword that starts a new statement or "end" to match the compound statement's "begin".

Therefore, "else" not being a keyword that starts a new statement, the only valid keyword at that location is the one that completes the compound statement which is, "end".

The production for a compound statement is: _CompoundStatement      = "begin" _Statement { ";" _Statement } "end" therefore, in the absence of another statement, that production should be requesting an "end".



This is your opinion, not fact.
That's my opinion like 2 + 2 = 4 is my "opinion" too.  I hope the explanation about the compound statement production above is sufficient to demonstrate that.

HTH.


FPC v3.0.4 and Lazarus 1.8.2 on Windows 7 64bit.

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 6631
  • Debugger - SynEdit - and more
    • wiki
Re: About compiler error messages...
« Reply #8 on: July 05, 2020, 04:10:19 pm »
Quote
If the compiler finds a keyword, it must be either, a keyword that starts a new statement or "end" to match the compound statement's "begin".
Well yes, we all agree that "else" should give an error, because it can not occur there.

But how do we get
- From: The compiler found an incorrect keyword
- To: Therefore it must expect a keyword (just a different keyword)?

If the keyword "else" is wrong, we can conclude something else must be expected. But that something else can be anything: A new none-empty statement, an empty statement followed by a ";", an "end".



Following your argument, that the compiler should treat an unexpected keyword as a special error, then it should simply state
"incorrect keyword"

In the same way as
Code: Pascal  [Select][+][-]
  1.   while true do 'abc'
gives: "Error: Illegal expression"

and
Code: Pascal  [Select][+][-]
  1.   while true do XX
gives: "Error: Identifier not found"

Btw, many other keywords give "illegal expression".
And that would be fine for the "else" too.

Yet, point in your favour:
Code: Pascal  [Select][+][-]
  1. repeat
  2.   while true do else
  3.  
gives: Fatal: Syntax error, "UNTIL" expected but "ELSE" found
So it uses the knowledge about the outer block.

Personally, I would like to see the "; expected" error in the last case too. Just for consistency. (As both errors are equally good)



Of course, one has to ask why change the error at all.
Purely technically it is correct. (It is not complete, but it is one correct possibility, and neither would "end expected" be complete).

The reasons to change an error message are:
1- It is plain wrong (but this is a subset of the next point)
2- The message can be improved to give the user a better idea what is wrong.

The first does not apply.

As for the second, "; expected" or "end expected" are both equally helpful.
Given that ";" is statistically more likely, the "; expected" error is actually ever so slightly more helpful.





marcov

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 8734
  • FPC developer.
Re: About compiler error messages...
« Reply #9 on: July 05, 2020, 04:34:05 pm »
After adding an "end" (TEST_B) the compiler will complain about it not being followed by a period ".", after adding the period (per error message - TEST_C), the program is correct.


Following Delphi's error message instructions yields a correct program.  Following FPC's error message instructions does _not_ lead to a correct program.

Yes. And in neither case it was meant to simply substitute. It is basically a summary of the state of the parser, not a correction advise. 

Example: Delphi gives the exact same error for this code:

Code: [Select]
  procedure yy;
  begin
    while true>false do  else

  end;

And here substuting ELSE with  "end" is not a solution, IOW the Delphi message is not meant as a "insert this" solution either. As said before, the substitute for else is more likely to be something else then END than that it is END, so I think the Delphi solution is inferior.

440bx

  • Hero Member
  • *****
  • Posts: 2000
Re: About compiler error messages...
« Reply #10 on: July 05, 2020, 05:04:07 pm »
But how do we get
- From: The compiler found an incorrect keyword
- To: Therefore it must expect a keyword (just a different keyword)?
I didn't say it  must expect a keyword.  What I said is, if it finds a keyword that does not start a new statement, as in the case with "else", then the keyword must be "end".  The reason for that is because the compiler is parsing a compound statement, therefore, it must expect a keyword to terminate the compound statement and, the _only_ keyword that does not start another statement that fulfills the requirement is "end". 

If the keyword "else" is wrong, we can conclude something else must be expected. But that something else can be anything: A new none-empty statement, an empty statement followed by a ";", an "end".
No, it cannot be anything.  What the compiler (Delphi) is saying, if you want a keyword that does not start another statement (as is the case with "else") there then it _must_ be "end". There is no other choice.

FPC stating that it expected a semicolon, in addition to being incorrect, it is also utterly useless because semicolons separate empty statements.  The compiler could ask for 200 semicolons there and achieve just as much.  It's asking for a semicolon is the same (in that case) as asking for a space characters.  Utterly useless.

Following your argument, that the compiler should treat an unexpected keyword as a special error, then it should simply state
"incorrect keyword"
It's not a special error.  A compound statement is terminated with "end".  There is no reason for the compiler to go "generic" and state "incorrect keyword" when it knows that the only acceptable keyword that does not start another statement is "end".    Since "else" does not start another statement then the only acceptable keyword that does not start another statement (as "else" is) is "end".


Yet, point in your favour:
Code: Pascal  [Select][+][-]
  1. repeat
  2.   while true do else
  3.  
gives: Fatal: Syntax error, "UNTIL" expected but "ELSE" found
So it uses the knowledge about the outer block.
It's not so much that it uses knowledge of the outer block, once the "while" is parsed, execution returns to the "repeat" production to finish parsing it, at that time, it finds an "else".  The only keyword that does not start another statement that "repeat" is going to accept is "until".  Since, "else" does not start another statement, the production demands an "until" keyword, as it should.

Personally, I would like to see the "; expected" error in the last case too. Just for consistency. (As both errors are equally good)
It really shouldn't do that because, at some point the "repeat" production has to ask/demand the "until" it needs.  It can't just go asking for another semicolon every time it needs an "until".

Of course, one has to ask why change the error at all.
Purely technically it is correct. (It is not complete, but it is one correct possibility, and neither would "end expected" be complete).
No, technically, it is incorrect.  That's the problem, a compound statement is not ended with a semicolon, it's ended with an "end" keyword.  It can't keep asking for semicolons endlessly.  It really has to ask for the "end" and it has to ask for it whenever it  see a symbol that does not start a new statement, whatever it may be.

The reasons to change an error message are:
1- It is plain wrong (but this is a subset of the next point)
2- The message can be improved to give the user a better idea what is wrong.

The first does not apply.
On the contrary, the first applies in full.  The compound statement is not asking for its terminating symbol, that's incorrect/wrong/bad/no good/trump.


Given that ";" is statistically more likely, the "; expected" error is actually ever so slightly more helpful.
Not if the programmer wants to eventually have a program that compiles because all the semicolons in the universe put before the else aren't going to do it.



It is basically a summary of the state of the parser, not a correction advise. 
Error messages are not correction advise ? ... if doing what they state isn't going to lead to a correct statement then why does the compiler bother emitting them ?...  ascii visual entertainment ?...

Example: Delphi gives the exact same error for this code:
But, here is the thing you are attempting to avoid.  If you follow Delphi's corrective "suggestions" (not advice, of course) you will eventually end up with a program that compiles.

Attempting to use FPC's "ascii visual entertainment" yields a request for more and more semicolons and never a program that compiles successfully.

That applies to the example you showed.  I used your example.  Followed Delphi's "suggestions" and after a few compiles had a syntactically correct program.  If I were still following FPC's suggestion (not advice!) this entire post would be semicolons and the program would still not compile (and not ever compile.)

I think the Delphi solution is inferior.
Interesting that the solution that leads to a syntactically correct program is "inferior" while the "solution" that has the programmer adding semicolons for infinity is not inferior.

Anyway, I just wanted to know if there was interest in having correct error message.  I have the answer and, have no doubt about what it is.


« Last Edit: July 05, 2020, 05:06:38 pm by 440bx »
FPC v3.0.4 and Lazarus 1.8.2 on Windows 7 64bit.

marcov

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 8734
  • FPC developer.
Re: About compiler error messages...
« Reply #11 on: July 05, 2020, 05:25:50 pm »
Example: Delphi gives the exact same error for this code:
But, here is the thing you are attempting to avoid.  If you follow Delphi's corrective "suggestions" (not advice, of course) you will eventually end up with a program that compiles.

Nope. There is no way to place END in that source code to make it compile. That it happens to be for your original code is coincidence. Again: fix this by adding end or replacing else by end;

Code: Pascal  [Select][+][-]
  1. procedure yy;
  2.   begin
  3.     while true>false do  else
  4.   end;
  5.  

In case the end isn't far enough, and you get confused again, use this code:

Code: Pascal  [Select][+][-]
  1. procedure yy;
  2.   begin
  3.     while true>false do  else
  4.     flup;
  5.   end;
  6.  

to make sure you don't accidentally reach the end of the procedure again.
« Last Edit: July 05, 2020, 05:29:18 pm by marcov »

440bx

  • Hero Member
  • *****
  • Posts: 2000
Re: About compiler error messages...
« Reply #12 on: July 05, 2020, 05:50:57 pm »
if you follow the instructions given by the compiler (Delphi), you end up with this (which compiles!) :
Code: Pascal  [Select][+][-]
  1. program TestWhileFOLLOW2;
  2.  
  3. procedure yy;
  4. begin
  5.   while true > false do  end;
  6.   //flup;
  7. end.
  8.  
  9. begin
  10. end.
  11.  
I did have to add a "program" clause.  I think you should be ok with that.
FPC v3.0.4 and Lazarus 1.8.2 on Windows 7 64bit.

marcov

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 8734
  • FPC developer.
Re: About compiler error messages...
« Reply #13 on: July 05, 2020, 06:21:35 pm »
if you follow the instructions given by the compiler (Delphi), you end up with this (which compiles!) :
Code: Pascal  [Select][+][-]
  1. program TestWhileFOLLOW2;
  2.  
  3. procedure yy;
  4. begin
  5.   while true > false do  end;
  6.   //flup;
  7. end.
  8.  

Suddenly, that ; has become a ".".  So you just wrangled things till it compile, (and falsely, potentially omitting umpteen functions defined after this one). And way  more than what Delphi says. With a ";", it doesn't compile if you replace else by "end;"

And yes, of course i had a begin..end. further down, that was just left out for clarity.
« Last Edit: July 05, 2020, 09:53:16 pm by marcov »

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 6631
  • Debugger - SynEdit - and more
    • wiki
Re: About compiler error messages...
« Reply #14 on: July 05, 2020, 08:05:38 pm »
But how do we get
- From: The compiler found an incorrect keyword
- To: Therefore it must expect a keyword (just a different keyword)?
I didn't say it  must expect a keyword.  What I said is, if it finds a keyword that does not start a new statement, as in the case with "else", then the keyword must be "end".  The reason for that is because the compiler is parsing a compound statement, therefore, it must expect a keyword to terminate the compound statement and, the _only_ keyword that does not start another statement that fulfills the requirement is "end". 

Ignoring the rest of your reply. Makes no sense to talk about any other point, as long as you completely missed the point on this.

You say it yourself: "I didn't say it  must expect a keyword."

As for: "if it finds a keyword that does not start a new statement, "...." then the keyword must be "end""
The entire point is, why must it be replaced/insertatian by a keyword?????
It does (and you said so) not have to expect one in first.
It finds an error (that happens to be a keyword, but that might well be a misspelled function name "Esel" (German word)).

The random fact that the token causing an error happens to be a keyword, does not in any way implicate that the correct token (the token to be expected) actually has to be a keyword.
So the expected token can very well be a ";".


 

TinyPortal © 2005-2018