March 08

On Friday, 8 March 2024 at 03:23:12 UTC, Paul Backus wrote:

>

By the way, if you did want to treat a .c or .h file like its own module, you'd still be able to do so with Mixin C. Just write a simple .d wrapper, like this:

module libwhatever;

mixin(C) {
    #include <libwhatever.h>
}

This also makes it trivial to apply function attributes to C declarations. For example, if you want everything from libwhatever.h to be nothrow and @nogc, you just write this:

nothrow @nogc
mixin(C) {
    #include <libwhatever.h>
}

There's currently no way to do this with ImportC, and even if there were, it would likely require modifying the header file (for example, with the #pragma suggested in issue 23812).

March 09

On Friday, 8 March 2024 at 18:03:47 UTC, Paul Backus wrote:

>
nothrow @nogc
mixin(C) {
    #include <libwhatever.h>
}

There's currently no way to do this with ImportC, and even if there were, it would likely require modifying the header file (for example, with the #pragma suggested in issue 23812).

wow, that is cool!

March 23

On Friday, 8 March 2024 at 03:23:12 UTC, Paul Backus wrote:

>

What if instead of importing C files like D modules, we could write bits of C code directly in the middle of our D code, like we do with inline ASM?

It might look something like this:

void main()
{
    mixin(C) {
        #include <stdio.h>
        printf("Hello from C!\n");
    }
}

Here's how it could work:

  1. The compiler takes the content of the mixin(C) block and passes it through the external C preprocessor.
  2. The result of (1) is parsed as a C AST fragment using the ImportC parser.
  3. The result of (2) is spliced into the AST in place of the mixin(C) block, and undergoes semantic analysis using ImportC semantics.

So the entirety of stdio.h is included in the body of the D main function? Is that wise?

The other problem is if you want to use C expressions in D. For example, let's say you have the C definition:

#define PI 3.14159

How can I use this in D land? I could assign it to a variable maybe?

mixin(C) {
   #include "pidef.h"
   double PI_ = PI;
}

Note, I have to use a new name. And it has to be a variable, because that's all you can do in C. What if I wanted it to be an enum? Too bad, C doesn't support that.

What I'd like to see is:

a) the C preprocessor is run on all the mixin(C) islands of the file regardless of where they appear, whether they are in templates, etc. Basically, take all the mixin(C) things and concatenate them, run the result through the preprocessor, and put the results back where they were. THEN run the importC compiler on them. This allows a more cohesive C-like experience, without having to import/define things over and over.
b) Allow mixin(C) expressions, such as enum PI = mixin(C) { PI }. Maybe this was already the intention? But I didn't get that vibe from the proposal.

-Steve

March 23

On Saturday, 23 March 2024 at 02:51:31 UTC, Steven Schveighoffer wrote:

>

So the entirety of stdio.h is included in the body of the D main function? Is that wise?

In this specific example, it's overkill.

In general...is there a better alternative? The C preprocessor is a blunt instrument, and if we want to have full support for it, we are going to have to live with the consequences of that bluntness.

With Mixin C, the D programmer at least gets to choose whether they would rather pay the cost of #include-ing C headers multiple times, or the cost of translating preprocessor macros by hand.

>

The other problem is if you want to use C expressions in D. For example, let's say you have the C definition:

#define PI 3.14159

How can I use this in D land? I could assign it to a variable maybe?

mixin(C) {
   #include "pidef.h"
   double PI_ = PI;
}

Note, I have to use a new name. And it has to be a variable, because that's all you can do in C. What if I wanted it to be an enum? Too bad, C doesn't support that.

You can use a lambda:

enum PI = () {
    mixin(C) {
        #include "pidef.h"
        return PI;
    }
}();
>

What I'd like to see is:

a) the C preprocessor is run on all the mixin(C) islands of the file regardless of where they appear, whether they are in templates, etc. Basically, take all the mixin(C) things and concatenate them, run the result through the preprocessor, and put the results back where they were. THEN run the importC compiler on them. This allows a more cohesive C-like experience, without having to import/define things over and over.

Some downsides to this approach:

  1. Concatenating all of the mixin(C) blocks in a module for preprocessing violates D's scoping rules and creates a lot of opportunities for "spooky action at a distance."
  2. This would allow sharing macro definitions across mixin(C) blocks, but would not allow sharing declarations. You'd still have to #include <stdio.h> twice if you wanted to call printf in two different blocks, for example.
  3. In order to "put the results back where they were" the D compiler would have to parse the preprocessor's output for line markers. Since the format of these is not specified by the C standard, this means the D compiler would have to have separate parsers for each C preprocessor implementation (or, at least, one for gcc/clang and one for MSVC).
March 28

On Saturday, 23 March 2024 at 19:02:52 UTC, Paul Backus wrote:

>

On Saturday, 23 March 2024 at 02:51:31 UTC, Steven Schveighoffer wrote:

>

So the entirety of stdio.h is included in the body of the D main function? Is that wise?

In this specific example, it's overkill.

In general...is there a better alternative? The C preprocessor is a blunt instrument, and if we want to have full support for it, we are going to have to live with the consequences of that bluntness.

My objection is to the requirement that you include it in the main function. It's very different from D nested imports, as it redefines everything inside the function.

Having to re-import everything everywhere you need to use a macro is really bad.

>

You can use a lambda:

enum PI = () {
    mixin(C) {
        #include "pidef.h"
        return PI;
    }
}();

Ugh, still having to include the entirety of a C header inside a function context.

> >

What I'd like to see is:

a) the C preprocessor is run on all the mixin(C) islands of the file regardless of where they appear, whether they are in templates, etc. Basically, take all the mixin(C) things and concatenate them, run the result through the preprocessor, and put the results back where they were. THEN run the importC compiler on them. This allows a more cohesive C-like experience, without having to import/define things over and over.

Some downsides to this approach:

  1. Concatenating all of the mixin(C) blocks in a module for preprocessing violates D's scoping rules and creates a lot of opportunities for "spooky action at a distance."

But that's what you get with C. For instance, you can #define a macro inside a function, and use it inside another function, as long as it comes later in the file. It's not spooky to C programmers. You can even #undef things or re #define them.

>
  1. This would allow sharing macro definitions across mixin(C) blocks, but would not allow sharing declarations. You'd still have to #include <stdio.h> twice if you wanted to call printf in two different blocks, for example.

but you wouldn't have to include them inside the functions. You get the function definitions and macros in the right place (at module level).

>
  1. In order to "put the results back where they were" the D compiler would have to parse the preprocessor's output for line markers. Since the format of these is not specified by the C standard, this means the D compiler would have to have separate parsers for each C preprocessor implementation (or, at least, one for gcc/clang and one for MSVC).

I came up with an approach for this, I detailed it in my dconf talk last year. All preprocessors have a flag which preserves comments.

But yes, you are right this is a big hacky problem to solve.

-Steve

March 28

On Thursday, 28 March 2024 at 02:24:34 UTC, Steven Schveighoffer wrote:

>

On Saturday, 23 March 2024 at 19:02:52 UTC, Paul Backus wrote:

>

Some downsides to this approach:

  1. Concatenating all of the mixin(C) blocks in a module for preprocessing violates D's scoping rules and creates a lot of opportunities for "spooky action at a distance."

But that's what you get with C. For instance, you can #define a macro inside a function, and use it inside another function, as long as it comes later in the file. It's not spooky to C programmers. You can even #undef things or re #define them.

I think most C programmers would regard this as a horrifying abuse of the preprocessor--the kind of thing they switch to D (and other languages) to get away from.

> >
  1. This would allow sharing macro definitions across mixin(C) blocks, but would not allow sharing declarations. You'd still have to #include <stdio.h> twice if you wanted to call printf in two different blocks, for example.

but you wouldn't have to include them inside the functions. You get the function definitions and macros in the right place (at module level).

It sounds like the usage pattern you're envisioning is something like this:

/// Bindings for libfoo
module foo;

mixin(C) {
    #include "libfoo.h"
}

/// Wraps foo_do_stuff
void doStuff(int x)
{
    mixin(C) {
        // reuses top-level #include
        foo_do_stuff(x, FOO_SOME_MACRO);
    }
}

/// Wraps foo_do_other_stuff
void doOtherStuff(const(char)* s)
{
    mixin(C) {
        // reuses top-level #include
        foo_do_other_stuff(s, FOO_SOME_OTHER_MACRO);
    }
}

I agree that supporting this usage pattern would be desirable, but I'm not sure concatenating every mixin(C) block in a module is the best way to do so. Perhaps instead we can have a dedicated "mixin(C) header" block that can appear at module scope, whose content is prepended to each mixin(C) block?

E.g.,

/// Bindings for libfoo
module foo;

mixinC_header {
    #include "foo.h"
}

void doStuff(int x)
{
    mixin(C) {
        // #include "foo.h" inserted here
        foo_do_stuff(x, FOO_SOME_MACRO);
    }
}

// etc.

Granted, this only solves the UX problems of the original proposal, not the performance problems--under the hood you are still doing a bunch of separate preprocessor calls, and including the same file over and over. But, again, you are not forced to use Mixin C like this, and programmers who want to optimize their build times will still have alternatives to turn to that do not require opening the door to total macro madness.

March 29

On Friday, 8 March 2024 at 03:23:12 UTC, Paul Backus wrote:

>

What if instead of importing C files like D modules, we could write bits of C code directly in the middle of our D code, like we do with inline ASM?

[...]

https://github.com/dlang/dmd/pull/14114

March 29
You can add unit tests for C code already:

```
import myccode; // #define cmacro(a) 2

unittest
{
    assert(cmacro(1) == 2);
}
```
March 29
On 3/8/2024 10:03 AM, Paul Backus wrote:
> There's currently no way to do this with ImportC

```
__declspec(nothrow) int foo();
```
March 29
On 3/7/2024 7:23 PM, Paul Backus wrote:
> Mixin C would solve two big issues with the current ImportC approach: the poor preprocessor support,

I don't see how it would improve preprocessor support. C macros used in D code won't fare any better than they do now.

> and the conflicts between `.c` and `.d` files in the compiler's import paths.

Frankly, I never understood why that was an issue. Change the name of one of them, or put them in different paths.

I appreciate the effort you've put into this. But I have to be blunt. I've seen this before. Here's what it looks like in practice:

https://elsmar.com/elsmarqualityforum/media/redneck-car-air-conditioning.1560/


D is a beautiful language to program in. Let's keep it that way!

https://www.joemacari.com/stock/ferrari-daytona-spyder/10004904