Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Proposal] Allow flag usage in native blocks #167

Open
al1-ce opened this issue Jul 9, 2024 · 9 comments
Open

[Proposal] Allow flag usage in native blocks #167

al1-ce opened this issue Jul 9, 2024 · 9 comments
Assignees
Labels
enhancement New feature or request

Comments

@al1-ce
Copy link

al1-ce commented Jul 9, 2024

Current way of doing conditional compilation is nice, but it is very reminiscent of C's #ifdefs, which when used with native blocks is kind of cumbersome.

From all languages I've seen D seems to handle conditional compilation the best. For checking compiler flags (like fut's -D) it uses version blocks, which are used in this way:

version(WINDOWS) {
    // Some code
}

Which lands itself nicely with chaining code blocks

version(LINUX) if (somevar) {
    // Some other code
}

My proposal is to instead of current conditional compilation blocks introduce something similar to D's version statement.

Example:

// not sure about statement name though
flag(JS) native {
    // native js only code
}

flag(JAVA) {
    // java related code
} else {
    // code for everything else
}

flag(CS) {
    // c# code
} else flag(CPP) {
    // c++ code
} else {
    // everything else
}

And since it's code block based it lands nicely with both native blocks and out of box elseif support.

More about conditional compilation in D: https://dlang.org/spec/version.html (there's a bunch more interesting features)

Related: #142

@al1-ce
Copy link
Author

al1-ce commented Jul 9, 2024

For comparison:

/// Current:

#if d
    native {
        module hellofu;
    }
#endif

public static class HelloFu {
    /// Return hello message
    public static string GetMessage() {
        #if d
            native {
                import std.stdio;
                writeln("This is a D injection!");
            }
        #elif js
            native {
                console.log("This is a JS injection!");
            }
        #endif

        #if c
            return "Hello C!";
        #else
            return "Hello World!";
        #endif
    }
}

---------------------------------------------------------
/// Proposed:

flag(D) native {
    module hellofu;
}

public static class HelloFu {
    /// Return hello message
    public static string GetMessage() {
        flag(D) native {
            import std.stdio;
            writeln("This is a D injection!");
        }

        flag(JS) native {
            console.log("This is a JS injection");
        }

        flag(C) {
            return "Hello C!"
        } else {
            return "Hello World!";
        }
    }
}

@pfusik
Copy link
Collaborator

pfusik commented Jul 11, 2024

D's version has one huge disadvantage: non-D programmers are not familiar with it. #if is instantly recognized by C, C++ and C# programmers.

I don't expect #if do be often used in Fusion code, so saving a few keystrokes here and there doesn't seem to justify syntax unfamiliar to most programmers. The whole fut codebase doesn't have a single #if or native.

I could consider adding an argument to native that specifies the target language, because native is normally used under a conditional compilation anyway.

@pfusik pfusik self-assigned this Jul 11, 2024
@al1-ce
Copy link
Author

al1-ce commented Jul 11, 2024

D's version has one huge disadvantage: non-D programmers are not familiar with it. #if is instantly recognized by C, C++ and C# programmers.

Tbh I don't think most of C# programmers even know about #if since C# is interpreted. Conditional compilation is used in... compiled languages. But I might be wrong about C#.

I don't expect #if do be often used in Fusion code

Most programmers right now are python, js/ts (or web in general), rust (which has attributes and macros as way for conditional compilation). And imo C/C++ programmers wouldn't use fusion, unless they really need (as of right now) JavaScript and TypeScript as from all other languages have at least some sort of C interop.

so saving a few keystrokes here and there doesn't seem to justify syntax unfamiliar to most programmers.

Isn't native an unfamiliar syntax? It is easy to figure out, since it's literally called native, but still.. Also every language has some sort of it's own syntax which is unfamiliar to some/many programmers. There's always some learning curve, so, personally, I don't think it's an issue.

The whole fut codebase doesn't have a single #if or native.

That's fair, but it's a single project. There are many cases where one would want a bunch of native blocks: attributes (c++, c#, java, d...), unittests (d (idk if there are others with native unittest support)), lang-specific libraries, inline assembly (c, c++, d). For conditional compilation it can be just doing different thing in different languages. And as amount of languages grow there will be more and more edge cases where #ifs and native blocks would become nessesary

I could consider adding an argument to native that specifies the target language, because native is normally used under a conditional compilation anyway.

That is fair, but what would be syntax? There are basically two ways I see right now without changing much,

  • native(FLAG) {}
  • native FLAG {}

First falls under your unfamiliar syntax argument and second sticks out enough to prompt programmer to look at docs, so... I guess second?

D's version has one huge disadvantage

Oh, on that note there's also a static if () {}, which would maybe satisfy you more, as if instead of #if it'd be something similar to static if, but using different keyword maybe?

Also another thing why I made this proposal is readability. C-style preprocessor statements take a lot of space since they're mostly line-based (and I personally stand for OTBS).

Here's an argument, which would be eliminated if to integrate (optional) flags into native blocks

// currently
int func() {
    #if FLAG
        native {
            // code
        }
    #endif
}
// Or
int func() {
    #if FLAG
    native {
        // code
    }
    #endif
}
// Or
int func() {
#if FLAG
    native {
        // code
    }
#endif
}
// what I was proposing
int func() {
    flag(FLAG) native {
        // code
    }
}
// if to integrate flags into native
int func() {
    native FLAG {
        // code
    }
}

For me in current way all three options are bad way to write code. Both my proposed way and native flags way are more natural, so, if not have better #if syntax, then I heavily vote for flags in native!

If you'd rather go with flags in native, then please rename this issue, just so it's easier to track

@pfusik
Copy link
Collaborator

pfusik commented Jul 16, 2024

D's version has one huge disadvantage: non-D programmers are not familiar with it. #if is instantly recognized by C, C++ and C# programmers.

Tbh I don't think most of C# programmers even know about #if since C# is interpreted. Conditional compilation is used in... compiled languages. But I might be wrong about C#.

I don't know what's your experience with C#, but from 2004 to 2014 I worked full-time as a C# developer, on a team of a dozen developers. #if is much rarer in C# than in C, but we were aware of it.

I don't expect #if do be often used in Fusion code

Most programmers right now are python, js/ts (or web in general), rust (which has attributes and macros as way for conditional compilation). And imo C/C++ programmers wouldn't use fusion, unless they really need (as of right now) JavaScript and TypeScript as from all other languages have at least some sort of C interop.

Could you elaborate on your point of view?

Fusion is an alternative to C interop and I expected the target audience to be people tired of developing libraries in C with tons of bindings.
Why would a web developer need Fusion at all?

so saving a few keystrokes here and there doesn't seem to justify syntax unfamiliar to most programmers.

Isn't native an unfamiliar syntax?

I was referring to #if vs version. #if being used by C, C++ and C# programmers, version only by D programmers.

native is a different story, because no mainstream language is designed for transpiling. The closest I can think of is:

  • Java's native (same keyword!)
  • C/C++ asm statement - does kind of what native in Fusion, however what you put in Fusion is certainly not assembly language

It is easy to figure out, since it's literally called native, but still.. Also every language has some sort of it's own syntax which is unfamiliar to some/many programmers. There's always some learning curve, so, personally, I don't think it's an issue.

The whole fut codebase doesn't have a single #if or native.

That's fair, but it's a single project. There are many cases where one would want a bunch of native blocks: attributes (c++, c#, java, d...), unittests (d (idk if there are others with native unittest support)), lang-specific libraries, inline assembly (c, c++, d). For conditional compilation it can be just doing different thing in different languages. And as amount of languages grow there will be more and more edge cases where #ifs and native blocks would become nessesary

Is your Fusion codebase open source?

I have eight open source projects (fut being one of) and there's exactly one use of native in one project. And it's just a quick hack that I was too lazy to design properly.

The expected integration with the target languages is to use Fusion-generated classes, sometimes subclassing them, not add much native code in Fusion source files.

I could consider adding an argument to native that specifies the target language, because native is normally used under a conditional compilation anyway.

That is fair, but what would be syntax? There are basically two ways I see right now without changing much,

  • native(FLAG) {}
  • native FLAG {}

First falls under your unfamiliar syntax argument and second sticks out enough to prompt programmer to look at docs, so... I guess second?

First one looks good. I don't know if FLAG should be a preprocessor symbols expression or maybe a list of languages? (lower/uppercase? || or comma-separated?)

D's version has one huge disadvantage

Oh, on that note there's also a static if () {}, which would maybe satisfy you more, as if instead of #if it'd be something similar to static if, but using different keyword maybe?

This is again D syntax. As much as I have sympathy for D, I don't want to clone its syntax.

Also another thing why I made this proposal is readability. C-style preprocessor statements take a lot of space since they're mostly line-based (and I personally stand for OTBS).

I find it an advantage: if conditional compilation looks bad, design your code to avoid it.

If you'd rather go with flags in native, then please rename this issue, just so it's easier to track

I'd be happy to see your usecases!

Fusion wasn't designed in vain. From the very beginning, it served as an implementation language for working projects.

native was an easy addition, but if abused, it would totally obfuscate Fusion code.

@al1-ce
Copy link
Author

al1-ce commented Jul 16, 2024

Ok, I see your points (with a bit clearer mind) and I'd say let's stick to thinking about improving native syntax and keep #if as is.

So, potential solutions are:

// i assume capital flags here
// because they are easier
// to differentiate from normal
// syntax and symbols

// more like a preprocessor expr
native C {} 

// more functional approach
native(C) {}

// if to allow a list of languages
native(C || CPP || OPENCL) {}
// commas feel best for func style 
native(C, CPP, OPENCL) {}
// and || feels best for preproc
native C || CPP {} 
native C, CPP {}

Either one is fine, though func feels cleaner. I'd say your choice which one it'd be.

The question is, would language flags be predefined when compiling to that languahe or if it'd be up to user to define it with -D flag?

Another question is if it'd be predefined would it be allowed to use user-defined flags (I vote for yes). For example if languages are defined and I want to use some generic code for, let's say, both C, C++ and OpenCL and I'd define CLIKEflag, it'd be useful to be able to use it with native block instead of listing all languages (in some cases it could also contain Vala, D and other languages that have C interop).

@al1-ce al1-ce changed the title [Proposal] Nicer #if #endif syntax [Proposal] Allow flag usage in native blocks Jul 16, 2024
@pfusik pfusik added the enhancement New feature or request label Jul 16, 2024
@pfusik
Copy link
Collaborator

pfusik commented Jul 16, 2024

native (C || CPP || OPENCL) {}

This looks best:

  • parentheses for consistency with if (yes, I'm aware of Raku, Go, Rust removing them)
  • preprocessor symbols, so that you can add your own
  • the intent of || is clear, the comma is not

I think fut should predefine the preprocessor symbol for the language it is transpiling to, to make it standard and easy-to-use.
We risk a potential collision with languages added in the future. Shouldn't be a big problem, though.
Possible mitigation is defining the language symbol as lowercase and enforcing -D to be uppercase - but that looks odd and c, d could be confused with local variables.

Perhaps we should drop the condition-less native syntax? I expect native are always wrapped in #if anyway.

As we're brainstorming here, in the beginning of Fusion, I also considered

#c printf("Hello, world!\n");
@java System.out.println(42);

The @ symbol is currently unassigned in the whole language. But native doesn't seem so common to justify the use of this character.

@al1-ce
Copy link
Author

al1-ce commented Jul 16, 2024

What if

native (@C || @D) {}

Or #C or some other symbol to distinguish languages from user-defined flags.

And tbh I don't think there's going to be any collision with languages, there's barely any with same names. I know, I've looked at a lot of them. I'd say I saw more then 200 and only collisions I saw were some unknown toy languages.

Also I'm personally not fan of || because it kind of implies possiblity of && (also since it's flags why not |)... Actually, I am a fan now, what if to allow it? It's not going to work with languages, but possibly (@C | @CPP) & LINUX or something like that.

Yea, maybe remove second bar and certainly allow having full on expressions!

@pfusik
Copy link
Collaborator

pfusik commented Jul 16, 2024

And tbh I don't think there's going to be any collision with languages, there's barely any with same names.

I didn't mean different languages having the same name.

I meant that if someone uses fut -D FOO for some conditional compilation, then suddenly a new language Foo becomes popular, and fut picks it as its target, this breaks #if FOO. Now that I think of it, this requires that the user starts targeting the new language. So not a problem.

The @ prefixes are interesting, but I think we don't need the symbols for target languages look different from regular -D symbols.

@al1-ce
Copy link
Author

al1-ce commented Jul 16, 2024

You could also add a warning/error when user flags are colliding with predefined ones so that it's more apparent

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants