A Short Swift GYB Tutorial

Posted by Umberto Raimondi on February 9, 2016 中文版

The GYB (Generate Your Boilerplate) tool is a Swift tool intended for internal use that generates source files starting from a template.

It’s extremely useful when you have more than one struct/class/enum that share a common structure, and you are not eager to maintain multiple versions of what is, actually, the same code. Every time you are writing the same set of methods or properties for slightly different objects, your maintenance effort (and bugs resulting from careless copy/paste) could be reduced using GYB. The tool is used extensively throughout the Swift codebase and there are just a few things you need to know to use it in your projects.

As a diligent guinea-pig (AFAIK, the only other project that uses GYB at the moment is Swift-JNI, part of the Android Swift porting project), I’ve used GYB extensively in Bitter, a Swift library that simplifies working with bits and bitwise operations, where I had a lot of very similar code inside extensions for each one of the fixed size Swift Ints.

With this tool, I was able to define a single template that the tool was able to expand to the 10 separate extensions I initially coded by hand.

Let’s see what you need to know to start using GYB.

GYB Engine Elements

The GYB templating engine is quite simple but requires a minimal knowledge of Python. A template is composed by these elements:

  • Lines starting with % (leading whitespace is ignored) are followed by Python code and are used for control flow statements, as you would expect those statement are closed with an %end. Statements can be nested.

  • Lines that do not start with an % are treated as text and simply inserted into the output.

  • Elements with the form ${VARIABLE_NAME} are replaced with the value of VARIABLE_NAME

  • The % and $ characters are escaped respectively as %% and $$.

  • Blocks of Python code can be added to the template and are delimited by %{ and }% , the indentation outside the block is stripped, so, it’s irrelevant for your Python code.

Let’s see what we can do with these few simple rules with an example, taken from the Bitter template, that adds to all fixed size integers a computed property allOnes, that returns an Int/UInt initialized with a bit pattern with all ones:


%{
  intTypes = [8,16,32,64]
}%

% for intType in intTypes:
    % for sign in ['','U']:

/// Extension that adds a few additional functionalities to ${sign}Int${intType}
extension ${sign}Int${intType} {

        /// Returns a ${sign}Int${intType} with all ones
        %if sign == '':
    public static var allOnes:Int${intType}{return Int${intType}(bitPattern: UInt${intType}.max)}
        %else:
    public static var allOnes:UInt${intType}{return UInt${intType}.max}
        %end

}
    %end
%end

With a Python block we declare an array with all the fixed sizes of the Ints available in Swift and then iterate over it, using an internal loop to consider signed and unsigned integers too. We than output two different snippets depending on the value of the sign variable, we’ll output the first one if the sign variable is empty (signed integers) or the second one if it’s not (unsigned integers).

In this example we are using a simple if/else and foreach statements, but we could have used everything that is valid in Python like an elif or a variation of that for.

Running this through GYB we’ll get 8 extensions, one for each fixed size integer, from Int8/UInt8 to Int64/UInt64.

Generating the source

You can download GYB from the Swift repository:


wget https://github.com/apple/swift/raw/master/utils/gyb
wget https://github.com/apple/swift/raw/master/utils/gyb.py
chmod +x gyb

And parse your template this way:


./gyb --line-directive '' -o ../Sources/Bitter/Bitter.swift Bitter.swift.gyb

The -o option specifies the output file, while the last filename specifies the name of the file containing the template.

Without the --line-directive '' parameter, GYB outputs additional debugging comments, that for each section of the output describe which element in the original template was executed to generate it.

Useful while you are still in the process of writing your template but once you are done the debug comments can be disabled to obtain a clean output.

Did you like this article? Let me know on Twitter or subscribe for updates!

This is the blog of Umberto Raimondi, you can reach me at me@this domain.

I'm also on Twitter and GitHub.

Subscribe via RSS or email.