Using opDispatch to generate properties

opDispatch is a good hook for code generation of all kinds. Here, we'll use it to generate properties to access an associative array of keys in a different format in order to mimic the style property in the JavaScript DOM, which translates foo.style.backgroundColor, for example, to the background-color CSS property.

How to do it…

Let's execute the following steps to use opDispatch to generate properties:

  1. Write a function to transform the camelCase string to a dash-separated string.
  2. Create a struct type with two opDispatch @property methods: a getter that takes no runtime arguments and a setter which takes a string runtime argument. Both the opDispatch methods should take one compile-time string argument.
  3. Use the enum keyword with the transformation function to ensure it is done at compile time.
  4. Constrain opDispatch to work only on the properties you want to enable, or if you want it to work on everything, set it minimally to not work on popFront.
  5. Add a member and constructor that points to the associative array it will manipulate.
  6. Test it by creating a small DomElement class with a style property that returns the Style struct and a main function which tries to use it.

The code for the preceding steps is as follows:

// transformation function
string unCamelCase(string a) {
  import std.string;
  string ret;
  foreach(c; a)
    if((c >= 'A' && c <= 'Z'))
      ret ~= "-" ~ toLower("" ~ c);
    else
      ret ~= c;
  return ret;
}

private struct Style {
  // pointer to the associative array we'll modify
  string[string]* aa;
  this(string[string]* aa) {
    this.aa = aa;
  }

  // getter
  @property string opDispatch(string name)() if(name != "popFront") {
    enum n = unCamelCase(name); // step 3
    return (*aa)[n];
  }

  // setter
  @property string opDispatch(string name)(string value) if(name != "popFront") {
    enum n = unCamelCase(name); // step 3
    return (*aa)[n] = value;
  }
}

class DomElement {
  string[string] styles;
  // step 6
  @property Style style() {
    return Style(&styles);
  }
}

void main() {
  auto element = new DomElement();
  element.style.backgroundColor = "red";
  element.style.fontSize = "1em";

  import std.stdio;
  writeln(element.styles);
  writeln(element.style.backgroundColor);
}

By running the preceding program, we will get the following output:

["background-color":"red", "font-size":"1em"]
red

This shows that the properties transformed properly for both reading and writing.

How it works…

opDispatch is invoked whenever a non-existent member is requested of an aggregate with the dot syntax. It is passed with the name of the requested member as a compile-time parameter. Otherwise, it is just a regular template and may provide code or data.

Note

It is strongly recommended to always put a constraint on opDispatch, because without it, the compile-time duck typing will be completely thrown off. For example, the isInputRange function will return true because the missing methods of empty, popFront, and front will be provided by opDispatch. This will result in functions such as writeln trying to iterate over the object as a range and print its contents. However, since they weren't intentionally written, it is unlikely that the object will actually work as a range.

Since the name is passed as a compile-time parameter, it can be used for all kinds of compile-time transformations, including changing the string into a new kind of literal and even mixing it in as a domain-specific language.

Here, we used it to change the format of a string to generate thin accessor properties. The code is very straightforward; a regular function manipulates the string. Then, we use the enum keyword to force the manipulation to be done at compile time for maximum runtime performance and use it to look up data in the associative array.

We also wanted the generated properties to work on the child property, style. Since opDispatch only works with one level at a time, we created a helper object and property function to expose it. The helper object needs a reference to the data it modifies, which we pass in the constructor as a pointer. We could have also passed a reference to the DomElement class. Even if the Style struct were nested inside the DomElement class, it will still be necessary to explicitly pass it a reference to avoid compile errors.

When dot syntax is used on the style property, the child properties are generated transparently and on demand by opDispatch.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.226.88.110