This might derail the thread somewhat, but I think it also shows that languages alone are not the point.
I would actually parse that string as soon as it enters my domain. I want to get a {:sheet "boo", :start "a1", :end "b2"}. This sheet language is not too small to make it right. What if the syntax gets extended later to also allow other files or other computers (whose names might contain \! and \:)?
In other words, the method that started that thread shouldn’t exist in the first place.
I don’t think the code by that book author is much better, because it still has the method, it just makes the code much more complicated without touching the core issue (and claiming “it’s generated, so no rules” is another issue or two).
In Java, I would have an (immutable) SheetRef class which has a constructor or static method that parses from a string (among others possibly). In keeping with Java style, I would probably then define getters for the parts. I would definitely not do partial parses all over the code.
Now, to get back to our thread, is the NIL returned by :sheet good? I think yes, but maybe you want to think about your domain, whether you should put in the current sheet name upon parsing if absent?