www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - D's SwitchStatement accepts statements with ridiculous semantics

reply Don Clugston <edibleplutonium completelysafe.com> writes:
Guess what this prints

----
import std.stdio;

void main()
{
   int i = 0;

   switch (i) for (i = 8; i < 10; ++i)
   {
     case 7:
         writeln(i);
         return;

     default: ;
   }
}
----


Why does this even compile? It's because the grammar is:

SwitchStatement:
     switch ( Expression ) ScopeStatement


and ScopeStatement allows almost anything.
I think the only sane grammar is

SwitchStatement:
     switch ( Expression ) BlockStatement

Initially I thought ScopeStatement was accepted in order to 
enable Duff's device, but it isn't necessary for that. It might 
have originally accepted ScopeStatement to support

E e; switch( e ) with (E) { .... }

Or it may have just been an accident.
But regardless of the original motivation, it allows some truly 
dreadful semantics.
Can we disallow this silliness please?
Sep 29
next sibling parent reply Dukc <ajieskola gmail.com> writes:
On Friday, 29 September 2017 at 09:12:54 UTC, Don Clugston wrote:
 Guess what this prints
My guess is it prints "1". By "guess" I mean it, I did not test! Anyway reminds me a lot of very badly used gotos.
Sep 29
parent reply sarn <sarn theartofmachinery.com> writes:
On Friday, 29 September 2017 at 09:56:17 UTC, Dukc wrote:
 On Friday, 29 September 2017 at 09:12:54 UTC, Don Clugston 
 wrote:
 Guess what this prints
My guess is it prints "1". By "guess" I mean it, I did not test! Anyway reminds me a lot of very badly used gotos.
Yeah, it's a lot like Duff's Device: https://en.wikipedia.org/wiki/Duff's_device For anyone who's wondering, it works because switch is just a computed goto. The code's equivalent to this: import std.stdio; void main() { int i = 0; // switch(i) if (i == 7) { goto case_7; } else { goto case_default; } // for loop initialiser i = 8; // for loop test while (i < 10) { case_7: writeln(i); return; case_default: // for loop update ++i; } }
Sep 29
parent drug <drug2004 bk.ru> writes:
30.09.2017 05:35, sarn пишет:
 
 For anyone who's wondering, it works because switch is just a computed 
 goto.  The code's equivalent to this:
 
 import std.stdio;
 
 void main()
 {
      int i = 0;
 
      // switch(i)
      if (i == 7)
      {
          goto case_7;
      }
      else
      {
          goto case_default;
      }
 
      // for loop initialiser
      i = 8;
      // for loop test
      while (i < 10)
      {
 case_7:
          writeln(i);
          return;
 case_default:
          // for loop update
          ++i;
      }
 }
Thanks for clarification!
Sep 30
prev sibling next sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 29.09.2017 11:12, Don Clugston wrote:
 Guess what this prints
 
 ----
 import std.stdio;
 
 void main()
 {
    int i = 0;
 
    switch (i) for (i = 8; i < 10; ++i)
    {
      case 7:
          writeln(i);
          return;
 
      default: ;
    }
 }
 ----
 
 
 Why does this even compile? It's because the grammar is:
 
 SwitchStatement:
      switch ( Expression ) ScopeStatement
 
 
 and ScopeStatement allows almost anything.
 I think the only sane grammar is
 
 SwitchStatement:
      switch ( Expression ) BlockStatement
 
 Initially I thought ScopeStatement was accepted in order to enable 
 Duff's device, but it isn't necessary for that. It might have originally 
 accepted ScopeStatement to support
 
 E e; switch( e ) with (E) { .... }
 
 Or it may have just been an accident.
It is very likely that this part of the grammar was deliberately copied from C. It's also consistent with how all other control flow constructs are parsed.
 But regardless of the original motivation, it allows some truly dreadful 
 semantics.
I don't see what your proposed grammar change accomplishes: switch(i){ for(i=8;i<10;++i){ case 7: writeln(i); return; default:{} } } I.e., you seem to have misidentified the culprit. Whether or not to the curly braces are required by the parser has nothing to do with switch semantics.
 Can we disallow this silliness please?
 
Maybe this specific case can be disallowed during semantic (but I don't really see why it helps, it mostly just makes the language definition more complex).
Sep 29
next sibling parent reply Don Clugston <edibleplutonium completelysafe.com> writes:
On Friday, 29 September 2017 at 10:32:02 UTC, Timon Gehr wrote:
 On 29.09.2017 11:12, Don Clugston wrote:
 Guess what this prints
 
 ----
 import std.stdio;
 
 void main()
 {
    int i = 0;
 
    switch (i) for (i = 8; i < 10; ++i)
    {
      case 7:
          writeln(i);
          return;
 
      default: ;
    }
 }
 ----
 
 
 Why does this even compile? It's because the grammar is:
 
 SwitchStatement:
      switch ( Expression ) ScopeStatement
 
 
 and ScopeStatement allows almost anything.
 I think the only sane grammar is
 
 SwitchStatement:
      switch ( Expression ) BlockStatement
 
 Initially I thought ScopeStatement was accepted in order to 
 enable Duff's device, but it isn't necessary for that. It 
 might have originally accepted ScopeStatement to support
 
 E e; switch( e ) with (E) { .... }
 
 Or it may have just been an accident.
It is very likely that this part of the grammar was deliberately copied from C. It's also consistent with how all other control flow constructs are parsed.
 But regardless of the original motivation, it allows some 
 truly dreadful semantics.
I don't see what your proposed grammar change accomplishes: switch(i){ for(i=8;i<10;++i){ case 7: writeln(i); return; default:{} } } I.e., you seem to have misidentified the culprit. Whether or not to the curly braces are required by the parser has nothing to do with switch semantics.
That case looks quite different to me. It's rather more obvious that the `for` statement has been skipped. The problem I have with the original example is that it looks as though the body of the `for` loop is the body of the switch statement.
 Can we disallow this silliness please?
 
Maybe this specific case can be disallowed during semantic (but I don't really see why it helps, it mostly just makes the language definition more complex).
I believe it makes it simpler. You cannot avoid the reference to BlockStatement. Note that: "A switch statement must have a default statement." This is only possible only in two situations. 1. Silly degenerate case. switch (i) default: ; 2. A statement which contains a BlockStatement. It accepts unreachable code. switch (i) if ( foo() ) {} else { default: ; } A switch statement followed by anything other than a BlockStatement is *always* wrong. Always. We improved the switch statement a lot by disallowing implicit fallthrough. But it still allows other nonsense that was presumably inherited from the very early days of K&R C. This example just struck me as exceedingly silly, and quite misleading.
Sep 29
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 29.09.2017 17:05, Don Clugston wrote:
 I don't see what your proposed grammar change accomplishes:

 switch(i){
     for(i=8;i<10;++i){
         case 7:
             writeln(i);
             return;
         default:{}
     }
 }

 I.e., you seem to have misidentified the culprit. Whether or not to 
 the curly braces are required by the parser has nothing to do with 
 switch semantics.
That case looks quite different to me. It's rather more obvious that the `for` statement has been skipped. ...
Oh, I see. (They look the same to me.)
 The problem I have with the original example is that it looks as though 
 the body of the `for` loop is the body of the switch statement.
 ...
This can be done with basically any control flow construct we have: if(i) for(i=8;i<10;++i) { // ... } (This relates to my point about consistency.)
 Can we disallow this silliness please?
Maybe this specific case can be disallowed during semantic (but I don't really see why it helps, it mostly just makes the language definition more complex).
I believe it makes it simpler. You cannot avoid the reference to BlockStatement.
I don't see why. For example, my compiler implementation of SwitchStm does not mention BlockStm.
 Note that:  "A switch statement must have a default statement."
 
 This is only possible only in two situations.
 
 1. Silly degenerate case.
       switch (i) default: ;
 
 2. A statement which contains a BlockStatement.
 
 It accepts unreachable code.
 
    switch (i) if ( foo() ) {} else { default: ; }
 
 A switch statement followed by anything other than a BlockStatement is 
 *always* wrong. Always.
 ...
Well, I have used the switch(...) with(...) idiom you mentioned in the original post a few times, and I'm quite confident you'd meet some opposition if you were to break it. There's also this case: switch(x) static foreach(i;0..10) static if(i==0) default: return f!0(); else case i: return f!i(); and, of course, this one: final switch(x) static foreach(i;0..10) case i: return f!i();
 We improved the switch statement a lot by disallowing implicit 
 fallthrough.
Explicit fallthrough is still allowed though.
 But it still allows other nonsense that was presumably 
 inherited from the very early days of K&R C.
 
 This example just struck me as exceedingly silly, and quite misleading.
Well, 'switch' does not uphold the principles of structured programming. It is natural that one can extract some silliness from that, and I don't think changing grammar rules in inconsistent ways in order to disallow particular silly examples is a very good way to design a language. I think removing () and requiring {} everywhere as Go and Rust have done is quite good language design which eliminates many of C's grammar issues, but it is not the path that D has taken and it does not improve switch semantics.
Sep 29
parent Jacob Carlborg <doob me.com> writes:
On 2017-09-29 21:56, Timon Gehr wrote:

 Well, I have used the switch(...) with(...) idiom you mentioned in the
 original post a few times, and I'm quite confident you'd meet some
 opposition if you were to break it.
I've used that as well, but the other way around. I put the switch statement inside the with statement. -- /Jacob Carlborg
Sep 30
prev sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Fri, Sep 29, 2017 at 12:32:02PM +0200, Timon Gehr via Digitalmars-d wrote:
[...]
 It is very likely that this part of the grammar was deliberately
 copied from C. It's also consistent with how all other control flow
 constructs are parsed.
I believe one of the reasons the grammar was written this way was to allow Duff's device in D. Personally, I don't care much for it -- an optimizing compiler ought to be able to generate the best code without requiring the programmer to explicitly write Duff's device. Though Walter did indicate that dmd's optimizer is lacking in the loop unrolling department. T -- It is not the employer who pays the wages. Employers only handle the money. It is the customer who pays the wages. -- Henry Ford
Sep 29
prev sibling parent user1234 <user1234 12.lo> writes:
On Friday, 29 September 2017 at 09:12:54 UTC, Don Clugston wrote:
 Or it may have just been an accident.
 But regardless of the original motivation, it allows some truly 
 dreadful semantics.
 Can we disallow this silliness please?
There are two big family of switches. C-like and Pascal like. In Pascal you can only have cases in the switch. See https://en.wikipedia.org/wiki/Switch_statement#Semantics The C form doesn't prevent you to be stricter but it's all about self-discipline since the compiler will allow everything in there.
Sep 30