Code Blocks

A code block is a set of statements enclosed in curly braces ( {} ). What is significant about code blocks in Heron is that they can be named, passed as templates arguments and they can be expanded. In other words they are uninstantiable types.

Naming Code Blocks

Code blocks in Heron can be named by creating a type-alias to them using the keyword define. My personal favourite code block is the dump_location code block defined in the heron.meta module.

  define dump_location : {
    print_string("\nfunction : ");
    print_string($_function_name);
    print_string("\nfile : ");
    print_string($_file_name);
    print_string("\nline number : ");
    print_int($_line_number);
    print_string("\nmodule name : ");
    print_string($_module_name);
    print_string("\nprogram name : ");
    print_string($_program_name);
    print_string("\n");
  };

Expanding Code Blocks

A code-block can be expanded anywhere using the meta-expansion operator ( # ) as follows:

  program dump_location_demo;
  functions
    _main() {
      #dump_location
    }
  end

This would be translated by the compiler to the following code:

  program dump_location_demo;
  functions
    _main() {
      {
        print_string("\nfunction : ");
        print_string($_function_name);
        print_string("\nfile : ");
        print_string($_file_name);
        print_string("\nline number : ");
        print_int($_line_number);
        print_string("\nmodule name : ");
        print_string($_module_name);
        print_string("\nprogram name : ");
        print_string($_program_name);
        print_string("\n");
      }
    }
  end

The output is something similar to the following:

  function : _main
  file : \hrn\src\demo.hrn
  line number : 4
  module name : dump_location_demo
  program name : dump_location_demo

That is a pretty simple example, things get more interesting when you start using named code-blocks with type parameters.

Effect of Introducing Meta-Expansion into Heron

From an implementation and language design standpoint, the meta-expansion operator was a very simple addition to the Heron language. The changes required to Heron to incorporate meta-expansion was simply to allow the treatment of code-blocks as uninstantiable types and to introduce the meta-expansion operator.

Meta-Expansion instead of Macros

Meta-expansions along with the other metaprogramming functionality makes macros redundant in Heron, so they are left out. Code blocks are much safer than macros because they accept only types as parameters, and not arbitrary tokens. This makes them better integrated into the language, less prone to paranthesis or comma errors. This also makes it much easier to compile and understand Heron code.

Meta-Expansion and Aspect Oriented Programming

The meta-expansion operator is crucial part of aspect oriented programming support in Heron. There is an implicit meta-expansion statement at the beginning ( #_before and end ( #_after ) of each function call. Aspect oriented programming is made possible by allowing the programmer to override _before and _after code blocks at different scopes.

Example: A Regular Expression Parser

Code blocks and meta-expansion can be an extremely useful tool for generating programs at compile time, or in other words doing metaprogramming. An interesting example of metaprogramming is the regular expression parsing library demo program:

  program redemo;
  /*
    This program extracts identifiers from a file and outputs them one per line.
  */
  types {
    define p_idents : {
      while (!AtEnd()) {
        #re<p_ident>
        if (result) {
          P(GetLastMatch());
        }
        else {
          IncIndex();
        }
      }
    };
  }
  functions {
    _main() {
      String sInput;
      sInput = LoadStringFromFile($_file_name);
      RegExParser<p_idents> parser;
      parser.Parse(sInput);
      wait_for_keypress();
    }
  }
  end

The RegExParser class is a recursive descent parrser, instantiated with a code block which defines the core matching action of the parser. Notice that the code block p_idents which matches identifiers, until the end of input is reached, uses an expanded code block ( #re<p_ident> ) during the iterative step. If we look into the heron.re unit we find various types for building regular expression parsers such as the following:

  define re<type T> :
  {
    result = true;
    PushIndex();
    #T
    if (!result) {
      RestoreIndex();
    }
    else {
      SetLastMatch(PopIndex(), GetIndex());
    }
  };

  define re_opt<type T> :
  // attempts to match T once
  // sets result to true automatically
  {
    #re<T>
    result = true;
  };

  define re_and<type T> :
  {
    if (result) {
      #T
    }
  };

  define re_repeat<type T, meta_int n> :
  // attempts to match T n times
  {
    for (Int i(0); i < $n; i++) {
      #T
    }
  };

Using metaprogramming techniques is not crucial to building a recursive descent parser, but it can be a useful way of improving performance by removing the function overhead. It also allows us to easily inline whatever semantic actions we want.