Lazarus

Free Pascal => General => Topic started by: MarkMLl on September 29, 2022, 12:06:47 pm

Title: Pascal-style case and C-style break
Post by: MarkMLl on September 29, 2022, 12:06:47 pm
In a number of circumstances C's ability to fall-through to the next case in a switch statement is useful, and there is the occasional newcomer to Pascal who complains that it is unsupported.

Pascal does, obviously, have the break statement which can be used in a loop context, but I found myself wondering whether it had a convenient antithesis: it does, of course, which is constrained use of a goto.

I have the code below in a procedure which is interpreting a message received via some form of serial connection.

Code: Pascal  [Select][+][-]
  1. function TSerialThread.recvMessage(serHandle: TSerialHandle; var buffer: string;
  2.                                 timeout, limit: integer): boolean;
  3. label
  4.   handleBcc;
  5.  
  6. const
  7.   obeyTimeout= true;                    (* Normally true                        *)
  8.  
  9. type
  10.   tRecvState= (expectSOH, expectAddress, expectSeq, expectCtrl, expectPayload,
  11.                                 expectHandshake, expectOptionalEtx, expectBcc,
  12.                                                                     completed);
  13. ...
  14.  
  15.         expectOptionalEtx: if ch = ETX then begin
  16.                              buffer += ch;
  17.                              bccCount := BccLength;
  18.                              recvState := expectBcc (* To process next character *)
  19.                            end else begin
  20.                              bccCount := BccLength;
  21.                              recvState := expectBcc;
  22. { Works }              goto handleBcc     (* To process current character *)
  23. { Fails } //                goto expectBcc     (* To process current character *)
  24.                            end;
  25.         expectBcc:     begin
  26. handleBcc:
  27.                          if not (ch in ['!'..'~']) then
  28.                            break;
  29.                          buffer += ch;
  30.                          bccCount -= 1;
  31.                          if bccCount = 0 then
  32.                            recvState := completed
  33.                        end
  34.       otherwise
  35.         break                           (* Quite simply can't happen            *)
  36.       end
  37.   until obeyTimeout and Elapsed(limitTD, limit);
  38.  

Using a goto is entirely natural in that context, and in my opinion it is difficult to criticise since it is actually /inhibiting/ the transfer of control to some distant part of the program (Dijkstra himself complained about religious application of his "considered harmful" suggestion).

However, it does turn out to be necessary to declare a distinct label, simply using one of the label-like lines as the destination results in an "identifier isn't a label" error.

Would it be feasible for the compiler to allow a goto to target a (numeric) destination in this situation?

MarkMLl
Title: Re: Pascal-style case and C-style break
Post by: Zvoni on September 29, 2022, 12:43:50 pm
@Fallthrough:
Huh? Just use consecutive If-Then with an Else-Clause for the last if
If you need to exit the "switch" prematurely move the whole thing to a Function/Procedure
@Goto in your sample:
plain and simple: i avoid Goto like the plague.
In a case like yours i'd rather use a (nested) Function/Procedure instead of Goto SomeLabel
Title: Re: Pascal-style case and C-style break
Post by: MarkMLl on September 29, 2022, 12:49:37 pm
Looking through code, I've not used goto /except/ in this context in about 20 years. I'm considering whether it would be possible to eliminate it- or more specifically the label declaration- from the language.

MarkMLl
Title: Re: Pascal-style case and C-style break
Post by: 440bx on September 29, 2022, 01:04:35 pm
I've needed the C switch fallthrough feature a number of times and, since "goto"s are unequivocally unacceptable to me, I found a solution that implements the fallthrough C switch using a Pascal case statement without any need for a separate function and or procedure, no goto(s) either of course and, all inline as it should be (and as it is in C/C++)

It's not quite as CPU efficient as the C switch but, for the extremely slight loss of performance, the construct is even more powerful and flexible than the C switch.   Jumps over C switches in a single case statement. :)

Say NO to "goto"
Title: Re: Pascal-style case and C-style break
Post by: MarkMLl on September 29, 2022, 01:19:20 pm
since "goto"s are unequivocally unacceptable to me,

Why? I quote Dijkstra on this:

Quote
"Please don't fall into the trap of believing that I am terribly dogmatical about [the goto statement]. I have the uncomfortable feeling that others are making a religion out of it, as if the conceptual problems of programming could be solved by a single trick, by a simple form of coding discipline!"

For the specific situation where it is shifting between the states of a state machine without soliciting further input I thing it's the lesser of the several possible evils.

MarkMLl
Title: Re: Pascal-style case and C-style break
Post by: 440bx on September 29, 2022, 01:28:24 pm
Why?
Because the target destination of a goto can be anywhere in the function/procedure.  IOW, its destination needs to be determined by searching for the target label (which can be anywhere in the function/procedure, not necessarily in the case statement, that's a problem.)  When a programmer reads a "goto xyz" statement, it provides no information whatsoever as to where the destination "xyz" is.

Additionally, a goto, requires defining labels and manually keeping the labels "in synch" with the cases.  That's makes maintenance more difficult and error prone, not to mention that declaring a bunch of labels is tedious.  Pascal has enough deterministic control flow features to make the "goto" statement completely unnecessary, therefore a deficient solution.
Title: Re: Pascal-style case and C-style break
Post by: MarkMLl on September 29, 2022, 01:53:55 pm
Because the target destination of a goto can be anywhere in the function/procedure.  IOW, its destination needs to be determined by searching for the target label (which can be anywhere in the function/procedure, not necessarily in the case statement, that's a problem.)  When a programmer reads a "goto xyz" statement, it provides no information whatsoever as to where the destination "xyz" is.

Additionally, a goto, requires defining labels and manually keeping the labels "in synch" with the cases.  That's makes maintenance more difficult and error prone, not to mention that declaring a bunch of labels is tedious.  Pascal has enough deterministic control flow features to make the "goto" statement completely unnecessary, therefore a deficient solution.

Both of which I propose removing.

MarkMLl
Title: Re: Pascal-style case and C-style break
Post by: PascalDragon on September 29, 2022, 01:59:41 pm
Would it be feasible for the compiler to allow a goto to target a (numeric) destination in this situation?

The goto statement can already target numeric destinations, however these need to be declared as a label nevertheless and that will stay, because Pascal is a language where things need to be declared before use.
Also the branches of a case-statement are not labels, because they can be ranges as well and thus even if non-declared labels could be used you still can't target the branches of a case-statement with that.
Title: Re: Pascal-style case and C-style break
Post by: Warfley on September 29, 2022, 01:59:50 pm
C was developed for system development, and all the decisions in C must be viewed through this lens. The C switch-case statement is not an if-then-else replacement like in Pascal, it is a jumping table. This is why the case statement can only have a single value, and why you have the break. When entering a switch statement, it jumps to the case with the respective value and continues from there.

When doing low level programming, jumping tables are essential, therefore this makes completely sense, and also in other development it can still be quite useful. For example when I worked on an emulator, that had many instructions with very similar effects, e.g. write to memory and increment and write to memory. In C this is simply:
Code: C  [Select][+][-]
  1. switch (instr) {
  2. ...
  3. case INC_STORE: reg++;
  4. case STORE:
  5.   memory[addr] = reg;
  6.   break;
  7. ...
  8. }
In Pascal it either requires nested ifs or goto.

But the more high level you get, the less you need such constructs, so on a high level language (what Pascal most likely tries to be), I don't see the need for it. Especially as it is very confusing for many beginners, who learn C's switch case statements as "alternative to if-then-else" (which to be clear, it isn't and was never intendet to be, it's a jumping table, nothing more nothing less), and I had more than once to answer the question "My switch statement is broken".
Because for some reason this jumping table concept somehow made it into languages like Java or C#, where you never need a jumping table
Title: Re: Pascal-style case and C-style break
Post by: 440bx on September 29, 2022, 02:04:55 pm
Both of which I propose removing.
I am unclear as to what things you are proposing removing.  Can you be explicit on what they are ?



In Pascal it either requires nested ifs or goto.
It can be done in FPC/Delphi without if(s) and without goto(s) but, "lesser" forms of Pascal may require either or both.
Title: Re: Pascal-style case and C-style break
Post by: MarkMLl on September 29, 2022, 02:18:43 pm
When doing low level programming, jumping tables are essential,

Yes, and I have seen an ALGOL compiler which had several related variants (jump tables, format tables, substitution tables etc.) all of which had to be declared at the head of the program. To call it painful in an era where one can't just order a few extra printouts and spread them on a big desk is an understatement.

MarkMLl
Title: Re: Pascal-style case and C-style break
Post by: jamie on September 29, 2022, 10:54:56 pm
I have ~ two pages of a single CASE statement using a GOTO loopback

This process decodes a serial stream of digital data that can be incomplete with the current cached data and thus sets the GOTO loop to resume where it was.

 Also, this large CASE plays the role of a C style switch whereas it changes the CASE switch to process to the next in line switch and uses a GOTO back to the loop, again.

 If for some reason the current CASE index cannot be completed it just allows the case to exit as normal but keeps the current CASE index to pick up for the next time.

 This logic was very hard to process using IF statements and the like, it was much easier using a C style Case switch.
Title: Re: Pascal-style case and C-style break
Post by: Thaddy on September 29, 2022, 11:16:37 pm
jump tables, format tables, substitution tables
You mean Birthday parties, Christmas tables and finding eggs in the garden?
Title: Re: Pascal-style case and C-style break
Post by: MarkMLl on September 30, 2022, 08:58:22 am
I have ~ two pages of a single CASE statement using a GOTO loopback

This process decodes a serial stream of digital data that can be incomplete with the current cached data and thus sets the GOTO loop to resume where it was.

 Also, this large CASE plays the role of a C style switch whereas it changes the CASE switch to process to the next in line switch and uses a GOTO back to the loop, again.

 If for some reason the current CASE index cannot be completed it just allows the case to exit as normal but keeps the current CASE index to pick up for the next time.

 This logic was very hard to process using IF statements and the like, it was much easier using a C style Case switch.

Yes, very much my rationale. In effect goto is overloading the selector value and going back to the start of the case: very much like continue does with a while.

MarkMLl
Title: Re: Pascal-style case and C-style break
Post by: alpine on September 30, 2022, 11:03:13 am
I have ~ two pages of a single CASE statement using a GOTO loopback

This process decodes a serial stream of digital data that can be incomplete with the current cached data and thus sets the GOTO loop to resume where it was.

 Also, this large CASE plays the role of a C style switch whereas it changes the CASE switch to process to the next in line switch and uses a GOTO back to the loop, again.

 If for some reason the current CASE index cannot be completed it just allows the case to exit as normal but keeps the current CASE index to pick up for the next time.

 This logic was very hard to process using IF statements and the like, it was much easier using a C style Case switch.

Yes, very much my rationale. In effect goto is overloading the selector value and going back to the start of the case: very much like continue does with a while.

MarkMLl

BTW, I don't miss that particularity of the C-switch, besides one very special case of https://forum.lazarus.freepascal.org/index.php/topic,60218.msg450448.html#msg450448

With spending one more boolean:

Code: Pascal  [Select][+][-]
  1.     function TSerialThread.recvMessage(serHandle: TSerialHandle; var buffer: string;
  2.                                     timeout, limit: integer): boolean;
  3.     label
  4.       handleBcc;
  5.      
  6.     const
  7.       obeyTimeout= true;                    (* Normally true                        *)
  8.      
  9.     type
  10.       tRecvState= (expectSOH, expectAddress, expectSeq, expectCtrl, expectPayload,
  11.                                     expectHandshake, expectOptionalEtx, expectBcc,
  12.                                                                         completed);
  13.   done := false;
  14.   repeat case RecvState of  
  15.       ...
  16.      
  17.             expectOptionalEtx: if ch = ETX then begin
  18.                                  buffer += ch;
  19.                                  bccCount := BccLength;
  20.                                  recvState := expectBcc (* To process next character *)
  21.                                end else begin
  22.                                  bccCount := BccLength;
  23.                                  recvState := expectBcc;
  24.                                  continue;
  25.     { Works } //             goto handleBcc     (* To process current character *)
  26.     { Fails } //                goto expectBcc     (* To process current character *)
  27.                                end;
  28.             expectBcc:     begin
  29.     handleBcc:
  30.                              if not (ch in ['!'..'~']) then
  31.                                break;
  32.                              buffer += ch;
  33.                              bccCount -= 1;
  34.                              if bccCount = 0 then
  35.                                recvState := completed
  36.                            end
  37.           otherwise
  38.             break                           (* Quite simply can't happen            *)
  39.           end; done := true; until done;
  40.       until obeyTimeout and Elapsed(limitTD, limit);
  41.      

Or, as Zvoni wrote, in that particular case with an extra check:
Code: Pascal  [Select][+][-]
  1.      expectOptionalEtx, expectBcc:
  2.                              begin
  3.                                if (RecvState = expectOptionalEtx) then begin
  4. //           expectOptionalEtx: if ch = ETX then begin
  5.                                  bccCount := BccLength;
  6.                                  recvState := expectBcc;
  7.                                  if (ch = ETX) then begin
  8.                                    buffer += ch;
  9.                                    break;
  10.                                  end;
  11.                                end;
  12. //            expectBcc:     begin
  13.                              if not (ch in ['!'..'~']) then
  14.                                break;
  15.                              buffer += ch;
  16.                              bccCount -= 1;
  17.                              if bccCount = 0 then
  18.                                recvState := completed
  19.                            end
  20.           otherwise
  21. ...

Title: Re: Pascal-style case and C-style break
Post by: MarkMLl on September 30, 2022, 11:13:40 am
BTW, I don't miss that particularity of the C-switch, besides one very special case of https://forum.lazarus.freepascal.org/index.php/topic,60218.msg450448.html#msg450448

I don't miss it, since I'm only a C programmer rarely and under duress. However since dislike of the Pascal-style case statement has been a longstanding grumble among non-devotees, I found myself wondering what the opposite of (C-style) break would be since neither (Pascal-style) break nor continue can be used on account of their messing up a surrounding while or repeat, and at that point it occurred to me that certainly in my code this was about the only place that goto was being used.

MarkMLl
Title: Re: Pascal-style case and C-style break
Post by: BeniBela on September 30, 2022, 02:35:35 pm

The goto statement can already target numeric destinations, however these need to be declared as a label nevertheless and that will stay, because Pascal is a language where things need to be declared before use.

Perhaps he meant with numeric goto that you could address the label indirectly


Not this

Code: Pascal  [Select][+][-]
  1. procedure huh;
  2. label 0,1,2;
  3. begin
  4.   goto 1;
  5.   0: writeln('a');
  6.   1: writeln('b');
  7.   2: writeln('c');
  8. end;
  9.  

Not this

 

Code: Pascal  [Select][+][-]
  1. procedure huh;
  2. label 0,1,2;
  3. var
  4.   i: Integer;
  5. begin
  6.   i := 1;
  7.   case i of
  8.   0: writeln('a');
  9.   1: writeln('b');
  10.   2: writeln('c');
  11.   end;
  12. end;
  13.  

but this

Code: Pascal  [Select][+][-]
  1. procedure huh;
  2. label 0,1,2;
  3. var
  4.   i: Integer;
  5. begin
  6.   i := 1;
  7.   goto (i);
  8.   0: writeln('a');
  9.   1: writeln('b');
  10.   2: writeln('c');
  11. end;
  12.  
Title: Re: Pascal-style case and C-style break
Post by: Thaddy on September 30, 2022, 02:46:44 pm
Simply = without else....
Title: Re: Pascal-style case and C-style break
Post by: MarkMLl on September 30, 2022, 02:49:21 pm
Perhaps he meant with numeric goto that you could address the label indirectly

Yes, as in the example I posted when I started the thread. Also Sven makes the point that

Quote
...the branches of a case-statement are not labels, because they can be ranges as well and thus even if non-declared labels could be used you still can't target the branches of a case-statement with that.

while I was musing on eliminating labels in their entirety and only using goto as the antithesis of (C-style) break in the context of a (Pascal-style) case statement.

MarkMLl
Title: Re: Pascal-style case and C-style break
Post by: Zvoni on September 30, 2022, 02:59:20 pm
Or, as Zvoni wrote, in that particular case with an extra check:
Code: Pascal  [Select][+][-]
  1.      expectOptionalEtx, expectBcc:
  2.                              begin
  3.                                if (RecvState = expectOptionalEtx) then begin
  4. //           expectOptionalEtx: if ch = ETX then begin
  5.                                  bccCount := BccLength;
  6.                                  recvState := expectBcc;
  7.                                  if (ch = ETX) then begin
  8.                                    buffer += ch;
  9.                                    break;
  10.                                  end;
  11.                                end;
  12. //            expectBcc:     begin
  13.                              if not (ch in ['!'..'~']) then
  14.                                break;
  15.                              buffer += ch;
  16.                              bccCount -= 1;
  17.                              if bccCount = 0 then
  18.                                recvState := completed
  19.                            end
  20.           otherwise
  21. ...
My statement was more along the lines of
Code: Pascal  [Select][+][-]
  1. function TSerialThread.recvMessage(serHandle: TSerialHandle; var buffer: string;
  2.                                 timeout, limit: integer): boolean;
  3.  
  4. const
  5.   obeyTimeout= true;                    (* Normally true                        *)
  6.  
  7. type
  8.   tRecvState= (expectSOH, expectAddress, expectSeq, expectCtrl, expectPayload,
  9.                                 expectHandshake, expectOptionalEtx, expectBcc,
  10.                                                                     completed);
  11.         Function FuncExpectBCC(Const Ach:Char;var AbccCount:Integer;var ArecvState:tRecvState):Boolean;
  12.         Begin          
  13.                 Result:=False; 
  14.                 if not (Ach in ['!'..'~']) then
  15.                    Exit(True);
  16.                 buffer += ch;
  17.                 AbccCount -= 1;
  18.                 if AbccCount = 0 then
  19.                    ArecvState := completed;                      
  20.         End;
  21.  
  22. ........
  23.  
  24.         expectOptionalEtx: if ch = ETX then begin
  25.                              buffer += ch;
  26.                              bccCount := BccLength;
  27.                              recvState := expectBcc (* To process next character *)
  28.                            end else begin
  29.                              bccCount := BccLength;
  30.                              recvState := expectBcc;
  31.                              If FuncExpectBCC(ch,bccCount,recvState) Then Break;
  32.                            end;
  33.         expectBcc:     If FuncExpectBCC(ch,bccCount,recvState) Then Break
  34.       otherwise
  35.         break                           (* Quite simply can't happen            *)
  36.       end
  37.   until obeyTimeout and Elapsed(limitTD, limit);
  38.  
No Goto or labels or other such nonsense
Title: Re: Pascal-style case and C-style break
Post by: alpine on September 30, 2022, 03:29:56 pm
@Fallthrough:
Huh? Just use consecutive If-Then with an Else-Clause for the last if
If you need to exit the "switch" prematurely move the whole thing to a Function/Procedure
It is my misunderstanding, I thought you've meant to merge cases (the fall through case and the previous one) and then to use if-then to split them again in  the compound operator.
Title: Re: Pascal-style case and C-style break
Post by: PascalDragon on September 30, 2022, 03:30:54 pm

The goto statement can already target numeric destinations, however these need to be declared as a label nevertheless and that will stay, because Pascal is a language where things need to be declared before use.

Perhaps he meant with numeric goto that you could address the label indirectly

I'm aware of what he meant. Which is why I added the part about the branches of the case-statement.
Title: Re: Pascal-style case and C-style break
Post by: Zvoni on September 30, 2022, 04:54:12 pm
@Fallthrough:
Huh? Just use consecutive If-Then with an Else-Clause for the last if
If you need to exit the "switch" prematurely move the whole thing to a Function/Procedure
It is my misunderstanding, I thought you've meant to merge cases (the fall through case and the previous one) and then to use if-then to split them again in  the compound operator.
Ahhh….. ok, now i understand.
Agreed. That is still my technique if i need fall-through.
But any „fall-through“ scenario has to be coded very carefully (order of conditions etc.)
TinyPortal © 2005-2018