Interpret /// (pronounced 'slashes') Because we can't get enough of esoteric language golfs, can we?

///&mdash;pronounced slashes&mdash;is a fun little language based on the s/// regex-replacement function of Perl fame. It contains only two special characters, slash / and backslash \. You can find a full article on it at the esolangs wiki, but I will reproduce a description of the language below, as well as some examples.

In short, it works by identifying /pattern/repl/rest in the program and making the substitution as many times as possible. No characters are special except / and \: / demarcates patterns and replacements in the program, while \ allows you to insert literal / or \ characters into your code. Notably, these are not regular expressions, just plain string substitutions.

Your challenge is to produce an interpreter for the /// language, as either a program reading STDIN or a function taking a string argument, in as few characters as possible.

You may use any language except for /// itself. You may not use any libraries that interpret ///; you may, however, use regexes, regex libraries, or string-matching libraries.



Execution

There are four states, print, pattern, replacement, and substitution. In every state except substitution:


If the program is empty, execution halts.
Else, if the first character is \, do something with the next character (if present) and remove both from the program.
Else, if the first character is /, remove it, and change to the next state.
Else, do something with the first character and remove it from the program.
Repeat.


The states cycle through print, pattern, replacement, and substitution in order.


In print mode, 'do something' means output the character.
In pattern mode, 'do something' means add the character to the current Pattern.
In replacement mode, 'do something' means add the character to the current Replacement.


In substitution mode, you follow a different set of rules. Repeatedly substitute the first occurrence of the current Pattern with the current Replacement in the program, until no more substitutions are possible. At that point, clear the Pattern and Replacement and return to print mode.

In the program /foo/foobar/foo foo foo, the following happens:

/foo/foobar/foo foo foo
foo foo foo
foobar foo foo
foobarbar foo foo
foobarbarbar foo foo
...


This loops forever and never exits substitution mode. Similarly, if the Pattern is empty, then the first occurrence of the empty string&mdash;at the beginning of the program&mdash;always matches, so substitution mode loops forever, never halting.



Examples

no


Output: no.

/ world! world!/Hello,/ world! world! world!


Output: Hello, world!.

/foo/Hello, world!//B\/\\R/foo/B/\R


Output: Hello, world!.

a/ab/bbaa/abb


Output: a. Program does not halt.

//


Output: none.

///


Output: none. Program does not halt.

/\\/good/\/


Output: good.

There is also a quine on the wiki you can try.
J - 181 190 170 char

This was a nightmare. I rewrote it from scratch, twice, because it just kept bugging me. This is a function taking a single string argument, outputting to STDOUT.

(0&amp;$`((2{.{:@&gt;&amp;.&gt;)((j{.]),-i@=`p@.~:~/@[,]}.~#@p+j=.0{p I.@E.])i 5;@}.&amp;,'/';"0;&amp;.&gt;)@.(2&lt;#)@}.[4:1!:2~{:@&gt;@p=.&gt;@{.@[)@((0;(0,:~1 0,.2);'\';&amp;&lt;1 0)&lt;;._1@;:'/'&amp;,)i=. ::](^:_)


To explain, I will break it up into subexpressions.

i =. ::](^:_))
parse =: ((0;(0,:~1 0,.2);'\';&amp;&lt;1 0)&lt;;._1@;:'/'&amp;,)
print =: 4:1!:2~{:@&gt;@p=.&gt;@{.@[
eval  =: 0&amp;$`((2{.{:@&gt;&amp;.&gt;)sub 5;@}.&amp;,'/';"0;&amp;.&gt;)@.(2&lt;#)@}.
sub   =: ((j{.]),-i@=`p@.~:~/@[,]}.~#@p+j=.0{p I.@E.])i

interp =: (eval [ print) @ parse i



i (short for iterate) is an adverb. It takes a verb argument on the left and returns a verb (f)i, which when applied to an argument, applies f repeatedly to the argument until one of two things happens: it finds a fixed point (y = f y), or it throws an error. The fixed-point behaviour is inherent to ^:_, and ::] does the error handling.
parse tokenizes the input into what I call half-parsed form, and then cuts it up at the unescaped '/'. It binds escaping backslashes to their characters, but doesn't get rid of the backslashes&mdash;so we can either revert it or finish it depending on which we want.

The bulk of the interesting work occurs in ;:. This is a sequential-machine interpreter primitive, taking a description of the machine ((0;(0,:~1 0,.2);'\';&amp;&lt;1 0)) on the left and something to parse on the right. This does the tokenizing. I will note that this specific machine actually treats the first character unspecial, even if it's a \ and should bind. I do this for a few reasons: (1) the state table is simpler, so it can be golfed further; (2) we can easily just add a dummy character to the front to dodge the problem; and (3) that dummy-character gets half-parsed at no extra cost, so I can use it to set up for the cutting phase, next.

We also use &lt;;._1 to cut the tokenized result on unescaped / (which is what I choose to be the first char). This is handy for pulling out the output, pattern, and replacement from out/patt/repl/rest all in one step, but unfortunately also cuts up the rest of the program, where we need those / to stay untouched. I splice these back in during eval, because making &lt;;._1 leave them alone ends up costing a lot more.
The fork (eval [ print) executes print on the result from parse for its side-effects, and then runs eval. print is a simple verb that opens up the first box (the one we know for sure is output), finishes parsing it, and sends it to STDOUT. However, we also take the chance to define a utility verb p.

p is defined as &gt;@{.@[, so it takes its left arg (acts like the identity if given only one arg), takes the first item of that (identity when given a scalar), and unboxes it (identity if already unboxed). This will come in very handy in sub.
eval evaluates the remainder of the processed program. If we don't have a full pattern or a full replacement, eval throws it out and just returns an empty list, which terminates evaluation by making ;: (from parse) error out on the next iteration. Else, eval fully parses the pattern and replacement, corrects the remainder of the source, and then passes both to sub. By explosion:

                                              @}.  NB. throw out printed part
                                       @.(2&lt;#)     NB. if we have a pattern and repl:
      2{.                                          NB.  take the first two cuts:
             &amp;.&gt;                                   NB.   in each cut:
         {:@&gt;                                      NB.    drop escaping \ from chars
     (          )                                  NB.  (these are pattern and repl)
                                   &amp;.&gt;             NB.  in each cut:
                                  ;                NB.   revert to source form
                            '/';"0                 NB.  attach a / to each cut
                          &amp;,                       NB.  linearize (/ before each cut)
                     5  }.                         NB.  drop '/pattern/repl/'
                      ;@                           NB.  splice together
    (            sub                  )            NB.  feed these into sub
   `                                               NB. else:
0&amp;$                                                NB.  truncate to an empty list

sub is where one (possibly infinite) round of substitutions happens. Because of the way we set up eval, the source is the right argument, and the pattern and replacement are bundled together in the left. Since the arguments are ordered like this and we know the pattern and replacement don't change within a round of substitutions, we can use another feature of i&mdash;the fact that it modifies only the right argument and keeps passing in the same left&mdash;to delegate to J the need to worry about keeping track of the state.

There are two spots of trouble, though. The first is that J verbs can have at most two arguments, so we don't have an easy way to access any that are bundled together, like pattern and replacement, here. Through clever use of the p utility we defined, this isn't that big of a problem. In fact, we can access the pattern in one character, just by using p, because of its &gt;@{.@[ definition: the Unbox of the First item of the Left arg. Getting the replacement is tricker, but the shortest way would be p&amp;|., 2 chars shorter than manually getting it out.

The second problem is that i exits on fixed points instead of looping forever, and if the pattern and replacement are equal and you make a substitution, that looks like a fixed point to J. We handle this by entering an infinite loop of negating 1 over and over if we detect they are equal: this is the -i@=`p@.~:~/ portion, replacing p&amp;|..

                                    p    E.]    NB. string search, patt in src
                                      I.@       NB. indices of matches
                                  0{            NB. take the first (error if none)
                               j=.              NB. assign to j for later use
                           #@p+                 NB. add length of pattern
                       ]}.~                     NB. drop that many chars from src
                   /@[                          NB. between patt and repl:
                  ~                             NB.  patt as right arg, repl as left
              @.~:                              NB.  if equal:
        -i@=                                    NB.   loop forever
            `p                                  NB.  else: return repl
 (j{.])                                         NB. first j chars of src
       ,              ,                         NB. append all together
(                                           )i  NB. iterate

This cycle repeats due to the use of i, until something outside of sub errors out. As far as I'm aware, this can only happen when we are out of characters, of when we throw out an incomplete set of pattern-and-replacement.


Fun facts about this golf:


For once, using ;: is shorter than manually iterating through the string.
0{ should have a chance to error out before sub goes into an infinite loop, so this it should work fine if the pattern matches the replacement but never shows up in the remainder of the source. However, this may or may not be unspecified behaviour, since I can't find a citation either way in the docs. Whoopsie.
Keyboard interrupts are processed as spontaneous errors inside running functions. However, due to the nature of i, those errors get trapped too. Depending on when you hit Ctrl+C, you might:

Exit out of the negate-forever loop, error out of the sub loop by trying to concatenate a number to a string, and then go on interpreting /// as if you finished substituting a string with itself an infinite number of times.
Leave sub halfway through and go on interpreting a half-subbed /// expression.
Break out of the interpreter and return an unevaluated /// program to the REPL (not STDOUT, though).



Example usage:

   f=:(0&amp;$`((2{.{:@&gt;&amp;.&gt;)((j{.]),-i@=`p@.~:~/@[,]}.~#@p+j=.0{p I.@E.])i 5;@}.&amp;,'/';"0;&amp;.&gt;)@.(2&lt;#)@}.[4:1!:2~{:@&gt;@p=.&gt;@{.@[)@((0;(0,:~1 0,.2);'\';&amp;&lt;1 0)&lt;;._1@;:'/'&amp;,)i=. ::](^:_)
   f 'no'
no
   f '/ world! world!/Hello,/ world! world! world!'
Hello, world!
   f '/foo/Hello, world!//B\/\\R/foo/B/\R'
Hello, world!
   f '//'  NB. empty string

   f '/\\/good/\/'
good