oakc icon indicating copy to clipboard operation
oakc copied to clipboard

Add variadic functions

Open kevinramharak opened this issue 4 years ago • 10 comments

Im wondering what would be needed to add variadic functions. As it would greatly help to implement printf and change the following code:

putstr("the first occurence of '"); putchar(needle); putstr("' in the string '"); putstr(haystack); putstr("' is at index "); putnumln(index);
// to
printf("The first occurence of '%c' in the string '%s' is at index %i\n", needle, haystack, index);

kevinramharak avatar Aug 23 '20 10:08 kevinramharak

I think I would prefer to do something similar to Rust's take on variadic functions. I don't think functions themselves should take a variable number of arguments, but I think we should allow the user to make macros that handle variadic arguments at compile time.

Right now I'm laying down the groundwork for user defined macros for expressions, statements, and declarations. I also think that some form of static polymorphism could be done using a good enough macro system.

adam-mcdaniel avatar Aug 23 '20 12:08 adam-mcdaniel

Hm, im not familiar with how rust macro's work but that sounds pretty cool. Also makes it easier to do the variadic stuff at compile time as the runtime wont have to deal with it.

kevinramharak avatar Aug 23 '20 13:08 kevinramharak

I'm thinking something like this:

#[std]
#[macro print(arg, args[argc]) {
    if is_type(arg, num) {
        putnum(arg as num)
    } else if is_type(arg, &char) {
        putstr(arg as &char)
    } else if is_type(arg, char) {
        putchar(arg as char)
    } else if is_type(arg, bool) {
        putbool(arg as bool)
    }
    if argc > 0 {
        $print($args)
    } else {
        putchar('\n')
    }
}]

This example defines a macro print, which takes an argument, arg, and a variadic argument args with length argc.

The $ operator for args simply just pastes the supplied arguments. So for $print(1, 2, "test", true), $args is 2, "test", true. Predefined macros such as get_arg(n, args[argc]) could be used to get specific arguments from variadic arguments.

To use print, we would call it with $print("Hello world!")

adam-mcdaniel avatar Aug 25 '20 17:08 adam-mcdaniel

This seems like a good start. I think it would be a great replacement to having to write out all the put* functions right now.

kevinramharak avatar Aug 25 '20 20:08 kevinramharak

To do macros best, I think we need to move away from having the parser spit out IR code. The parser should generate an AST node, which could then be transformed into the top level of IR. This would allow us to do AST transformations without worrying about messing with any side-effecting declarations (like issue #68). Additionally, macros wouldnt need to be expanded to blocks of MIR or HIR, they could all be dealt with exclusively in the AST!

adam-mcdaniel avatar Aug 25 '20 23:08 adam-mcdaniel

I am a bit confused on what the difference between the AST and the current TIR would be. Are they not already a tree like structure?

kevinramharak avatar Aug 26 '20 08:08 kevinramharak

Yes, TIR is a tree like structure, but it's a very strict structure. Information about the movability of objects and their members needs to be extracted here for use in HIR, copy and drop methods need to be added based on the movability, etc.

An AST for Oak, however, could be implemented very loosely. An AstNode structure might look like the following.

enum AstNode {
    Identifier(String),
    MacroArg(String), // Such as $arg in the example above
    MacroCall(String, Vec<Self>), // Such as $print($args) in the example above
    FunctionDef {
        name: Box<Self>, 
        args: Vec<Self>,
        return_type: Box<Self>,
        body: Box<Self>
    },
    Block(Vec<Self>), // A list of `AstNode` objects between brackets
    ...
}

This way, macros could act on ANY part of the AST.

This could look like something like this:

#[macro make_fn(name, type, body) {
    fn $name() -> $type $body
}]

This could make the macro system incredibly powerful, I think.

adam-mcdaniel avatar Aug 26 '20 19:08 adam-mcdaniel

All the AST would need to compute is

  1. Computing every macro call until there are none left (There might need to be an iteration limit set by a flag for REALLY recursive macros)
  2. Checks (check, for example, that when a user calls a compiler directive like #[test], that the flag actually is valid.
  3. Convert to TIR

adam-mcdaniel avatar Aug 26 '20 20:08 adam-mcdaniel

Additionally, we could add some compiler optimizations similar to Haskells GHC plugin system (with a TON of work)

#[optimize (if (true) $then_body else $else_body) -> ($then_body)]

This could be really difficult, but possible?

adam-mcdaniel avatar Aug 26 '20 20:08 adam-mcdaniel

Right, an AST with interchangable nodes would make optimizing easier. The attempt in PR #77 is very limited in that regard.

kevinramharak avatar Aug 27 '20 21:08 kevinramharak