Author Topic: [Email suggestion No. 1] macros in C2  (Read 8010 times)

magnusi

  • Newbie
  • *
  • Posts: 17
    • View Profile
[Email suggestion No. 1] macros in C2
« on: July 19, 2016, 10:21:29 AM »

It has been stated in previously that macros need to be changed and not like the ones in C/C++, since those were limited/impacted by the age in which C and C++ were created in. When I was playing with Rust, which is a great programming language with some oddities (like filenames having significance in code, odd build system, variables immutable by default etc.), I stumbled upon Rust's macros, which are a great part of the language I otherwise think is okay, but not overly great. This is how a macro in Rust looks

Code: [Select]
macro_rules! foo {
    (x => $e:expr) => (println!("mode X: {}", $e));
    (y => $e:expr) => (println!("mode Y: {}", $e));
}


The following macro would be then used as:

Code: [Select]
foo!(x => 5); or foo!(y => 10);
because this example uses Rust's pattern matching. I think that a modification of Rust macros, suited for C2's cause, could be a good way how macros could work. Here is a simple suggestion of rules C2 macros could follow:

  • Each starts with keyword like macro, followed by name and the content of the macro.
  • The content may be either single value/statement or a mutiple lines of stuff in {} brackets.
  • Macros are multi-line by default, no need to use \ to extend number of lines
  • Macros must contain parse-able C2 code
  • When used, an exclamation mark ! must be added to the name of the macro and semicolon at the and as if it was a regular statement. example macro:

Code: [Select]
macro myassert(desc, test) {
    tests_ran++;
    if (!(test)) {
        char* error;
        asprintf(&error, "[%s:%d]\x1b[31mTEST FAILED\x1b[0m: %s\n",__FILE__, __LINE__, desc);
        asprintf(&finalmessage, "%s%s", finalmessage, error);
    }
    else {
        char* success;
        tests_passed++;
        asprintf(&success, "[%s:%d]\x1b[32mTEST PASSED\x1b[0m: %s\n", __FILE__, __LINE__, desc);
        asprintf(&finalmessage, "%s%s", finalmessage, success);
    }
}

Which could then be used as

Code: [Select]
myassert!("Test if 1 == 1", 1 == 1);
Of course, this is just a simple suggestion, it is just an idea how fitting and readable yet distinguishable macros could work in C2.

admin

  • Administrator
  • Newbie
  • *****
  • Posts: 11
    • View Profile
Re: [Email suggestion No. 1] macros in C2
« Reply #1 on: July 21, 2016, 07:51:26 AM »
It's a bit strange that some language cling to macros, while others
(C#, Java, etc) never seem to need them. To make a really good macro
system, I tried to write do the *purpose* of macros. What I came up
with:
  • replacing common code
  • inlining (instead of function). This one is actually not valid,
      since a function call is superior if the compiler can inline it.
  • feature selection
the difference with a function is that a macro is able to 'access'
the 'calling' scope.

The Nim language is a bit in the same domain as C2. It has some pretty
nice ideas as well. For macros they stated (something like): "with macros
you can change the AST". Indeed the case if you think about it.

http://nim-lang.org/docs/tut2.html#macros

I agree with your design (1-5). My additions:
6. A macro can be public or not, just like other declarations.
7. There needs to be a distinction
 (also described in the nim link): There are *expression* macros and
 *statement/decl* macros. You cannot have a declaration if a place wher
 an expression is expected. It might be possible to discover this, but
 it might be needed for a developer to specify this as well..
8. The argument of a macro must be an identifier (only). This avoids
    nasty issues like in C:
Code: [Select]
        #define MAX(x, y) (x > y ? x : y)
        int c = MAX(a++, b++)
as this  expands to
Code: [Select]
int c = ((a++) > (b++) ? (a++) : (b++));

magnusi

  • Newbie
  • *
  • Posts: 17
    • View Profile
Re: [Email suggestion No. 1] macros in C2
« Reply #2 on: July 21, 2016, 02:32:57 PM »
It's a bit strange that some language cling to macros, while others
(C#, Java, etc) never seem to need them. To make a really good macro
system, I tried to write do the *purpose* of macros. What I came up
with:
  • replacing common code
  • inlining (instead of function). This one is actually not valid,
      since a function call is superior if the compiler can inline it.
  • feature selection
the difference with a function is that a macro is able to 'access'
the 'calling' scope.

The Nim language is a bit in the same domain as C2. It has some pretty
nice ideas as well. For macros they stated (something like): "with macros
you can change the AST". Indeed the case if you think about it.

http://nim-lang.org/docs/tut2.html#macros

I agree with your design (1-5). My additions:
6. A macro can be public or not, just like other declarations.
7. There needs to be a distinction
 (also described in the nim link): There are *expression* macros and
 *statement/decl* macros. You cannot have a declaration if a place wher
 an expression is expected. It might be possible to discover this, but
 it might be needed for a developer to specify this as well..
8. The argument of a macro must be an identifier (only). This avoids
    nasty issues like in C:
Code: [Select]
        #define MAX(x, y) (x > y ? x : y)
        int c = MAX(a++, b++)
as this  expands to
Code: [Select]
int c = ((a++) > (b++) ? (a++) : (b++));


I agree with everything here. I propose a small addition to rule 8 and that is to allow literals, too. So

Code: [Select]
mymacro!(5, 10); //fine
mymacro!(x, y); //fine
mymacro!(x++, x*y) //not fine


admin

  • Administrator
  • Newbie
  • *****
  • Posts: 11
    • View Profile
Re: [Email suggestion No. 1] macros in C2
« Reply #3 on: July 22, 2016, 09:17:40 AM »
Maybe we can also create a new point that forbids the use of macro's within a macro?

magnusi

  • Newbie
  • *
  • Posts: 17
    • View Profile
Re: [Email suggestion No. 1] macros in C2
« Reply #4 on: July 22, 2016, 11:14:08 AM »
Maybe we can also create a new point that forbids the use of macro's within a macro?

Yes, recursive macros and so on are the bane of C/C++ code and to prevent it is a good idea.

As a technical suggestion, I suggest that macros will expand to AST rather than source code, to prevent the need of preprocessors as it is seen in C/C++.

So to sum up the rules and looks:

design rules:
  • Each macro has to start with the "macro" keyword, followed by the macro's identifier and the contents of the macro
  • The content may be either single value/statement or a multiple lines of statements in {} brackets.
  • Macros support multi-line content by default, no need to use \ to extend number of lines
  • Macros must contain parse-able C2 code
  • When used, an exclamation mark ! must be added to the name of the macro.
  • A macro can be public or not, just like other declarations.
  • There needs to be a distinction
     (also described in the nim link): There are *expression* macros and
     *statement/decl* macros. You cannot have a declaration if a place where
     an expression is expected. It might be possible to discover this, but
     it might be needed for a developer to specify this as well..
  • The argument of a macro must only be an identifier or a literal, expressions are not allowed.
  • No macro can be used within other macros

For point 7, I think that it could be figured out from the insides of the macro beforehand and each macro would be internally pre-assigned a type whether it's a "function" macro or an "expression" macro. Expression macros would be only able to contain an expression, otherwise it could be handled as a statement/function macro

syntax:
Declaration:
Code: [Select]

//statement  macros
macro loudmultiplication(x, y) printf("value %d at line %d in file %s\n", (x*y),  __LINE__, __FILE__);
public macro loudaddition(x, y) printf("value %d at line %d in file %s\n", (x+y), __LINE__, __FILE__);

public macro printfactorial(limit) {
     int64 factorial = 1;
     if(limit > 19) printf("error: the number is too big for signed 64-bit integers");
     else {
          for(int8 i = 0; i <= limit; i++) {
               factorial *= i;
          }
          printf("factorial: %d", factorial);
     }
}

macro myassert(desc, test) {
    tests_ran++;
    if (!(test)) {
        char* error;
        asprintf(&error, "[%s:%d]\x1b[31mTEST FAILED\x1b[0m: %s\n",__FILE__, __LINE__, desc);
        asprintf(&finalmessage, "%s%s", finalmessage, error);
    }
    else {
        char* success;
        tests_passed++;
        asprintf(&success, "[%s:%d]\x1b[32mTEST PASSED\x1b[0m: %s\n", __FILE__, __LINE__, desc);
        asprintf(&finalmessage, "%s%s", finalmessage, success);
    }
}

//expression or literal macros
macro dummyname "dummy";
macro makearray(count) malloc(sizeof(int32) * count);

macro anytrue(x, y, z, a, b, c) {
    (  x || y
    || z || a
    || b || c )
}

//usage
public func void testmacros() {
    loudmultiplication!(25, 14);
    loudaddition!(15, 41);
    printfactorial!(14);
    myassert!("test whether true is true", true);
    puts(dummyname);
    int32[] = makearray(5);
}

On a second thought, how about allowing boolean expressions, too? It is better for unit testing and assertions.

bas

  • Full Member
  • ***
  • Posts: 220
    • View Profile
Re: [Email suggestion No. 1] macros in C2
« Reply #5 on: July 22, 2016, 09:22:41 PM »
In your previous example about the factorial, there is no reason at all to use a macro,
since the compiler can figure out perfectly well if the function is pure (ie only depends on
the arguments and doesn't access globals). So the whole factorial(x) can be calculated at
compile time if a constant is used as argument.

For unit testing, I almost never use boolean expressions in macros, so instead of (for example)
Code: [Select]
assert_equal(a == b, "a is not b");

it's much better to use
Code: [Select]
assert_equals(a, b);
assert_greater(a ,b);
because the code can then immediately print something like: expected <x>, got <y>.

There might be other cases where a boolean expression is really needed, but I can't think
of them right now.. I'll spent some time thinking about this.

When thinking about the implementation, let's talk about the macro changing variables in the caller's environment,
eg: (the example is a bit contrived, but i hope the meaning is clear)
Code: [Select]
public macro increment_b_with(x) {
   b += x;
}
(In this case, b could also have been added as argument, but for the example it's not).
So when parsing the code, the macro is ok, but when it's used, there must be a variable
b in scope. If not, you ideally want a nice syntax error, like:

caller.c2:10 macro 'mod.increment_b_with' needs external identifier 'b'


One last idea is to let a developer impose some type restrictions. So for example an argument
must be type struct X. This does however come into the domain of regular functions..

magnusi

  • Newbie
  • *
  • Posts: 17
    • View Profile
Re: [Email suggestion No. 1] macros in C2
« Reply #6 on: July 23, 2016, 02:31:53 PM »
In your previous example about the factorial, there is no reason at all to use a macro,
since the compiler can figure out perfectly well if the function is pure (ie only depends on
the arguments and doesn't access globals). So the whole factorial(x) can be calculated at
compile time if a constant is used as argument.

Yes, it was just an example, not much of a realistic use of a macro. Calculating it a at compile time would be pretty neat.

For unit testing, I almost never use boolean expressions in macros, so instead of (for example)
Code: [Select]
assert_equal(a == b, "a is not b");

it's much better to use
Code: [Select]
assert_equals(a, b);
assert_greater(a ,b);
because the code can then immediately print something like: expected <x>, got <y>.

There might be other cases where a boolean expression is really needed, but I can't think
of them right now.. I'll spent some time thinking about this.

Yes, one of such uses can be when one wants to provide more readable/descriptive errors for failed asserts or if one is testing with a boolean function.

When thinking about the implementation, let's talk about the macro changing variables in the caller's environment,
eg: (the example is a bit contrived, but i hope the meaning is clear)
Code: [Select]
public macro increment_b_with(x) {
   b += x;
}
(In this case, b could also have been added as argument, but for the example it's not).
So when parsing the code, the macro is ok, but when it's used, there must be a variable
b in scope. If not, you ideally want a nice syntax error, like:

caller.c2:10 macro 'mod.increment_b_with' needs external identifier 'b'


A syntax error would be very nice. How about restricting this to only allow changing variables that are in global scope? Changing local variables that were not passed as an argument to the macro could create a lot of mess.

One last idea is to let a developer impose some type restrictions. So for example an argument
must be type struct X. This does however come into the domain of regular functions..

I like this idea, because it brings order to the mess that is C macros. I think it would be an amazing addition which could certainly grasp peoples' attention, just like incremental arrays do. Just this does not delve that deep into regular functions, but it does bring some sane vibe of them.