Using Swift As a Scripting Language

Much fanfare accompanied the “playground” feature of Xcode 6 that allows developers to write some code and see the effect in realtime. If you have any experience with scripting languages such as Ruby or Python, you recognize this as nothing more than running the interactive language interpreter. With Ruby you type irb at the command line; for Python try python.


$ irb
1.9.3-p448 :001 > fruit = ['apples', 'pears', 'bananas']
=> ["apples", "pears", "bananas"]


$ python
Python 2.7.6 (default, May 30 2014, 22:21:15)
[GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.34.2)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> fruit = ['apples', 'pears', 'bananas']

For some reason Swift employs the more technical name of an interpreter, the REPL, or Read-Eval-Print-Loop. The interpreter reads what you entered, evaluates it, prints it, and then loops back to read. The REPL isn’t magic, and its not anything new, it’s just new to those whose only experience with iOS and Mac development is Objective-C. Objective-C (and it’s C and C++ brethren) use the ECLR paradigm: Edit-Compile-Link-Run.

It stands to reason that if you can run a Swift interpreter in Xcode, you can run it from the commandline. And sure enough you can you just have to dig down into the Xcode 6 package contents and find the binary to run. So open up a Terminal or iTerm 2 and type into your shell:


export PATH="/Applications/Xcode6-Beta.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/":$PATH

Now, this assumes that the Xcode6-Beta application was installed into /Applications. If you are running it off of your desktop or some other location, adjust the path accordingly. For those unfamiliar with the Unix PATH environment variable, check out Wikipedia. Let’s fire up the interactive Swift interpreter!


$ swift -repl
Welcome to Swift! Type :help for assistance.
1>

Create an array similar to the examples above:


1> fruit = ["apples", "pears", "bananas"]
:1:1: error: use of unresolved identifier 'fruit'
fruit = ["apples", "pears", "bananas"]
^

Okay, so we know that we still have to use the Swift syntax for declaring variables. We can cope.


1> var fruit = ["apples", "pears", "bananas"]
fruit: String[] = size=3 {
[0] = "apples"
[1] = "pears"
[2] = "bananas"
}

Note that similar to Ruby’s interpreter, Swift echoed back what we declared. That’s the Print part of the REPL.

Now iterate over the fruit:


2> for f in fruit
:3:3: error: expected '{' to start the body of for-each loop
}
^

Oops… we didn’t end the line with an opening brace. Swift clearly prefers that the opening brace be on the same line as the for keyword, and we happen to prefer that style as well.


5> for f in fruit {
6. println("One type of fruit is \(f)")
7. }
One type of fruit is apples
One type of fruit is pears
One type of fruit is bananas

To quit out of the interpreter you can type CTRL-D or just type :q and hit Return.

Cool indeed. But what we’d really like is to treat Swift as scripting language, not just another language we can play with in an interpreter. We’ll give that a try by writing a simple Swift script that accepts a list of filenames and then performs the Unix wc function. Those that have dabbled in system administration or report generation know that wc prints the number of lines, words, and characters in each of its input files.

With the use of the -i flag to the swift executable we can do exactly that. Along the way we’ll continue to drive home the point that adopting Swift does not mean you have to abandon everything you know about iOS and OS X application development.

To make a point, we won’t be using the Xcode editor to edit the file wc.swift, but rather our favorite Mac version of Emacs known as Aquamacs. If you are familiar with Emacs editing modes, there’s even one for swift that we like to use.

The first line of wc.swift should be

import Foundation

This is the real reason why developing in Swift already feels so robust; it doesn’t require abandonment of the core iOS and Mac OS X frameworks. Bringing in the Foundation Framework links in all manner of classes ranging from NSCalendar to NSXMLParser. You will certainly almost always want to start your Swift files with import Foundation, and it’s so fundamental that Xcode puts the line in for you when creating a new Swift file.

The next line of code sets up the array of filenames that were (or will be) passed into our application.

let filenames = Process.arguments

Now that we have our filenames we’ll iterate on them and use the fact that Swift String gives us access to NSString methods to load a string with the contents of that file.

for filename in filenames {
  var fileContents:String? = String.stringWithContentsOfFile(filename)

Before we continue let’s again look at why we had to declare fileContents as a String?. The reason is straightforward: imagine if the stringWithContentsOfFile method failed for some reason, then what would fileContents hold? Nothing. That’s why the variable is declared as an optional: it can either be a String or it can be nothing at all.

Since fileContents is an optional, any further methods called on it would have to accompanied with an exclamation mark or we would have to assign the contents to an unwrapped variable or constant. Trying to access methods using the optional results in:


wc.swift:16:23: error: 'String?' does not have a member named 'componentsSeparatedByCharactersInSet'
var lines:Array = fileContents.componentsSeparatedByCharactersInSet(newline)
^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

So, we have to use exclamation mark:


var lines:Array = fileContents!.componentsSeparatedByCharactersInSet(newline)

This is, however, poor form. If reading the file contents of the string did fail (for example, there was no file there to begin with), accessing it in this manner will crash with a likely fatal error: Can't unwrap Optional.None. Instead we should use optional binding to encapsulate our wc routine:

if let contents = fileContents {

This line basically says, “If fileContents is a String and not the None type then assign that string to the constant contents and provide that constant to the true branch of the if statement.” That’s a mouthful. What this does is allows use to use contents without the exclamation mark, because it is already unwrapped and we know that it is a String.

The remaining code is straightforward and illustrates once more that Swift provides access to the Foundation Framework. Using NSCharacterSet and the NSString componentsSeparatedByCharactersInSet method we are able to quickly pull apart how many lines and words are in our string. Tying it all together we get:

import Foundation

let filenames = Process.arguments

for filename in filenames {

    var fileContents:String? = String.stringWithContentsOfFile(filename)

    if let contents = fileContents {

        var whitespace = NSCharacterSet.whitespaceAndNewlineCharacterSet() as NSCharacterSet
        var newline    = NSCharacterSet.newlineCharacterSet() as NSCharacterSet

        var lines:Array = contents.componentsSeparatedByCharactersInSet(newline)
        var words:Array = contents.componentsSeparatedByCharactersInSet(whitespace)
    
        var numChars = countElements(contents)
        var numLines = lines.count - 1
        var numWords = words.count - 1

        println("t(numLines)t(numWords)t(numChars)t(filename)")
    } else {
        println("wc.swift: (filename):  No such file")
    }
    
}

Finally, how to run this! The swift binary provides the -i switch for running in immediate mode, which is what we want. So we type swift -i wc.swift to run our code and…


$ swift -i wc.swift
wc.swift:5:8: error: cannot load underlying module for 'Foundation'
import Foundation
^
:0: note: did you forget to set an SDK using -sdk or SDKROOT?
:0: note: use "-sdk $(xcrun --show-sdk-path --sdk macosx)" to select the default OS X SDK installed with Xcode

Oops. Remember that your Xcode development environment is capable of compiling applications for all manner of iOS versions as well as Mac OS X. There are differences between the operating systems and versions, so you need to tell swift which SDK you’re going to use (similar to setting your iOS SDK in Xcode). Thankfully swift gives a nice little hint: "use -sdk $(xcrun --show-sdk-path --sdk macosx)" to select the default OS X SDK. We’ll try that.


$ swift -i -sdk $(xcrun --show-sdk-path --sdk macosx) wc.swift
wc.swift: -i: No such file
31 184 831 wc.swift
wc.swift: -enable-objc-attr-requires-objc-module: No such file
wc.swift: -target: No such file
wc.swift: x86_64-apple-darwin14.0.0: No such file
wc.swift: -module-name: No such file
wc.swift: wc: No such file

What the… What’s happening here is the default behavior of Process.arguments. If there are no options presented to the application Process.arguments will contain the commandline for swift, which is not what we want (obviously). In our example, Process.arguments contained [-i, wc.swift, -enable-objc-attr-requires-objc-module, -target, x86_64-apple-darwin14.0.0, -module-name, wc, -sdk, /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.9.sdk, -color-diagnostics].

To invoke the script in the intended manner we use the “dash-dash” option and then provide the filename. $ swift -i -sdk $(xcrun --show-sdk-path --sdk macosx) wc.swift -- declaration.txt:

declaration.txt contains:

Was writing and executing this as straightforward as Ruby or Python? No. But it does illustrate you can use Swift as a scripting language on your Mac and iteratively run bits of code without going through the Edit-Compile-Link-Run cycle.

3 comments

  1. John VelmanReply

    This is some of what I’ve been looking for, finally a little bit of content.

    I got the standard library reference as
    well as the swift iBook, and I’ve never had a worse introduction to a language, and I’ve written executable (or interpretable code) in Fortran II, FAP, Fortran IV, Fortran 68, Basic, VisualBasic, C, C++, prolog, python, Haskell, OCaml. And, oh, Objective C. Maybe others.

    Where can I find out more of the kind of information you have here?

    • Joe Post authorReply

      John – thanks for the compliment. As Swift is new there’s an obvious lack of “cookbook” training material. You might find http://www.sososwift.com/ to be a good resource for finding more articles (I’ve posted my posts there), and I can guarantee you within the next few months an O’Reilly book will hit the shelves. If there’s any topic you’d like covered here let me know, I’d be happy to research it.

      • john VelmanReply

        Thanks. With the reference to sososwift and the other material on this website I have more than enough to keep me busy for a while!

Leave a Reply

Your email address will not be published. Required fields are marked *