3

I'm starting to like the Swift string formatting since it uses variable names in the string rather than ambiguous formatting tags like "%@"

I want to load a large string from a file that has Swift-style formatting in it (like this)

Now is the time for all good \(who) to come to babble incoherently.

Then I want to feed the contents of that String variable into a statement that lest me replace

\(who)

with the contents of the constant/variable who at runtime.

The code below works with a string constant as the formatting string.

let who = "programmers"
let aString = "Now is the time for all good \(who) to come to babble incoherently."

That code does formatting of a quoted string that appears in-line in my code.

Instead I want something like the code

let formatString = "Now is the time for all good %@ to come to babble incoherently."
aString = String(format: formatString, who)

But where I can pass in a Swift-style format string in a constant/variable I read from a file.

Is that possible? I didn't have any luck searching for it since I wasn't exactly sure what search terms to use.

I can always use C-style string formatting and the String class' initWithFormat method if I have to...

Duncan C
  • 128,072
  • 22
  • 173
  • 272

4 Answers4

3

I don't think there's a way to do this. String interpolation is implemented via conforming to the StringInterpolationConvertible protocol, and presumably you're hoping to tap into that in the same way you can tap into the methods required by StringLiteralConvertible, a la:

let someString = toString(42)

// this is the method String implements to conform to StringLiteralConvertible
let anotherString = String(stringLiteral: someString)

// anotherString will be "42"
print(anotherString)

Unfortunately, you can't do quite the same trick with StringInterpolationConvertible. Seeing how the protocol works may help:

struct MyString: Printable {
    let actualString: String
    var description: String { return actualString }
}

extension MyString: StringInterpolationConvertible {
    // first, this will get called for each "segment"
    init<T>(stringInterpolationSegment expr: T) {
        println("Processing segment: " + toString(expr))
        actualString = toString(expr)
    }
    // here is a type-specific override for Int, that coverts
    // small numbers into words:
    init(stringInterpolationSegment expr: Int) {
        if (0..<4).contains(expr) {
            println("Embigening \(expr)")
            let numbers = ["zeo","one","two","three"]
            actualString = numbers[expr]
        }
        else {
            println("Processing segment: " + toString(expr))
            actualString = toString(expr)
        }
    }
    // finally, this gets called with an array of all of the
    // converted segments
    init(stringInterpolation strings: MyString...) {
        // strings will be a bunch of MyString objects
        actualString = "".join(strings.map { $0.actualString })
    }
}

let number = 3
let aString: MyString = "Then shalt thou count to \(number), no more, no less."
println(aString)
// prints "Then shalt thou count to three, no more, no less."

So, while you can call String.init(stringInterpolation:) and String.init(stringInterpolationSegment:) directly yourself if you want (just try String(stringInterpolationSegment: 3.141) and String(stringInterpolation: "blah", "blah")), this doesn't really help you much. What you really need is a facade function that coordinates the calls to them. And unless there's a handy pre-existing function in the standard library that does exactly that which I've missed, I think you're out of luck. I suspect it's built into the compiler.

You could maybe write your own to achieve your goal, but a lot of effort since you'd have to break up the string you want to interpolate manually into bits and handle it yourself, calling the segment init in a loop. Also you'll hit problems with calling the combining function, since you can't splat an array into a variadic function call.

Airspeed Velocity
  • 40,491
  • 8
  • 113
  • 118
  • Thanks Airspeed. That's what I figured, but thought it was worth asking. Pity, since the Swift string formatting syntax has named parameters. It would make your intent much clearer than a big string glob with tons of `%@`s and such in it. – Duncan C Apr 20 '15 at 14:44
  • I agree, it'd be nice. Maybe file a radar, you never know. Also I would guess they'll expand interpolation to cover more formatting in future so probably worth getting requests in. – Airspeed Velocity Apr 20 '15 at 14:46
  • I'd put a higher priority on things like making NSDates comparable equatable and being able to list tuple constants in the cases of a switch statement. I've written extensions to accomplish both, but it seems to me those should be baked into the language. – Duncan C Apr 20 '15 at 14:49
  • ...Actually, I think **YOU** wrote the code that lets me use tuple constants in switch statements. I should really have said "I have found extensions..." – Duncan C Apr 20 '15 at 15:03
1

I don't think so. The compiler needs to be able to resolve the interpolated variable at compile time.

Ferruccio
  • 98,941
  • 38
  • 226
  • 299
0

I'm not a Swift programmer, specifically, but I think you can workaround it to something pretty close to what you want using a Dictionary and standard string-replacing and splitting methods:

var replacement = [String: String]()
replacement["who"] = "programmers"

Having that, you can try to find the occurrences of "\(", reading what is next and prior to a ")", (this post can help with the split part, this one, with the replacing part), finding it in the dictionary, and reconstructing your string from the pieces you get.

Community
  • 1
  • 1
Rafael
  • 129
  • 5
-1

this one works like a charm:

let who = "programmers"
let formatString = "Now is the time for all good %@ to come to babble incoherently."
let aString = String(format: formatString, who)
Kubba
  • 3,390
  • 19
  • 34
  • That's not what I'm asking. I'm asking if there is a way to do that using Swift-style string formatting, like `"Now is the time for all good \(personClass) to babble incoherently."` – Duncan C Apr 23 '15 at 13:55
  • AFAIK - no, because of named variables. What's wrong with above? – Kubba Apr 23 '15 at 15:20
  • Well, it's the whole point of my question. Go back and reread my question. I state the goal clearly. – Duncan C Apr 23 '15 at 15:28