Template Example

As with any programming exercise, eventually you will encounter the need to do something repetitive. There are a lot of opportunities for this situation to arise in Wrapture specs: classes that wrap enumerations, identical error handling code in a library, or functions with very similar specifications. Wrapture provides a generalized capability to avoid repeating yourself called templates, which allow you to define any spec structure and define variables within them that can be replaced when they are invoked.

For this example, we’ll consider a fictional library that is able to answer just about any mathematical question you ask it. We want to extend this functionality so that it feels a little more natural in the target language. Lots of object oriented languages expose their math functions in a class with static methods, so we decide to do the same here:

classes:
  - name: "MagicMath"
    namespace: "magic_math"
    functions:
      - name: "IsMagical"
        static: true
        params:
          - name: "num"
            type: "int"
        return:
          type: "bool"
        wrapped-function:
          name: "is_magical"
          params:
            - value: "num"
          includes: "magic_math.h"
      - name: "IsPrime"
        static: true
        params:
          - name: "num"
            type: "int"
        return:
          type: "bool"
        wrapped-function:
          name: "is_prime"
          params:
            - value: "num"
          includes: "magic_math.h"
      - name: "IsRandom"
        static: true
        params:
          - name: "num"
            type: "int"
        return:
          type: "bool"
        wrapped-function:
          name: "is_random"
          params:
            - value: "num"
          includes: "magic_math.h"

But goodness, there sure is a lot of repetition in this specification. For each of these function, their bodies are almost identical - the only differences are their names, their parameter names, and the names of their wrapped functions. In the words of many an infomercial, there has to be a better way!

This is a perfect use case for Wrapture templates. We can define the repeated specification code as a template with parameters for the changing values, and simply invoke this template once for each function, resulting in much more concise specifications.

Templates can be inserted in any portion of a specification, for any use. They are defined in a separate portion of the specification from the classes, aptly named templates. Let’s look at the template specification for our extremely scientific library:

templates:
  - name: "magical-static-function"
    value:
      static: true
      params:
        - name: "num"
          type: "int"
      return:
        type: "bool"
      wrapped-function:
        name:
          is-param: true
          name: "wrapped-name"
        params:
          - value: "num"
        includes: "magic_math.h"

This template closely matches our function specifications above, with a few obvious differences. First, the template has a name associated with it that is referenced when we want to use it. Second, the name of the wrapped function is no longer a string value: it is an object with a member called is-param set to true. The presence of this key signifies that this is a parameterized portion of the template that we can replace as needed.

Now that we have a template defined, let’s see how we would use it to remove the redundancy from our previous declarations:

functions:
  - name: "IsMagical"
    use-template:
      name: "magical-static-function"
      params:
        - name: "wrapped-name"
          value: "is_magical"
  - name: "IsPrime"
    use-template:
      name: "magical-static-function"
      params:
        - name: "wrapped-name"
          value: "is_prime"
  - name: "IsRandom"
    use-template:
      name: "magical-static-function"
      params:
        - name: "wrapped-name"
          value: "is_random"

The declaration code for each function has been shortened from twelve lines to six, cutting it in half. More importantly, we have placed the common code into a single spot (the template) where it can be modified once and reflected at all use sites, a clean example of the DRY principle. Verbosity and ease of maintenance will continue to improve as we add more to the function specs, such as error handling.

If you’d like to run this example in its entirety, you can do so with the following invocations:

# generating the wrapper source code
wrapture magic_math.yml

# assuming that you're using sh and have g++
g++ -I . \
   magic_math.c MagicMath.cpp magic_math_usage.cpp \
   -o magic_math_usage_example

./magic_math_usage_example
# generates the following output:
# listing primes from 0 to 100:
# 2
# 3
# 5
# 7
# 11
# 13
# 17
# 19
# 23
# 29
# 31
# 37
# 41
# 43
# 47
# 53
# 59
# 61
# 67
# 71
# 73
# 79
# 83
# 89
# 97