Creating user-defined literals

D doesn't have specialized syntax for user-defined literals, but using the template syntax with the right implementation, we can generate the same code as if it did. A distinguishing factor of user-defined literals is that there must be zero runtime cost to use them. Here, we'll write the code to enable integer literals written in octal, a simplified version of the implementation of octal in Phobos' std.conv module.

Getting ready

First, let's write an octal number parser, converting from string to integer. The algorithm is simple: read a digit, multiply our accumulator by eight, and then add the value to the accumulator until we're out of digits. Any invalid digit is an exception. All these steps are shown in the following code:

int readOctalString(string n) {
  int sum = 0;
  foreach(c; n) {
    if(c < '0' || c > '7')
      throw new Exception("Bad octal number " ~ n);
    sum *= 8;
    sum += c - '0';
  }

  return sum;
}

unittest {
  assert(readOctalString("10") == 8);
  assert(readOctalString("15") == 13);
  assert(readOctalString("4") == 4);
  import std.exception;
  assertThrown!Exception(readOctalString("90"));
}

How to do it…

Let's execute the following steps to create user-defined literals:

  1. Write a regular function to get the value you want from a string.
  2. Write a template that takes a string parameter and whose body consists of one line: enum myname = yourFunction(s);.
  3. Write templates that take other types of literal, if appropriate, and convert them to string to forward them to the template discussed in the preceding step.

The code is as follows:

// step 2:
template octal(string s) {
  enum octal = readOctalString(s);
}
// step 3: octals also make sense with some int literals
template octal(int i) {
  import std.conv;
  enum octal = octal!(to!string(i));
}
// usage test:
void main() {
  import std.stdio;
  writeln(octal!10);
  writeln(octal!"15");
  writeln(octal!4);
}

It prints 8, 13, and 4, matching our preceding unit test. Let's also confirm the compiler outputs the same code as it will for a normal integer literal by looking at the following disassembly:

$ objdump -d -M intel simpleoctal | grep _Dmain -A 30
0808f818 <_Dmain>:
 808f818:       55                      push   ebp
 808f819:       8b ec                   mov    ebp,esp
 808f81b:       b8 08 00 00 00          mov    eax,0x8
 808f820:       e8 e3 7d 00 00          call   8097608 <_D3std5stdio14__T7writelnTiZ7writelnFiZv>
 808f825:       b8 0d 00 00 00          mov    eax,0xd
 808f82a:       e8 d9 7d 00 00          call   8097608 <_D3std5stdio14__T7writelnTiZ7writelnFiZv>
 808f82f:       b8 04 00 00 00          mov    eax,0x4
 808f834:       e8 cf 7d 00 00          call   8097608 <_D3std5stdio14__T7writelnTiZ7writelnFiZv>
 808f839:       31 c0                   xor    eax,eax
 808f83b:       5d                      pop    ebp
 808f83c:       c3                      ret

The relevant lines are mov eax, 0x8, mov eax, 0xd, and mov eax, 0x4. They are indistinguishable from any other integer literals, and there are no calls to octal or readOctalString in sight! (The calls after those lines are to writeln.)

How it works…

This is an example of the general pattern we'll be exploring in this chapter. We will write a regular function to convert input from one form to another, and then use D to evaluate that function at compile time to generate code or data.

The enum keyword is central to our result. In D, the enum keyword is used for all named constants that do not have a memory address. This, of course, includes traditional enumerations such as enum Color { red, green, blue }, and it also works as a single value. In both cases, the value works just like a literal value at the usage site, as if you copied and pasted the value straight to the usage point.

Note

An enum structure is similar to a #define statement in C. The difference is that the value of an enum structure is always determined at compile time, even if it is assigned to a function, and enum values in D always have a specific type; they are not just text to be transplanted into code.

When you declare an enum structure with a value, the right-hand side of the equation is immediately evaluated at compile time, regardless of complexity, and it will cause a compile-time error if this is unsuccessful for any reason. This is an extension of constant folding as seen in almost all the compiled languages, where if you write 1 + 2, the compiler automatically translates that to 3 instead of a literal adding an instruction in the compiled program. D simply takes that concept much farther.

Therefore, the enum keyword is the D idiom to force a compile-time function evaluation to create a literal in the generated code.

Tip

The enum keyword is not the only way to trigger compile-time evaluation, and it is not always the best way. It is also run for a static initializer, such as a static variable or an initializer in a class or a struct definition, or anywhere else where the regular code can not be immediately run. Since the enum array literals always allocate at the usage point, they are often better represented as a static immutable variable.

The next task is to ensure we get the enum keyword's behavior without being forced to do the following tedious two-step process:

enum ourOctalPermissionLiteral = readOctalString("755");
chmod("my_file", ourOctalPermissionLiteral);

Since the compile-time evaluation and literal behavior only happens in specific contexts like enum, trying to call the readOctalString function directly will produce a plain runtime function call. We need to use the enum keyword as an intermediary. That's where the short octal template comes into play.

A template is fundamentally a block of code with parameters which act as placeholders that are replaced at the usage point to generate new code. We've used them extensively throughout this book to create new types and functions. Here, we're using a template to create an enum value from a string placeholder.

When the octal template is used, the enum structure is created on the spot and substituted for the template, giving us the convenience of template usage syntax with the behavior of enum literals, achieving our goal.

See also

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

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