fusionlanguage / fut

Fusion programming language. Transpiling to C, C++, C#, D, Java, JavaScript, Python, Swift, TypeScript and OpenCL C.
https://fusion-lang.org
GNU General Public License v3.0
1.74k stars 55 forks source link

[Proposal] Allow flag usage in native blocks #167

Open al1-ce opened 1 month ago

al1-ce commented 1 month ago

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 commented 1 month ago

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 commented 1 month ago

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.

al1-ce commented 1 month ago

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,

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 commented 1 month ago

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:

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 commented 1 month ago

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).

pfusik commented 1 month ago

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

This looks best:

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 commented 1 month ago

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 commented 1 month ago

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 commented 1 month ago

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