Author Topic: Macro-system design  (Read 17053 times)

bas

  • Full Member
  • ***
  • Posts: 220
    • View Profile
Macro-system design
« on: January 30, 2019, 09:17:13 AM »
Since C2 wants to avoid the pitfalls of the C textual-replacement based macro system (and C2 doesn't have includes), a replacement macro system is needed.
Macros have a various, valid use-cases. I'll focus on one case here and try to find a nice syntax + semantics.

What I want is to be able to put a loop over a data structure in a macro, so that changing the underlying data structure maybe doesn't need any 'api' change
for users. So something like

(in some function)
Code: [Select]
   // there is a list.List* mylist that points to the list (let's say a linked-list)
   list.foreach(mylist, i) {     // i is the name of the Element*
      // use Element* i here
      if (i.value == 10) break;
  }

So:
  • list.foreach is the macro
  • When changing the data structure from a linked list to a vector, nothing much has to change in the fragment.
  • The break statement should break out of the macro loop even if  the macro itself has multiple, nested loops.
Note that this is generally not possible in C, since the macro probably needs to add code before and after the user code inside the loop.

The definition could be roughly something like:
Code: [Select]
public macro foreach(List* thelist, iname) {
   Element* iname = thelist.first;
   while (iname != nil) {
       // macro body should be here, How to specify?
      iname = iname.next;
   }
}
So this brings up a lot of questions:
  • how to let a user specify the name of the iterator?
  • Do we required types for macro arguments?
  • How can we specify the place for the macro body?
  • Do we allow using variables from the calling scope (and how to specify)
  • How can we implement the break in user code
  • Do we allow nested macro calling?

For C macro's all these questions are easy, since it's just replacing text. But that gives rise to a lot of issues, so a new system
is definitely needed. But I think a non-textual system will have a lot more restrictions (which can be good), so when designing a
new system, the goal should not be to find a replacement for every possibility in C macros. This won't work. My plan is to
start creating a set of use-cases and build on top of that. The foreach example is just one of those use-cases.

lerno

  • Full Member
  • ***
  • Posts: 247
    • View Profile
Re: Macro-system design
« Reply #1 on: January 30, 2019, 10:33:30 AM »
1. I think it's a bad idea to syntax check the macros using types (the "List" type) there. In order to show constraints, prefer contracts like in the Go2 proposal. Such a contract can include type if one wants to.

Possibility (1) inline require/contract

Code: [Select]
public macro foreach(thelist, iname) {
   @require {
      thelist.first;
      thelist.first.next == thelist.first;
   }
   Element* iname = thelist.first;
   while (iname != nil) {
       // macro body should be here, How to specify?
      iname = iname.next;
   }
}

We can then continue making this even more general:

Code: [Select]
public macro foreach(thelist, iname) {
   @require {
      thelist.first;
      thelist.first.next == thelist.first;
   }
   typeof(iname.first) iname = thelist.first;
   while (iname != nil) {
       // macro body should be here, How to specify?
      iname = iname.next;
   }
}

I suggest the use of compile time variables to clean up things:

Code: [Select]
public macro foreach(thelist, iname) {
   @require {
      thelist.first;
      thelist.first.next == thelist.first;
   }
   $element_type = typeof(iname.first); // Not visible after compilation
   $element_type iname = thelist.first;
   while (iname != nil) {
       // macro body should be here, How to specify?
      iname = iname.next;
   }
}

The alternative to inline contracts are external ones:

Code: [Select]
type List contract {
   self.first;
   self.first.next == self.first;
}

public macro foreach(@List thelist, iname) {
   $element_type = typeof(iname.first); // Not visible after compilation
   $element_type iname = thelist.first;
   while (iname != nil) {
       // macro body should be here, How to specify?
      iname = iname.next;
   }
}


lerno

  • Full Member
  • ***
  • Posts: 247
    • View Profile
Re: Macro-system design
« Reply #2 on: January 30, 2019, 10:41:04 AM »
In the case of the inline body I think this would be reasonable:

Code: [Select]
public macro foreach(thelist, @body(Element *) ) {
   Element* iname = thelist.first;
   while (iname != nil) {
      @body(iname);
      iname = iname.next;
   }
}

Or a version that is more flexible:
Code: [Select]
public macro foreach(thelist, @body(typeof(thelist.first)) ) {
   typeof(thelist.first) iname = thelist.first;
   while (iname != nil) {
      @body(iname);
      iname = iname.next;
   }
}

Usage:

Code: [Select]
foreach(list, Element *i) { // <- Note type declaration!
   i.print();
}

Since type is going to appear very often, we could make a shortcut for it, like $@ as prefix meaning "typeof".

We then get

Code: [Select]
public macro foreach(thelist, @body($@thelist.first) ) {
   $@thelist.first iname = thelist.first;
   while (iname != nil) {
      @body(iname);
      iname = iname.next;
   }
}

Possibly we could even write the code like this:

Code: [Select]
public macro foreach(thelist, @body($element_type) ) {
   $element_type iname = thelist.first;
   while (iname != nil) {
      @body(iname);
      iname = iname.next;
   }
}

In this case $element_type works like "auto", but is also assigned the type, which then can be referred to in the signature.
« Last Edit: January 30, 2019, 10:45:58 AM by lerno »

lerno

  • Full Member
  • ***
  • Posts: 247
    • View Profile
Re: Macro-system design
« Reply #3 on: January 30, 2019, 10:47:08 AM »
Allowing nested macros could allow us to do recursive macro resolution, which opens up compile time execution.

lerno

  • Full Member
  • ***
  • Posts: 247
    • View Profile
Re: Macro-system design
« Reply #4 on: January 30, 2019, 10:48:55 AM »
All of the above ties into the generics proposal. The generics being a way to create packets of macros basically, but has space advantages.

bas

  • Full Member
  • ***
  • Posts: 220
    • View Profile
Re: Macro-system design
« Reply #5 on: February 28, 2019, 08:26:30 AM »
I agree with https://npf.io/2018/09/go2-contracts-go-too-far. It's too complex IMHO.

When you want an argument to be of a specific type in C, don't you just say myfunc(Foo a), instead of requiring a to be
of type Foo later. The only thing you can't do that way is force multiple arguments to be of some Type 'T' that just supports
the required operations in the macro. Something Generics may solve. Also the typeof() may be needed as in your example.

I want to learn Nim macros more before building my opinion about them.. Today is Nim day ;)

lerno

  • Full Member
  • ***
  • Posts: 247
    • View Profile
Re: Macro-system design
« Reply #6 on: March 01, 2019, 02:38:23 PM »
Did you think about my generics proposal? "import foo (i32, Bar, f32) as local;" "module foo (A, B, C);"