# Apply English punctuation rules

You've been hired to write some code for a dictation-taking app, which takes voice input from a spoken source, parses it as words, and writes it down on a screen.

The management doesn't really trust you with all that much powerin the project—you're known to sit around and golf code all day instead of doing your work, unfortunately—so they just give you a really simple task to perform: turn a Sentence with interspersed Punctuation into a properly formatted sentence, where 'properly formatted' is defined below.

1. The Sentence is the string of input. A Word is a group of continguous non-space characters. A Punctuation is a Word whose first character is ^.

2. A Word is capitalized if the first letter of the Word is not a lowercase letter (capitalized words match the regex /[^a-z].*/).

3. The first Word of the Sentence must be capitalized.

4. A ^COMMA is the comma character , and has a space following but not preceding. aaa ^COMMA bbb becomes aaa, bbb.

5. A ^COLON is a comma that looks like :.

6. A ^SEMICOLON is a comma that looks like ;.

7. A ^PERIOD is a comma that looks like .. The word following a ^PERIOD must be capitalized.

8. A ^BANG is a period that looks like !.

9. A ^DASH is the dash character - and has a space both preceding and following.

10. A ^HYPHEN is also the dash character - but has no space following or preceding.

11. An ^EMDASH is a hyphen (not a dash!) that is spelled --.

12. An ^OPENQUOTE is a quote character " that has a space preceding but not following. The word following an ^OPENQUOTE must be capitalized. If an ^OPENQUOTE is preceded by a Word that is not Punctuation, add a ^COMMA between that word and the ^OPENQUOTE. If an ^OPENQUOTE is preceded by a Punctuation that makes the next word capitalized, this skips over the ^OPENQUOTE to the next word.

13. A ^CLOSEQUOTE is the digraph ," that has a space following but not preceding. If a ^CLOSEQUOTE is preceded by a ^COMMA, ^PERIOD, or ^BANG, that Punctuation disappears and the ^CLOSEQUOTE is spelled ,", .", or !" respectively. If the disappearing Punctuation specified a capitalization, that capitalization must still occur on the next available word.

14. Initial or trailing spaces in the full final result must be removed, and any string of two or more spaces in a row must be all collapsed into a single space character.

15. Any case not covered above (e.g. ^COMMA ^COMMA or ^SEMICOLON ^CLOSEQUOTE or ^UNDEFINEDPUNCTUATION) will not occur in well-formed input and is thus undefined behaviour.

The development team informs you of the following:

• The project is being written in the language [your language here], and should be as short as possible so that it takes up as little space as possible when it's an app for Android/iPhone. You try to explain that that's not how app development works, but they don't listen. But hey, what a coincidence! You're an amazing golfer in [your language here]!

• The app will not have any web access permissions, and there won't be any libraries installed that do this formatting for you. You can probably convince the team lead to allow you a regex library if one exists for your language, though, if you think you need one.

• Support for nested quotations that use double/single quotes properly is planned for a later version of the app, but not the version that you're working on now, so don't worry about it.

• The management is a huge fan of test-driven development, and so the dev team has already had some hapless keyboard monkey write up some tests for your portion of the program: (newlines added for readability, treat them as spaces)

Input:

hello ^COMMA   world ^BANG


Output:

Hello, world!


Input:

once upon a time ^COMMA there was a horse ^PERIOD that horse cost me $50 ^PERIOD ^OPENQUOTE eat your stupid oats ^COMMA already ^BANG ^CLOSEQUOTE I told the horse ^PERIOD the horse neighed back ^OPENQUOTE no ^CLOSEQUOTE and died ^PERIOD THE END  Output: Once upon a time, there was a horse. That horse cost me$50. "Eat your
stupid oats, already!" I told the horse. The horse neighed back, "No,"
and died. THE END


Input:

begin a ^PERIOD b ^COMMA c ^COLON d ^SEMICOLON e ^BANG f ^HYPHEN g ^DASH h
^EMDASH i ^OPENQUOTE j ^PERIOD ^OPENQUOTE k ^SEMICOLON ^OPENQUOTE l
^CLOSEQUOTE m ^BANG ^CLOSEQUOTE n ^PERIOD 0x6C6F6C end


Output:

Begin a. B, c: d; e! F-g - h--i, "j. "K; "l," m!" N. 0x6C6F6C end


This is a code golf: the lowest score wins. You may write a function of one string argument, or a program reading from STDIN and writing to STDOUT.

• What if I want to use javascript? There is no standard input in it. Can I use prompt()? – nicael Jun 23 '14 at 8:01
• @nicael OP mentions using one string argument, so for my JS example I just made a function that takes one argument and assumed that argument was the string of words similar to STDIN – Eric Lagergren Jun 24 '14 at 22:22
• I wonder if there is esolang named "[your language here]" – Akangka Jan 2 '16 at 10:03

# JavaScript: 653 611 547 514 487 bytes

Oh my gosh. Brendan Eich I'm so sorry for this.

PS: I've added white space for readability, but stripping all allowable white space results in the byte count listed.

Theoretically I could shorten some parts like the -e- to something like -e or -e, but that might cause an issue if the previous word ends with, or the following word begins with the letter 'e' (or whichever word I decide to use). I suppose I could use an ASCII character. I'll look into that.

487 FF22+ Only

R = "replace", C = "charAt", U = "toUpperCase";
alert(a[R](/\^((COMMA)|(SEMICOLON)|(COLON)|(PERIOD)|(BANG)|(DASH)|(HYPHEN)|(EMDASH)|(OPENQUOTE)|(CLOSEQUOTE))/g, ((m, _, a, b, c, d, e, f, g, h, i, j) => a ? "," : b ? ";" : c ? ":" : d ? "." : e ? "!" : f ? "-" : g ? "-h-" : h ? "-e-" : i ? ' "' : '" '))[R](/\s((\.)|(\!)|(\,)|(\;)|(\:)|(\-\h\-\s)|(\-\e\-\s))/g, ((k, l, v, n, o, p, q, r, s) => v ? "." : n ? "!" : o ? "," : p ? ";" : q ? ":" : r ? "-" : "--"))[R](/[^!,"'.]\"\s/g, '"')[R](/.+?[\.\?\!](\s|$)/g, (t => t[C](0)[U]() + t.substr(1)))[R](/\"[a-z]/g, (u => u[C](0) + u[C](1)[U]())))  514 FF22+ Only alert(function(z) { R = "replace", C = "charAt", U = "toUpperCase"; return z[R](/\^((COMMA)|(SEMICOLON)|(COLON)|(PERIOD)|(BANG)|(DASH)|(HYPHEN)|(EMDASH)|(OPENQUOTE)|(CLOSEQUOTE))/g, ((m, _, a, b, c, d, e, f, g, h, i, j) => a ? "," : b ? ";" : c ? ":" : d ? "." : e ? "!" : f ? "-" : g ? "-h-" : h ? "-e-" : i ? ' "' : '" '))[R](/\s+((\.)|(\!)|(\,)|(\;)|(\:)|(\-\h\-\s+)|(\-\e\-\s+))/g, ((k, l, v, n, o, p, q, r, s) => v ? "." : n ? "!" : o ? "," : p ? ";" : q ? ":" : r ? "-" : "--"))[R](/[^!,"'.]\"\s/g, '"')[R](/.+?[\.\?\!](\s+|$)/g, (t => t[C](0)[U]() + t.substr(1)))[R](/\"[a-z]/g, (u => u[C](0) + u[C](1)[U]()))
}(a))


547 FF22+ Only

alert(function(z) {
R = "replace", C = "charAt", U = "toUpperCase";
return z[R](/\^((COMMA)|(SEMICOLON)|(COLON)|(PERIOD)|(BANG)|(DASH)|(HYPHEN)|(EMDASH)|(OPENQUOTE)|(CLOSEQUOTE))/g, ((m, _, a, b, c, d, e, f, g, h, i, j) => a ? "," : b ? ";" : c ? ":" : d ? "." : e ? "!" : f ? "-" : g ? "-h-" : h ? "-e-" : i ? ' "' : '" '))[R](/\s+((\.)|(\!)|(\,)|(\;)|(\:)|(\-\h\-\s+)|(\-\e\-\s+))/g, ((xx, __, k, l, m, n, o, p, q) => k ? "." : l ? "!" : m ? "," : n ? ";" : o ? ":" : p ? "-" : "--"))[R](/[^!,"'.]\"\s/g, '"')[R](/.+?[\.\?\!](\s+|$)/g, function(r) { return r[C](0)[U]() + r.substr(1) })[R](/\"[a-z]/g, function(s) { return s[C](0) + s[C](1)[U]() }) }(a))  611 FF 22+ Only alert(function(c) { return c.replace(/\^((COMMA)|(SEMICOLON)|(COLON)|(PERIOD)|(BANG)|(DASH)|(HYPHEN)|(EMDASH)|(OPENQUOTE)|(CLOSEQUOTE))/g, ((x, _, a, b, c, d, e, f, g, h, i) = > a ? "," : b ? ";" : c ? ":" : d ? "." : e ? "!" : f ? "-" : g ? "-h-" : h ? "-e-" : i ? ' "' : '" ')).replace(/\s+\./g, ".").replace(/\s+\!/g, "!").replace(/\s+\,/g, ",").replace(/\s+\;/g, ";").replace(/\s+\:/g, ":").replace(/\s\-\h\-\s/g, "-").replace(/[^!,"'.]\"\s/g, '"').replace(/\s+\-\e-\s+/g, "--").replace(/.+?[\.\?\!](\s+|$)/g, function(b) {
return b.charAt(0).toUpperCase() + b.substr(1)
}).replace(/\"[a-z]/g, function(b) {
return b.charAt(0) + b.charAt(1).toUpperCase()
})
}(a))


653 cross-browser

alert(function(c) {
return c.replace(/\^COMMA/g, ",").replace(/\^SEMICOLON/g, ";").replace(/\^COLON/g, ":").replace(/\^PERIOD/g, ".").replace(/\^BANG/g, "!").replace(/\^DASH/g, "-").replace(/\^HYPHEN/g, "h-h").replace(/\^EMDASH/g, "-e-").replace(/\^OPENQUOTE/g, ' "').replace(/\^CLOSEQUOTE/g, '" ').replace(/\s+\./g, ".").replace(/\s+\!/g, "!").replace(/\s+\,/g, ",").replace(/\s+\;/g, ";").replace(/\s+\:/g, ":").replace(/\s\h\-\h\s/g, "-").replace(/[^!,"'.]\"\s/g, '"').replace(/\s+\-\e-\s+/g, "--").replace(/.+?[\.\?\!](\s|$)/g, function(b) { return b.charAt(0).toUpperCase() + b.substr(1) }).replace(/\"[a-z]/g, function(b) { return b.charAt(0) + b.charAt(1).toUpperCase() }) }(a))  How it works: JSFiddle (for the 653 byte cross-browser solution) JSFiddle (for the 595 FF 22+ only solution) JSFiddle (for the 547 FF 22+ only solution) JSFiddle (for the 514 FF 22+ only solution) JSFiddle (for the 487 FF 22+ only solution) This is the first time I've had to write JS that uses more than one regex, and usually my regex is predefined. I'll continue to shave off bytes as much as I can. • You can shorten your first replaces like this: c.replace(/\^((COMMA)|(SEMICOLON)|(COLON)|(PERIOD)|(BANG))/g,(m,_,a,b,c,d,e)=>a?',':b?';':c?':':d?'.':'!')) ... and so on. Arrow sintax is short, but even 'function' should save same chars – edc65 Jun 24 '14 at 10:26 • You're right. I tested my regexp with Chrome, and it doesn't support fat arrows. I'm working on getting it straightened out with FF right now, but I hate how regexps don't really have an "and" operator like they do an "or". @edc65 – Eric Lagergren Jun 24 '14 at 22:13 • @edc65 so I figure I'll have to use two =>s in order to get it to work, but using the arrows saved me 40 bytes! – Eric Lagergren Jun 24 '14 at 22:23 • Replace replace with R='replace' ... [R] ;-) – edc65 Jun 24 '14 at 23:36 • Just did that :) Got it down to 563 @edc65 – Eric Lagergren Jun 25 '14 at 0:13 # PHP, 412 bytes (Ungolfed here for clarity; see ideone for golfed version.) PHP's preg_replace() function will accept array arguments, which is pretty useful here. I think the following code does everything that's required. It passes all the test cases at least. function x($s) {
$r='preg_replace';$s=$r('/ +/',' ',$s);
$s=$r(array('/ \^COMMA/','/ \^COLON/','/ \^SEMICOLON/','/ \^PERIOD/','/ \^BANG/',
'/\^DASH/','/ \^HYPHEN /','/ \^EMDASH /','/\^OPENQUOTE /','/ \^CLOSEQUOTE/'),
array(',',':',';','.','!','-','-','--','"',',"'),
$s);$s=$r('/(^\W*\w|([\.!]| ")\W+\w)/e','strtoupper("$0")',$s);$s=$r('/([,\.!]),/','\1',$s);
$s=$r('/(\w)( "\w)/e','"$1,".strtoupper("$2")',$s); echo$s;
}

• Works perfectly! ideone.com/AYtTiI Although what I'm confused about is are we supposed to have commas before open quotes? Because, grammatically speaking, quotation marks are for more than speech, yet only speech has the comma before the quotes. I assumed since there was a ^COMMA we'd be letting the user input the comma – Eric Lagergren Jun 26 '14 at 3:03