adam-mcdaniel/oakc

FFI right now is very bad

Opened this issue ยท 12 comments

Things in the ffi branch need to change drastically before I can merge it. I really dislike having to use the command line to include FFI files. I think it's incredibly inelegant, and I think the only valid solution is to add a few new flags to the language.

Flag Purpose
#[no_std] Do not automatically include any foreign functions, or any Oak standard library code. FFI files can still be included with the extern flag in the user's source code.
#[extern("foreign_file.*")] Include a foreign file in the generated output code. This allows added functionality to Oak code that isn't provided by the standard library. This will replace the flag to add foreign files in the cmd line.
#[error("Error message")] Throw an error message at compile time. This is helpful for throwing errors in conditional compilation statements.
#[if(const_expr) { // Oak code here }] Allow conditional compilation. This is a HUGE feature that is absolutely necessary, even without the need to support multiple output targets.
#[if(const_expr) { // Oak code here } else { // Oak code here }] Allow conditional compilation with an else clause.

These flags don't really help without a few constants to help, though.

Constant Meaning
TARGET This constant will be retrieved from the Target impl in the HIR stage of compilation. Each target will have their own character constant that represents that target. For example, C's target value might be 'c', and Golang's might be 'g'.
IS_STANDARD Does the target support all of the core 13 VM instructions exactly according to the standard? Targets that are implemented with a memory tape of int values, for example, would set this flag to be false.
ON_WINDOWS Is the compiler running on windows?
ON_MACOS Is the compiler running on macOS?
ON_LINUX Is the compiler running on linux?
ON_NIX Is the compiler running on a *nix platform?
ON_NON_NIX Is the compiler not running on a *nix platform?
OAK_MAJOR_VERSION The major version of the Oak compiler.
OAK_MINOR_VERSION The minor version of the Oak compiler.
OAK_PATCH_VERSION The patch version of the Oak compiler.
DATE_DAY The day (from 1 to 31) that the file was compiled.
DATE_MONTH The month (from 1 to 12) that the file was compiled.
DATE_YEAR The year that the file was compiled.

These constants are pretty much on par with C predefined macros and Rust's cfg! macro. This should give users enough information to do some serious compile time hacking.

Would #[extern("foreign_file.*")] only allow including oak files, or would it also allow including files written in the target language?

The extern flag would only be able to include files of the target language. For including Oak files, there's already the include flag. Check the examples folder for example usage of the include directive :)

Those flags would be great features to add. The extern flag would be a great addition. Conditional compiling would also be really useful.
I am a bit confused on the runtime constants tough. Is it normal to use the compiler runtime or the target runtime? Like the code probably doesnt care if it is compiled on windows, but it should care if it runs on windows right?

Hmm, that's an interesting thought. I assumed that whichever platform the compiler is running on would be the platform the generated code is running on, but I see that MIGHT not be true in niche places such as cross compilation or when compiling to targets without an OS. The constants are intended to help with operating specific information, like using different file extensions based on the operating system, or using different code to get the user's home directory.

I think you make an interesting point about needing to know the operating system at runtime. Maybe the compile time constants regarding the operating system should remain, and standard library functions (NOT added to the core VM instruction implementation files) should be added to determine the operating system that the code is running on.

I also see some problems with the #[no_std] flag. What happens when a file that requires the standard library includes a file with the #[no_std] flag?? Maybe a #[require_std] flag would be more appropriate, and the standard library should not be included by default.

Well my target has no OS for example. So it is pretty much already cross compiling to a target with no OS. I understand its intent, but i assume most languages state the target platform instead of the compiling one. For example my target the keyboard has no '\n' but pressing Enter generates a '\r' key code. getch is a great example where this information can be used to generate code that handles its platform correctly. I think having the standard library included by default is the correct default as that is what I would expect from a language. I think it would cause less frustration to an end user. If I don't want to use the standard library included I probably have a good reason for it, so being explicit about it seems like a sane default.
What about an compile time error in case of a conflicting #[no_std]? Tough I can imagine that gets complicated when you get an include chain.

I don't think it's that much of a stretch to require the user to use a #[require_std] flag. It's similar to how C, by default, does not require the standard library unless a header like stdio.h is included. And it would be much simpler to implement.

I do agree with you about the implementing the retrieval of information about the programs platform at runtime, though.

Right, I just checked it and never realised C works like that. So the VM implementation (and the code generated to kickstart it) would be the runtime and required? Any 'default' functions and data would be the standard library and optional? I guess it is easy to document and mention when introducing people to the language.
How about keeping the #[no_std] as a indicator that the program indicates it does not want the standard library to be loaded? It will emit an error when both are present. This way you can include files that include the standard library without a problem, but you can still assert it does not get included if your case requires it.

Yes, I think having both flags would definitely work. With the no_std flag (or the lack of a require_std flag), only the core VM instructions should be included along with the user code. No platform specific instructions, like getch, should be added in a no std environment. The extern flag should work regardless of the environment, though; it makes writing code for embedded development without the Oak standard library possible.

In PR #20, all of the aforementioned flags except no_std and require_std have been implemented. The only predefined constant that has been added is TARGET, though.

I like the idea of both flags being optional. As with constants id suggest to see how it evolves.

Finding it somewhat weird that you have C and Go FFI but not Rust...