52

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?

Brad Larson
  • 170,088
  • 45
  • 397
  • 571
  • id like this feature (multiline string literals) too. e.g. Xtend-Lang has this one. – Christian Dietrich Jun 19 '14 at 17:40
  • 9
    Finding a way to do this without macros sounds like a good [feature request](http://bugreport.apple.com). – rickster Jun 19 '14 at 17:47
  • 4
    Looking through the all the Swift info that I can find in regards to strings, I can't find any mention of multiline string literals, and it doesn't look like there a way to do this. I agree with rickster, this needs to be made into a feature request. Apple has said that they are going to pay attention to feedback, and this is precisely the sort of thing that we need provide feedback for. – CrimsonDiego Jun 19 '14 at 17:58
  • 1
    Yeah, I was hoping with all the crazy stuff people are doing with operator overloading, etc. that there was something I'd missed. Multiline strings of some form seem like they should be supported. – Brad Larson Jun 19 '14 at 18:02
  • my vote would be shell/ruby here document `let x = < – natbro Jun 19 '14 at 19:05
  • In some of my code that deals with shader snippets, I've been writing an operator overload that concatenates and inserts a newline; e.g. `"attribute vec4 position;" /` (newline) `"attribute vec4 normal;"` ->`"attribute vec4 position;\nattribute vec4 normal;"`. But it's less than ideal. – rickster Jun 19 '14 at 20:24
  • @rickster - The thing I'd really love to do is to live-prototype shaders in the playground, either with my framework or with plain OpenGL (ES). All I need is a clean way of describing the shaders. – Brad Larson Jun 19 '14 at 20:47
  • 1
    +1 for heredocs in Swift. Doesn't the macro have the bonus feature of Xcode highlight the syntax, though? I'm guessing most of the graphics code will still be written in Objective-C. – wjl Jun 20 '14 at 00:49
  • @wjl - It only gets some of the syntax highlighting (it treats GLSL like C, missing the vec4, etc. elements), but all I care about is easy copy and paste between shader files and inlined code. – Brad Larson Jun 20 '14 at 00:55
  • The closest I can get is `"\n".join(["first line", "next line", ...])`. I think much better than multiline strings would be the ability to embed separate files as string literals in code. – Pyry Jahkola Jun 20 '14 at 11:30
  • 2
    Oh, an one more thing, the `STRINGIZE(x)` macro is nice but I should point out that its argument gets macro expanded before the conversion into string, so e.g. `STRINGIZE(YES NO NULL)` will not turn into `"YES NO NULL"` but `"__objc_yes __objc_no ((void *)0)"`. Not that it's that likely with GLSL, though. – Pyry Jahkola Jun 20 '14 at 11:36
  • @pyrtsa - Yeah, that can lead to some interesting artifacts: http://stackoverflow.com/a/21213741/19679 – Brad Larson Jun 20 '14 at 14:00
  • @BradLarson Yeah, that's a better example that actually can happen. – Pyry Jahkola Jun 22 '14 at 20:56
  • What's the problem with using concatenation? In C\ObjC you separated each line with a "\" whereas in swift you would separate each line with a "+" – Alex Zielenski Jul 14 '14 at 03:21
  • @AlexZielenski - Concatenation makes it impossible to copy and paste shader code from actual shader files into string constants like this. Every single line would need to be reformatted, which would be an incredible pain for both insertion into code, as well as for pulling these out later into shader files if needed. The macro I had been using allowed for format and readability of the shaders to be preserved when using them as string constants. Also, there were some compiler issues with multiline string concatenation when I tried last (which hopefully should eventually go away). – Brad Larson Jul 14 '14 at 04:58

2 Answers2

4

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"
  ])
Community
  • 1
  • 1
holroy
  • 3,047
  • 25
  • 41
-1

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.

whitneyland
  • 10,632
  • 9
  • 60
  • 68
  • Inlined shaders are but one application of this. Even when it comes to shaders, I'd love to do rapid prototyping of shaders within a Swift playground by using multiline strings for them. As I'd edit the shader string, the result would be recomputed for display. Some kind of scripted command-line tool wouldn't be able to help there. Multiline string literals are a base capability that should be present in the language for a slew of different applications. – Brad Larson Aug 06 '14 at 20:26
  • Yes they could add support for multi-line literals without adding macros back and would still be quite helpful. I understand the macro part but the other seems like a no brainer to put into swift. – whitneyland Aug 06 '14 at 20:35