How can you implement this multiline string literal macro in Swift?

| | August 10, 2015

In my Objective-C code for my GPUImage framework, I have the following macro:

#define STRINGIZE(x) #x
#define STRINGIZE2(x) STRINGIZE(x)
#define SHADER_STRING(text) @ STRINGIZE2(text)

which allows me to inline multiline vertex and fragment shaders as NSString literals within my custom filter subclasses, like this:

NSString *const kGPUImagePassthroughFragmentShaderString = SHADER_STRING
(
 varying highp vec2 textureCoordinate;

 uniform sampler2D inputImageTexture;

 void main()
 {
     gl_FragColor = texture2D(inputImageTexture, textureCoordinate);
 }
);

GPUImage needs this in order to provide formatted vertex and fragment shaders that are included in the body text of filter subclasses. Shipping them as separate files would make the framework unable to be compiled into a static library. Using the above macro, I can make these shaders able to be copied and pasted between the framework code and external shader files without a ridiculous amount of reformatting work.

Swift does away with compiler macros, and the documentation has this to say:

Complex macros are used in C and Objective-C but have no counterpart
in Swift. Complex macros are macros that do not define constants,
including parenthesized, function-like macros. You use complex macros
in C and Objective-C to avoid type-checking constraints or to avoid
retyping large amounts of boilerplate code. However, macros can make
debugging and refactoring difficult. In Swift, you can use functions
and generics to achieve the same results without any compromises.
Therefore, the complex macros that are in C and Objective-C source
files are not made available to your Swift code.

Per the line “In Swift, you can use functions and generics to achieve the same results without any compromises”, is there a way in Swift to provide multiline string literals without resorting to a string of concatenation operations?

2 Responses to “How can you implement this multiline string literal macro in Swift?”

  1. Alas Swift multiline strings are still not available, as far as I know. However when doing some research regarding this, I found a workaround which could be useful. It is a combination of these items:

    Setup an automated service

    Using Automator you could set up an extra service with the following properties:

    • A single action of “Run Shell Script”
    • Tick off the “Output replaces selected text”
    • Change shell to /usr/bin/perl
    • Add the code excerpt below to the action window
    • Save as something like “Replace with quoted swift multiline join”

    Code excerpt

    print ""\n".join([n";   # Start a join operation
    
    # For each line, reformat and print
    while(<>) {
      print "    ";         # A little indentation 
      chomp;                # Loose the newline
      s/([\"])/\$1/g;    # Replace  and " with escaped variants
      print ""$_"";       # Add quotes around the line 
    
      print "," unless eof  # Add a comma, unless it is the last line
      print "n";           # End the line, preserving original line count
     }
    
    print "  ])"; # Close the join operation
    

    You are of course free to use whatever shell and code you want, I chose perl as that is familiar to me, and here are some comments:

    • I used the "n".join(...) version to create the multiline string, you could use the extension answer from Swift – Split string over multiple lines, or even the + variant, I’ll leave that as an exercise for the user
    • I opted for a little indentation with spaces, and to replace the and " to make it a little sturdier
    • Comments are of course optional, and you could probably shorten the code somewhat. I tried to opt for clarity and readability
    • The code, as is, preserves spaces, but you could be edited if that is not wanted. Also left as an exercise for the user

    Usage of service

    Open up your playground or code editor, and insert/write some multline text:

    • Mark the text block
    • Execute Xcode (or similar) > Services > Replace with quoted swift multiline join

    You now have a multiline string in proper swift coding. Here are an example of before and after text:

    Here is my multiline text 
    example with both a " and
    a  within the text
    
    "n".join([
        "Here is my multiline text ",
        "example with both a " and",
        "a \ within the text"
      ])
    
  2. It looks like your end goal is to avoid including standalone shader files?

    If so one technique would be to write a quick command line utility that generates a .swift file of string constants representing the shader functions in a certain folder.

    Include the resulting .swift file in your project and you have no runtime penalty, and even easier debugging if you generate the code nicely.

    Would probably take less than an hour, never need macros again for shaders.

Leave a Reply