9
\$\begingroup\$

The challenge is to parse a string like Python does and print the contents of the string.

  • Input (command-line argument or stdin): a string literal (e.g. "hello") (or multiple literals, see string literal concatenation below)
  • Output (stdout): the contents of the string (e.g. hello)

Rules for parsing the string:

  • A string literal is enclosed in matching pairs of single quotes ('a'), double quotes ("a"), triple single quotes ('''a''') or triple double quotes ("""a"""). The first reoccurrence of the type of quotes that opened the string ends the string.
  • Backslash escapes: \' within a string becomes ', \" becomes " and \\ becomes \. You do not need to implement any other backslash escapes. A backslash that is not part of an escape sequence stays a backslash.
  • String literal concatenation: The contents of adjacent string literals are concatenated. For example, "hello" 'world' becomes helloworld.
  • The input may contain spaces that are not part of any literal.
  • You do not need to support any other kind of whitespace, neither within nor outside literals.

Additional rules:

  • eval, exec and similar stuff is not allowed for parsing the literal or parts of it.
  • You may assume that the input is valid.
  • You may assume a maximum input length of 1023 characters.

Examples:

  • "hello" ' world' -> hello world
  • """\"""'\\\A""" -> """'\\A
  • ( '''"""'''"""'''""" ) (without parentheses, but with spaces) -> """'''

Shortest code wins.

\$\endgroup\$
  • \$\begingroup\$ Is the output to be of a form that can be stored, or is it sufficient to print it and be done with it? \$\endgroup\$ – DavidC Jul 18 '13 at 22:12
  • \$\begingroup\$ @David Printing it is all you need to do. \$\endgroup\$ – flornquake Jul 18 '13 at 22:22
  • \$\begingroup\$ So in (e.g.) "\z", the code is specifically required to output the backslash and the z? But \' becomes just an apostrophe, even if it appears inside double-quotes or triple-quotes? Is that correct? \$\endgroup\$ – breadbox Jul 18 '13 at 23:49
  • \$\begingroup\$ @breadbox Exactly. \$\endgroup\$ – flornquake Jul 19 '13 at 0:13
  • \$\begingroup\$ Should the code support raw strings? And what about concatenation of non-raw and raw strings? \$\endgroup\$ – Bakuriu Jul 19 '13 at 18:28
4
\$\begingroup\$

Perl, 54 characters

#!/usr/bin/perl -p
s/ |("""|'''|"|')((\\?.)*?)\1/$2/g;s/\\(["'\\])/$1/g

Just as I was posting this, I noticed that it's almost identical to Jan Dvorak's Ruby solution. I'm a little put out by how similar it is, in fact, but I'm going to say "Great minds think alike" and let it go at that.

This program highlights a weird corner case in counting characters in Perl scripts: By my reading, the presence of single-quotes in the script means that I need to count the -p option as two characters towards my total. Typically, when computing Perl script sizes, the initial dash character on the options is considered to be free, on the justification that it can be bundled with the -e that introduces the program proper ... but then you also have to account for any extra escapes you need to enter the script on the command-line. The single-quotes require lots of escaping, so to avoid that penalty I have to count it as a script run from a file, and therefore I get the #!/usr/bin/perl for free, but not any option characters. It's a little confusing.

\$\endgroup\$
  • 2
    \$\begingroup\$ If you want to be different, (('|")\2{2}?) is the same length as ("""|'''|"|') \$\endgroup\$ – Peter Taylor Jul 19 '13 at 17:16
3
\$\begingroup\$

C, 178 characters

char*p,*q,b[1024];d;main(t){for(p=q=gets(b);*p=*q++;)
d?*p==92&!(*q-*p&&*q-34&&*q-39)?*p++=*q++:*p-d||t&&*q-d|q[1]-d?++p:
(d=0,q+=2*t):*p-32?d=*p,t=*q==d&q[1]==d,q+=2*t:0;puts(b);}

This is one of those C solutions where everything is done inside a ternary-operator chain gang.

The program works by copying characters back into the same buffer, overwriting the metacharacters. d holds the delimiter when inside of a string, and t is true if the delimiter is a triple-quote.

\$\endgroup\$
  • \$\begingroup\$ I think you need to include a conditional extra incrementation of the loop control variable. For 'foo\\'bar' it gives foo\ar', which looks like it replaces \\ with \, but then continues the parsing with the freshly entered \, seeing the next token as \'. \$\endgroup\$ – manatwork Jul 19 '13 at 7:32
  • \$\begingroup\$ Actually, that example is invalid input. 'foo\\' refers to the string foo\, which is then followed by a character that is neither whitespace nor a string delimiter. \$\endgroup\$ – breadbox Jul 19 '13 at 7:34
  • \$\begingroup\$ Oops. I misread that rule. Then of course your code is correct. \$\endgroup\$ – manatwork Jul 19 '13 at 7:41
3
\$\begingroup\$

Ruby, 74 73 characters

puts gets.gsub(/('''|"""|'|")((\\?.)*?)\1|./,'\2').gsub /\\([\\'"])/,'\1'

The core here are two regexes: The first one determines the string boundaries and selects the contents only. The alteration is there to remove everything not inside strings, and it also drops unclosed strings. Backslashes are treated as possesive-optional followed by anything. Thus, Since the regex engine will not backtrack into (\\?.) for valid inputs (thanks @breadbox), a sole backslash cannot match there. Quotes are handled via lazy repetition. The second regex then strips a backslash before each escapeable character. The regex depends on the engine to always pick the leftmost alternative first.

I have also considered a state-machine approach, but it turned out quite big (19 states x 4 character classes) compared to the regex solution. I can still post the state machine if anyone is interested.

\$\endgroup\$
  • \$\begingroup\$ One minor glitch with this method: 'foo\\'bar' becomes foo\ instead of 'foo\'bar'. \$\endgroup\$ – manatwork Jul 19 '13 at 7:24
  • \$\begingroup\$ @manatwork this is correct, unless something has been lost in formatting. The first backslash escapes the second one. 'foo\\' is the first string and bar' is outside a string context when the input is 'foo\\'bar' \$\endgroup\$ – John Dvorak Jul 19 '13 at 7:29
  • \$\begingroup\$ Oops. No idea how I calculated it earlier. Of course it is correct. Sorry. \$\endgroup\$ – manatwork Jul 19 '13 at 7:35
  • \$\begingroup\$ When I try to run this I get an error message: "nested *?+ in regexp". Is there some minimum version or runtime flag that I need? \$\endgroup\$ – breadbox Jul 19 '13 at 16:28
  • \$\begingroup\$ @breadbox I haven't checked other versions, but I'm running ruby 1.9.3 (JRuby 1.7.2). should I assume 1.9.3 at least and edit that in? \$\endgroup\$ – John Dvorak Jul 19 '13 at 16:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.