src/ast/macro.h

    Macros

    The C preprocessor (also known as CPP) macros are familiar to C programmers (and are briefly introduced in the Tutorial). While they are very useful, their shortcomings are also well-known, for example:

    • multiline macros are hard to write, read and debug,
    • macros only deal with text i.e. they do not enforce the C syntax/grammar (which also makes them hard to write and debug),
    • macro statements cannot ‘return’ a value,
    • while macros can be used to implement simple iterators, the syntax is cumbersome.

    As an illustration, consider the following simple iteration macro

    #define iterator(start, end, index, expr) do {    \
      for (int index = start; index <= end; index++)  \
        expr					  \
      printf ("do something after the loop\n");	  \
    } while(0)
    
    int main() {
      iterator (0, 10, i, { printf ("%d\n", i); });
    }

    Using Basilisk macros, this would be written

    macro iterator (int start, int end, int index) {
      for (int index = start; index <= end; index++)
        {...}
      printf ("do something after the loop\n");
    }
    
    int main() {
      iterator (0, 10, i)
        printf ("%d\n", i);
    }

    The syntax/grammar of the macro definition is almost identical to the definition of a standard C function and is checked as such by the compiler. Using the macro also respects the C grammar (i.e. that of standard C iterators: for, while etc.).

    Two new reserved keywords have been introduced:

    • macro which indicates that what follows is a macro definition, and
    • {...} which is expanded to the statement used when the macro is called (i.e. to printf ("%d\n", i); in this example).

    Macro expansion

    The expansion of the arguments passed to Basilisk macros is what makes them different from functions. To illustrate this, consider the following example

    macro initialize (scalar s, double expr)
    {
      foreach()
        s[] = expr;
    }
    
    int main() {
      init_grid (16);
      scalar a[];
      initialize (a, sqrt (x*x + y*y));
    }

    It is clear that if initialize was a function (i.e. macro initialize... would just be replaced by void initialize...), this would not compile, because x and y are not defined when initialize is called in main. This works because Basilisk macros are expanded before compilation (like their CPP counterpart). After macro expansion the code would read

    ...
    int main() {
      init_grid (16);
      scalar a[];
      foreach()
        a[] = sqrt (x*x + y*y);
    }

    Note also that in this example the macro does not use the “ellipsis macro” operator {...}.

    Macros returning a value

    As mentioned above, standard C macros cannot ‘return’ a value like functions do. Basilisk macros can. There are two types of “return macros” in Basilisk.

    Inlined return macros

    They are simple macros which behave very much like their standard CPP counterpart. The body of the macro must consist of a single return statement e.g.

    macro number sq (number x) {
      return x*x;
    }

    The returned expression (x*x here) is expanded directly where the macro is called (i.e. it is “inlined”). Note that, unlike what the equivalent C function (not macro) would do, the arguments and the returned value are not implicitly cast to the specified type (number here). So the sq() macro will preserve the type of its arguments i.e. it will work with any numeric type (int, double, float etc.).

    Complex return macros

    Complex return macros return a value which is more complex than a simple expression. They cannot be inlined, because they are composed of several statements. Instead they are expanded as additional statements, which are inserted before the calling statement. In contrast with inlined macros, the type of the return value is automatically cast to the type given in the macro definition.

    Optional arguments

    Since macros share their syntax with that of functions, they can also define optional arguments. The default value None is specific to macros and will be expanded to nothing.

    Breaking out of macro iterators

    By default trying to break out of a macro iterator (like the iterator example above) will cause an error. Breaking must explicitly be set when defining the macro, for example

    macro iterator (int start, int end, int index, break = break) {
      ...
    }

    where breaking uses the standard break statement, or

    macro iterator (int start, int end, int index, break = (index = end + 1)) {
      ...
    }

    where the given expression will be expanded in replacement of the break statement.

    Overloading

    Unlike for C functions, one is allowed to define the same macro several times. The latest definition (in order of appearance in the source file) “overloads” the previous ones.

    It is sometimes useful to break this rule to specify a “default” definition which will only apply if no other definition is present. This can be done by adding the auto “storage class specifier” to the macro definition.

    Inheritance

    Macros implement a simple mechanism for “inheritance”. A “recursive” definition of macros is possible, which allows to include the expansion of a previous definition of a macro into the new (overloaded) definition.

    Non-casting of arguments

    The comment made above regarding the non-type-casting of the return values of inlined macros, also applies to the arguments of any macro. This is different from what happens for standard C functions.

    Postmacros

    Macro definitions can use an “order” parameter which define their expansion priority when preprocessing by qcc. This is used to control which macros are expanded and thus seen or not by the interpreter (only macro are expanded) or by computation kernels (macro and macro1 are expanded).

    This is a low-level functionality which must not be used for “user” macros.

    See also

    Implementation

    typedef struct {
      Ast * statement, * arguments, * parameters, * call, * label, * initial, * scope;
      Stack * sparameters;
      int * returnindex;  
      bool nolineno, complex_call;
      int postmacros;
    } MacroReplacement;
    
    static Ast * argument_value (Ast * identifier, Stack * stack, const MacroReplacement * r)
    {
      Ast * decl = ast_identifier_declaration (r->sparameters, ast_terminal (identifier)->start);
      if (!decl)
        return NULL;
      Ast * value = NULL;
      if (r->arguments) {
        Ast * parent = ast_parent (decl, sym_parameter_declaration);
        Ast * parameters = r->parameters->child[0];
        while (parameters && parameters->child[0]->sym == parameters->sym)
          parameters = parameters->child[0];
        Ast * arguments = r->arguments;
        foreach_item_r (arguments, sym_argument_expression_list_item, argument) {
          Ast * parameter = ast_child (parameters, sym_parameter_declaration);
          parameters = parameters->parent;
          assert (parameter);
          if (parameter == parent) {
    	value = argument;
    	break;
          }
        }
      }
      if (!value) {
        AstTerminal * t = ast_left_terminal (r->call);
        fprintf (stderr, "%s:%d: error: missing '%s' macro parameter\n",
    	     t->file, t->line, ast_terminal (identifier)->start);
        exit (1);
      }
      return value;
    }
    
    static Ast * remove_leading_spaces (Ast * n)
    {
      AstTerminal * t = ast_left_terminal (n);
      for (char * s = t->before; s && *s != '\0'; s++)
        if (!strchr (" \t\n", *s))
          return n;
      free (t->before); t->before = NULL;
      return n;
    }
    
    static void replace_arguments (Ast * n, Stack * stack, void * data)
    {
      const MacroReplacement * r = data;
      Ast * identifier;
      if ((identifier = ast_schema (n, sym_postfix_expression,
    				0, sym_primary_expression,
    				0, sym_IDENTIFIER))) {
        Ast * value = argument_value (identifier, stack, r);
        if (value) {
          if (value->child[0]->sym == sym_assignment_expression) {
    	Ast * primary = ast_is_simple_expression (value->child[0]);
    	if (primary) {
    	  primary = remove_leading_spaces (ast_copy (primary->parent));
    	  ast_set_line (primary, ast_terminal (identifier), true);
    	  Ast * identifier = ast_schema (primary, sym_primary_expression,
    					 0, sym_IDENTIFIER);
    	  if (identifier && !strcmp (ast_terminal (identifier)->start, "None"))
    	    ast_terminal (identifier)->start[0] = '\0';
    	}
    	else
    	  primary = NN(n, sym_primary_expression,
    		       NCA(n, "("),
    		       NN(n, sym_expression_error,
    			  NN(n, sym_expression,
    			     remove_leading_spaces (ast_copy (value->child[0])))),
    		       NCA(n, ")"));
    	ast_replace_child (n, 0, primary);
          }
          else { // postfix_initializer
    	assert (value->child[0]->sym == sym_postfix_initializer);
    	Ast * parent = ast_ancestor (n, 3);
    	assert (ast_schema (parent, sym_cast_expression,
    			    1, sym_type_name));
    	parent->sym = sym_postfix_expression;
    	ast_replace_child (parent, 3, value->child[0]);
    	int index = ast_child_index (parent);
    	Ast * grandparent = parent->parent;
    	ast_set_child (grandparent, index,
    		       NN(grandparent, sym_cast_expression,
    			  NN(grandparent, sym_unary_expression,
    			     parent)));
          }
        }
        else if (r->initial) {
          if (!strcmp (ast_terminal (identifier)->start, "S__FILE__")) {
    	char * filename = NULL;
    	str_append (filename, "\"", ast_left_terminal (r->initial)->file, "\"");
    	ast_replace_child (identifier->parent, 0, NN(n, sym_string,
    						     NB(n, sym_STRING_LITERAL, filename)));
    	free (filename);
          }
          else if (!strcmp (ast_terminal (identifier)->start, "S_LINENO")) {
    	int line = r->nolineno ? 0 : ast_left_terminal (r->initial)->line;
    	char sline[20];
    	snprintf (sline, 19, "%d", line);
    	ast_replace_child (identifier->parent, 0, NN(n, sym_constant,
    						     NB(n, sym_I_CONSTANT, sline)));
          }
        }
      }
      else if ((identifier = ast_schema (n, sym_direct_declarator,
    				     0, sym_generic_identifier,
    				     0, sym_IDENTIFIER))) {
        Ast * value = argument_value (identifier, stack, r);
        if (value) {
          assert (value->child[0]->sym == sym_assignment_expression); // does not know yet how to deal with postfix_initializer
          Ast * replacement = ast_is_identifier_expression (ast_schema (value, sym_argument_expression_list_item,
    								    0, sym_assignment_expression));
          if (!replacement) {
    	AstTerminal * t = ast_left_terminal (value);
    	fprintf (stderr, "%s:%d: error: macro argument '%s' must be a simple identifier\n",
    		 t->file, t->line, ast_terminal (identifier)->start);
    	t = ast_terminal (identifier);
    	fprintf (stderr, "%s:%d: error: because it is used here as a declarator\n",
    		 t->file, t->line);
    	exit (1);
          }
          free (ast_terminal (identifier)->start);
          ast_terminal (identifier)->start = strdup (ast_terminal (replacement)->start);
        }
      }
    }
    
    static void replace_ellipsis (Ast * n, Stack * stack, void * data)
    {
      if (ast_schema (ast_child (n, sym_statement), sym_statement,
    		  0, sym_basilisk_statements,
    		  0, sym_ELLIPSIS_MACRO)) {
        Ast * child = ast_child (n, sym_statement);
        MacroReplacement * r = data;
        ast_set_child (n, ast_child_index (child), ast_copy (r->statement));
        ast_destroy (child);
      }
    }
    
    Ast * ast_is_macro_declaration (const Ast * function_declaration)
    {
      return ast_find (ast_schema (function_declaration, sym_function_declaration,
    			       0, sym_declaration_specifiers),
    		   sym_declaration_specifiers,
    		   0, sym_storage_class_specifier,
    		   0, sym_MACRODEF) ?
        ast_schema (function_declaration, sym_function_declaration,
    		1, sym_declarator,
    		0, sym_direct_declarator,
    		0, sym_direct_declarator,
    		0, sym_generic_identifier,
    		0, sym_IDENTIFIER) : NULL;
    }
    
    static void replace_break (Ast * n, Ast * breaking, Ast * parent)
    {
      if (ast_schema (n, sym_statement,
    		  0, sym_jump_statement,
    		  0, sym_BREAK)) {
        Ast * loop = n->parent;
        while (loop != parent &&
    	   loop->sym != sym_forin_declaration_statement &&
    	   loop->sym != sym_forin_statement &&
    	   loop->sym != sym_iteration_statement &&
    	   (loop->sym != sym_selection_statement ||
    	    loop->child[0]->sym != sym_SWITCH) &&
    	   !ast_is_foreach_statement (loop))
          loop = loop->parent;
        if (loop == parent) {
          if (breaking) {
    	if (breaking->sym == sym_BREAK)
    	  ; // do nothing
    	else {
    	  assert (breaking->sym == sym_assignment_expression);
    	  n->child[0]->sym = sym_expression_statement;
    	  ast_replace_child (n->child[0], 0,
    			     NN(n, sym_expression,
    				ast_copy (breaking)));
    	}
          }
          else {
    	Ast * breaking = ast_schema (n, sym_statement,
    				     0, sym_jump_statement,
    				     0, sym_BREAK);
    	Ast * identifier = ast_schema (ast_parent (breaking, sym_macro_statement), sym_macro_statement,
    				       0, sym_MACRO);
    	fprintf (stderr, "%s:%d: error: cannot break out of macro '%s'\n",
    		 ast_terminal (breaking)->file, ast_terminal (breaking)->line,
    		 ast_terminal (identifier)->start);
    	exit (1);
          }
        }
      }
      else if (n->child)
        for (Ast ** c = n->child; *c; c++)
          replace_break (*c, breaking, parent);
    }
    
    static void replace_return (Ast * n, Stack * stack, void * data)
    {
      if (n->sym == sym_RETURN) {
        Ast * expr = ast_schema (n->parent, sym_jump_statement,
    			     1, sym_expression);
    
        n->sym = sym_GOTO;
        free (ast_terminal (n)->start);
        ast_terminal (n)->start = strdup ("goto");
        MacroReplacement * r = data;
        if (!r->label) {
          char rindex[25]; snprintf (rindex, 24, "_return_%d", (*r->returnindex)++);
          r->label = NN(n, sym_generic_identifier,
    		    NA(n, sym_IDENTIFIER, rindex));
        }
        Ast * label = ast_copy (r->label);
        ast_before (label, " ");
        ast_set_line (label, ast_terminal (n), true);
        if (expr) // 'return expr;' replaced with 'goto label;'
          ast_set_child (n->parent, 1, label);
        else // 'return;' replaced with 'goto label;'
          ast_new_children (n->parent, n->parent->child[0], label, n->parent->child[1]);
          
        Ast * statement = ast_parent (n, sym_statement);
        Ast * parent = statement->parent;
        int index = ast_child_index (statement);
    
        Ast * compound;
        if (r->call->sym == sym_function_call) {
          if (!expr) {
    	fprintf (stderr, "%s:%d: error: 'void' return value in a macro returning non-void\n",
    		 ast_terminal (n)->file, ast_terminal (n)->line);
    	exit (1);
          }
          char * val = strdup (ast_terminal (ast_child(label, sym_IDENTIFIER))->start);
          str_append (val, "val");
          AstTerminal * open = NCB(parent, "{"), * close = NCB(parent, "}");
          Ast * assign = NN(n, sym_statement,
    			NN(n, sym_expression_statement,
    			   NN(n, sym_expression,
    			      NN(n, sym_assignment_expression,
    				 NN(n, sym_unary_expression,
    				    NN(n, sym_postfix_expression,
    				       NN(n, sym_primary_expression,
    					  NB(n, sym_IDENTIFIER, val)))),
    				 NN(n, sym_assignment_operator,
    				    NCB(n, "=")),
    				 ast_child (expr, sym_assignment_expression))),	  
    			   NCB(n, ";")));
          free (val);
    
          compound = NN(parent, sym_statement,
    		    NN(parent, sym_compound_statement,
    		       open,
    		       NN(parent, sym_block_item_list,
    			  NN(parent, sym_block_item_list,
    			     NN(parent, sym_block_item,
    				assign)),
    			  NN(parent, sym_block_item,
    			     statement)),
    		       close));
        }
        else
          compound = statement;
    
        ast_set_child (parent, index, compound);
      }
    }
    
    static
    Ast * get_macro_definition (Stack * stack, const Ast * identifier, Ast * scope)
    {
      Ast * decl = scope ?
        ast_identifier_declaration_from_to (stack, ast_terminal (identifier)->start, scope, NULL) :
        ast_identifier_declaration (stack, ast_terminal (identifier)->start);
      Ast * macro_definition = ast_schema (ast_ancestor (decl, 6), sym_function_definition);
    
      if (!macro_definition) {
        fprintf (stderr, "%s:%d: error: undefined macro '%s'\n",
    	     ast_terminal (identifier)->file, ast_terminal (identifier)->line, ast_terminal (identifier)->start);
        exit (1);
      }
    
      Ast * non_auto_macro = macro_definition;
      while (ast_find (ast_schema (non_auto_macro, sym_function_definition,
    			       0, sym_function_declaration),
    		   sym_declaration_specifiers,
    		   0, sym_storage_class_specifier,
    		   0, sym_AUTO)) {
        Ast * other = ast_identifier_declaration_from_to (stack, ast_terminal (identifier)->start, decl, NULL);
        if (other == decl)
          break;
        non_auto_macro = ast_schema (ast_ancestor (other, 6), sym_function_definition);
        decl = other;
      }
      if (non_auto_macro)
        macro_definition = non_auto_macro;
      
      if (macro_definition == ast_parent (identifier, sym_function_definition)) { // This is a "recursive" macro
        Ast * other = ast_identifier_declaration_from_to (stack, ast_terminal (identifier)->start, decl, NULL);
        if (other == decl) {
          fprintf (stderr, "%s:%d: error: cannot find ancestor of recursive macro '%s'\n",
    	       ast_terminal (decl)->file, ast_terminal (decl)->line, ast_terminal (decl)->start);
          exit (1);
        }
        macro_definition = ast_schema (ast_ancestor (other, 6), sym_function_definition);
      }  
    
      return macro_definition;
    }
    
    static void replace_macros (Ast * n, Stack * stack, void * data)
    {
      if (n->sym == sym_statement || n->sym == sym_function_call) {
        MacroReplacement * r = data;
        ast_macro_replacement (n, r->initial, stack, r->nolineno, r->postmacros, true, r->returnindex, r->scope);
      }
    }
    
    void ast_macro_replacement (Ast * statement, Ast * initial, Stack * stack,
    			    bool nolineno, int postmacros, bool expand_definitions,
    			    int * return_macro_index, Ast * scope)
    {
      Ast * identifier, * macro_statement = ast_schema (statement, sym_statement,
    						    0, sym_basilisk_statements,
    						    0, sym_macro_statement);
      if (macro_statement)
        identifier = ast_child (macro_statement, sym_MACRO);
      else if ((identifier = ast_schema (statement, sym_function_call,
    				     0, sym_postfix_expression,
    				     0, sym_primary_expression,
    				     0, sym_MACRO)))
        macro_statement = statement;
      if (!macro_statement)
        return;
    
      if (!strcmp (ast_terminal (identifier)->start, "OMP_PARALLEL"))
        return;
    
      if (!expand_definitions) {
        Ast * definition = ast_parent (statement, sym_function_definition);
        if (definition && ast_is_macro_declaration (definition->child[0]))
          return;
      }
        
      if (!strcmp (ast_terminal (identifier)->start, "foreach_block") &&
          (inforeach (statement) || point_declaration (stack)))
        str_append (ast_terminal (identifier)->start, "_inner");
    
      Ast * macro_definition = get_macro_definition (stack, identifier, scope);
      if (postmacros >= 0) {
        Ast * macrodef = ast_find (ast_schema (macro_definition, sym_function_definition,
    					   0, sym_function_declaration),
    			       sym_declaration_specifiers,
    			       0, sym_storage_class_specifier,
    			       0, sym_MACRODEF);
        assert (macrodef);
        const char * suffix = ast_terminal (macrodef)->start + 5;
        if (atoi (suffix) > postmacros)
          return;
      }
      
      optional_arguments (macro_statement, stack);
    
      MacroReplacement r = {
        .statement = ast_child (macro_statement, sym_statement),
        .arguments = ast_child (macro_statement, sym_argument_expression_list),
        .parameters = ast_schema (macro_definition, sym_function_definition,
    			      0, sym_function_declaration,
    			      1, sym_declarator,
    			      0, sym_direct_declarator,
    			      2, sym_parameter_type_list),
        .call = macro_statement,
        .nolineno = nolineno,
        .complex_call = !ast_schema (ast_find (macro_definition, sym_compound_statement),
    				 sym_compound_statement,
    				 1, sym_block_item_list,
    				 0, sym_block_item,
    				 0, sym_statement,
    				 0, sym_jump_statement,
    				 0, sym_RETURN),
        .returnindex = return_macro_index,
        .postmacros = postmacros,
        .scope = scope
      };
    
      if (r.parameters) {
        r.sparameters = stack_new (sizeof (Ast *));
        ast_push_declaration (r.sparameters, ast_child (r.parameters, sym_parameter_list));
      }
      
      if (initial) {
        Ast * definition = ast_parent (initial, sym_function_definition);
        if (definition && ast_is_macro_declaration (definition->child[0]))
          r.initial = NULL;
        else
          r.initial = initial;
      }
    
      int na = 0;
      if (r.arguments)
        foreach_item (r.arguments, 2, argument) na++;
      int np = 0;
      if (r.parameters) {
        Ast * list = r.parameters->child[0];
        foreach_item (list, 2, parameter)
          if (!ast_schema (parameter, sym_parameter_declaration,
    		       0, sym_BREAK))
    	np++;
      }
    
      if (na != np) {
        AstTerminal * t = ast_terminal (identifier);
        fprintf (stderr, "%s:%d: error: too %s arguments for macro '%s'\n",
    	     t->file, t->line, na > np ? "many" : "few", ast_terminal (identifier)->start);
        t = ast_left_terminal (macro_definition);
        fprintf (stderr, "%s:%d: error: macro '%s' is defined here\n", t->file, t->line, ast_terminal (identifier)->start);
    #if 0
        ast_print_tree (macro_definition->child[0], stderr, 0, 0, -1);
        ast_print (r.call, stderr, 0);
    #endif
        exit (1);
      }
      
    #if 0
      fprintf (stderr, "***** replacing macro: \n");
      ast_print (macro_statement, stderr, 0);
      fprintf (stderr, "\n===== with: \n");
      ast_print (ast_child (macro_definition, sym_function_declaration), stderr, 0);
      ast_print (ast_child (macro_definition, sym_compound_statement), stderr, 0);
      fputc ('\n', stderr);
    #endif

    Replace ‘break’ with its macro definition (if it exists).

      if (r.statement) {
        Ast * breaking = NULL;
        if (r.parameters && !(breaking = ast_find (r.parameters, sym_parameter_declaration,
    					       2, sym_initializer,
    					       0, sym_assignment_expression)))
          breaking = ast_find (r.parameters, sym_parameter_declaration,
    			   2, sym_BREAK);
        Ast * copy = breaking;
        if (breaking) {
          if (r.parameters) {
    	copy = ast_copy (breaking);
    	stack_push (stack, &copy);
    	ast_traverse (copy, stack, replace_arguments, &r);
    	ast_pop_scope (stack, copy);
          }
        }
        replace_break (r.statement, copy, r.statement);
        if (copy != breaking)
          ast_destroy (copy);
      }
    
      Ast * copy = ast_copy (ast_find (macro_definition, sym_compound_statement));
      stack_push (stack, &copy);
      if (r.parameters) {
        ast_traverse (copy, stack, replace_arguments, &r);
        stack_destroy (r.sparameters);
      }
      if (r.complex_call)
        ast_traverse (copy, stack, replace_return, &r);
      if (r.statement)
        ast_traverse (copy, stack, replace_ellipsis, &r);
      ast_pop_scope (stack, copy);
      str_prepend (ast_left_terminal (copy)->before, ast_left_terminal (macro_statement)->before);
    
      stack_push (stack, &copy);
      ast_traverse (copy, stack, replace_macros, &r);
      ast_pop_scope (stack, copy);

    Return label

      if (r.label) {
        ast_set_line (r.label, ast_right_terminal (copy), true);
        ast_block_list_append (ast_schema (copy, sym_compound_statement,
    				       1, sym_block_item_list), sym_block_item,
    			   NN(copy, sym_statement,
    			      NN(copy, sym_labeled_statement,
    				 r.label,
    				 NCA(copy, ":"),
    				 NN(copy, sym_statement,
    				    NN(copy, sym_expression_statement,
    				       NCA(copy, ";"))))));
      }

    Statement

      if (statement->sym == sym_statement) {
        if (statement->parent->sym == sym_block_item) {
          Ast * list = ast_schema (copy, sym_compound_statement,
    			       1, sym_block_item_list);
          Ast * parent = ast_ancestor (statement, 2);
          assert (parent->sym == sym_block_item_list);
          foreach_item (list, 1, item) {
    	if (ast_child_index (item))
    	  ast_block_list_append (parent, sym_block_item, item->child[0]);
    	else {
    	  ast_before (item, ast_left_terminal (statement)->before);
    	  ast_set_child (parent, parent->child[1] ? 1 : 0, item);
    	}
          }
          ast_destroy (copy);
        }
        else
          ast_set_child (statement, 0, copy);
        ast_destroy (macro_statement);
      }

    Function call

      else {
        assert (statement->sym == sym_function_call);
        Ast * jump = ast_schema (copy, sym_compound_statement,
    			     1, sym_block_item_list,
    			     0, sym_block_item,
    			     0, sym_statement,
    			     0, sym_jump_statement);
        if (ast_schema (jump, sym_jump_statement,
    		    0, sym_RETURN)) {
          assert (!r.complex_call);

    This is a simple return macro i.e. ‘macro int func(…){ return …; }’.

          Ast * expr = ast_schema (jump, sym_jump_statement,
    			       1, sym_expression);
          if (!expr) {
    	AstTerminal * t = ast_terminal (identifier);
    	fprintf (stderr, "%s:%d: error: using value of macro '%s' returning void\n",
    		 t->file, t->line, ast_terminal (identifier)->start);
    	t = ast_terminal (ast_schema (jump, sym_jump_statement,
    				      0, sym_RETURN));
    	fprintf (stderr, "%s:%d: error: return value is defined here\n", t->file, t->line);
    	exit (1);
          }
          AstTerminal * o = NCB(statement, "("), * c = NCB(statement, ")");
          statement->sym = sym_primary_expression;
          for (Ast ** c = statement->child; *c; c++)
    	ast_destroy (*c);
          ast_set_line (expr, o, true);
          ast_new_children (statement, o,
    			NN(expr, sym_expression_error,
    			   expr), c);
          ast_destroy (copy);
        }
        else {

    This is a complex return macro.

          assert (r.complex_call);
          assert (r.label);
          
          Ast * returntype = ast_schema (macro_definition, sym_function_definition,
    				     0, sym_function_declaration,
    				     0, sym_declaration_specifiers,
    				     1, sym_declaration_specifiers);
          if (!returntype) {
    	fprintf (stderr, "%s:%d: error: the type of a macro returning a value must be defined\n",
    		 ast_left_terminal (macro_definition)->file,
    		 ast_left_terminal (macro_definition)->line);
    	exit (1);
          }
    
          char * label = strdup (ast_terminal (ast_schema (r.label, sym_generic_identifier,
    						       0, sym_IDENTIFIER))->start);
          str_append (label, "val");
          Ast * declaration = NN(copy, sym_declaration,
    			     ast_copy (returntype),
    			     NN(copy, sym_init_declarator_list,
    				NN(copy, sym_init_declarator,
    				   NN(copy, sym_declarator,
    				      NN(copy, sym_direct_declarator,
    					 NN(copy, sym_generic_identifier,
    					    NB(copy, sym_IDENTIFIER, label)))))),
    			     NCB(copy, ";"));
    
          AstTerminal * val =  NB(statement, sym_IDENTIFIER, label);
          for (Ast ** c = statement->child; *c; c++)
    	ast_destroy (*c), *c = NULL;
          statement->sym = sym_primary_expression;
          ast_set_child (statement, 0, (Ast *) val);
          
          Ast * parent = statement->parent;
          while (parent->sym != sym_statement && parent->sym != sym_declaration)
    	parent = parent->parent;
          
          Ast * gp = parent->parent;
          if (gp->sym == sym_block_item) {
    	Ast * s = NN(gp, sym_statement, copy);
    	ast_block_list_insert_before2 (gp, s);
    	assert (s->parent->sym == sym_block_item);
    	ast_block_list_insert_before2 (s->parent, declaration);
          }
          else {
    	int index = ast_child_index (parent);
    	AstTerminal * open = NCB(gp, "{"), * close = NCA(parent, "}");
    	Ast * compound = NN(gp, sym_statement,
    			    NN(gp, sym_compound_statement,
    			       open,
    			       NN(gp, sym_block_item_list,
    				  NN(gp, sym_block_item_list,
    				     NN(gp, sym_block_item_list,
    					NN(gp, sym_block_item,
    					   declaration)),
    				     NN(gp, sym_block_item,
    					NN(gp, sym_statement,
    					   copy))),
    				  NN(gp, sym_block_item,
    				     parent)),
    			       close));
    	ast_set_child (gp, index, compound);
          }
    
          free (label);
        }
      }
    }