adam-mcdaniel/oakc

Add variadic functions

Opened this issue · 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);

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.

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.

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!")

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.

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!

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?

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.

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

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?

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