Description
We want to change the first quote of every single-quoted string to backquote, where
  • the opening and the closing quotes of a quoted string may be on different lines, and
  • an escaped quote has to be treated literally.
Raw Input Desired Output
first 'aaa\'AAA' second 'bbb
 ccc' third 'ddd\'DE\'eee' 
fourth '' fifth 'fff
ggg' sixth 'hh' seventh '
' eight 'jjj kkk' ninth 'lll
mmm'
first `aaa\'AAA' second `bbb
 ccc' third `ddd\'DE\'eee' 
fourth `' fifth `fff
ggg' sixth `hh' seventh `
' eight `jjj kkk' ninth `lll
mmm'
Script and Comments
Script1
[ 1] :find_opening
[ 2] /\n/!s/^/\n/
[ 3] s/\n((\\.|[^'])*)/\1\n/
[ 4] /\n'/!{
[ 5] s/\n//
[ 6] n
[ 7] b find_opening
[ 8] }
[ 9] s/\n'/`\n/
[10] :find_closing
[11] /\n/!s/^/\n/
[12] s/\n((\\.|[^'])*)/\1\n/
[13] /\n'/!{
[14] s/\n//
[15] n
[16] b find_closing
[17] }
[18] s/\n'/'\n/
[19] b find_opening
Comments -r
  1. When processing a line, we use a newline character as a mark to designate which part of that line has not been examined. In this way, a line is divided into two parts: the examined part followed by the non-examined one, with a newline character between them.
  2. This script works in the following way:
    • If a line has not been processed at all, Step [2] and Step [11] insert a newline character at the beginning.
    • Then tries finding a non-examined opening quote (Step [3]),
      changes it to a backquote, and moves the mark after it (Step [9]),
    • Then tries to find the corresponding closing quote (Step [12]), and moves the mark after it (Step [18]).
    • If the desired quote is not found in the current line in either stage, this script
      • removes the newline character we inserted,
      • prints that line and then reads the next line,
      • then resumes the same stage.
      • Steps [4] thru [8], and Steps [13] thru [17] do this job.
Script2
[ 1] :loop
[ 2] /\n/!s/^/\n/
[ 3] s/\n((\\.|[^'])*)/\1\n/
[ 4] /\n'/!{
[ 5] s/\n//
[ 6] n
[ 7] b loop
[ 8] }
[ 9] G
[10] /\n$/s/\n'/`\n/
[11] /\n#$/s/\n'/'\n/
[12] s/\n#*$//
[13] x
[14] s/^/#/
[15] s/##//
[16] x
[17] b loop
Comments -r
  1. You may find that in the first script, Steps [2] thru [8] are the same as Steps [11] thru [17]. To remove this redundancy, this script uses HS to remember which stage it is:
    • If HS is empty, it is in the stage of finding the opening quote.
    • If HS is `#', it is in the stage of finding the closing quote.
    • After doing the substitution/movement, Steps [13] thru [16] are used to change the state from one stage to the other: `#' to empty, or empty to `#'.