| Bytes | Lang | Time | Link |
|---|---|---|---|
| 216 | Swift 6.1 | 250604T162809Z | macOSist |
| 236 | C++ gcc | 250603T163728Z | ceilingc |
| 328 | C++ | 140924T042759Z | Jerry Je |
| 211 | Python 2/3 | 190919T184338Z | Carlos L |
| 110 | Ruby | 180309T124412Z | Asone Tu |
| 102 | Pip | 161008T084452Z | DLosc |
| 395 | BaCon | 161026T183745Z | Peter |
| 226 | Cobra | 140916T074911Z | Οurous |
| 170 | J | 140902T060546Z | algorith |
| 133 | APL | 140903T113559Z | marinus |
| 3198 | Python 2 236 | 140830T160313Z | Bruno Le |
| 190 | Perl | 140830T222053Z | faubi |
Swift 6.1, 222 216 bytes
I wanted to challenge myself somewhat with this one. To the best of my knowledge, the closure below is 100% compliant with both specifications (the one on Esolangs and the one in this question). It also avoids any sort of recursion, meaning infinite loops are actually infinite.
{var e="",p=$0+e,m=0,b=[e,e,e],f={"\(p.removeFirst())"}
while e<p{let c=f()
"/"==c ?{if m>1{while e==b[1]||p != (p.replace(b[1],with:b[2]),p).1{}
b=[b[0],e,e]}
m=(m+1)%3}():(b[m]+="\\"==c ?e<p ?f():e:c)}
return b[0]}
Try it on SwiftFiddle! Takes the program as a String and returns the output as a String.
Here it is with whitespace and meaningful identifiers:
{
var empty = "",
program = $0 + empty,
mode = 0,
buffers = [empty, empty, empty],
first = { "\(program.removeFirst())" }
while empty < program {
let character = first()
"/" == character ? {
if mode > 1 {
while empty == buffers[1] || program != (
program.replace(buffers[1], with: buffers[2]),
program
).1 { }
buffers = [buffers[0], empty, empty]
}
mode = (mode + 1) % 3
}() : (
buffers[mode] += "\\" == character
? empty < program
? first()
: empty
: character
)
}
return buffers[0]
}
Breakdown
{
var empty = "",
program = $0 + empty,
mode = 0,
buffers = [empty, empty, empty],
first = { "\(program.removeFirst())" }
Assign our initial variables:
empty(i.e.e) is simply the empty string. Assigning it to a variable here saves a couple of bytes.program(i.e.p) is the current working program. It's initially assigned to$0, the implicit closure parameter (we concatenate it withemptyto assist the type checker with inference).mode(i.e.m) is the current mode we're in —0corresponds to print mode,1to pattern mode, and2to replacement mode. (There is no dedicated substitution mode.)buffers(i.e.b) contains the output, pattern, and replacement buffers, in that order. Using an array to store these lets us assign directly to the appropriate value by subscripting withmode, instead of having to duplicate the assignment logic for each mode.first(i.e.f) is simply a wrapper aroundprogram.removeFirst(). It's called twice, so this saves a few bytes.
while empty < program {
let character = first()
Loop while the program is non-empty, popping the first character from the program each time. (The empty string compares less than all other strings.)
"/" == character ? {
Check if character is a forward slash, i.e. whether we need to switch modes. (Using the ternary operator with a self-executing closure saves a byte over an if statement.)
if mode > 1 {
while empty == buffers[1] || program != (
program.replace(buffers[1], with: buffers[2]),
program
).1 { }
buffers = [buffers[0], empty, empty]
}
If we're in replacement mode (i.e. mode == 2) when we hit a forward slash, perform a substitution:
- Replace every occurence of the pattern (
buffers[1]) inprogramwith the replacement (buffers[2]). Repeat this until callingreplacestops changing the program.- We take advantage of the execution order of operator arguments and tuple elements to save some bytes — the lhs of
!=(_:_:)is before replacement, and the rhs is after replacement. - While Swift's
replace(_:with:maxReplacements:)method does replace all occurences in the string, it only does one pass, so we still need to loop on it ourselves. replacedoes nothing if the pattern is empty. We want to loop forever in that case, so we manually check if the pattern is empty in the loop condition.
- We take advantage of the execution order of operator arguments and tuple elements to save some bytes — the lhs of
- Clear the pattern and replacement, while leaving the output intact.
mode = (mode + 1) % 3
Increment mode, cycling back to 0 via the modulo operator if needed.
}() : (
buffers[mode] += "\\" == character
? empty < program
? first()
: empty
: character
)
If character is not a forward slash, update the appropriate buffer as follows:
- If
characteris a backslash, check whether the program is empty (i.e. if this backslash is the last character):- If so, do nothing — the closure will return immediately after this anyway.
- Otherwise, pop another character and append that to the buffer, ignoring the backslash.
- If
characteris not a backslash, simply append it to the buffer.
}
return buffers[0]
}
If/when the main loop completes, simply return the output buffer.
C++ (gcc), 287 269 262 249 240 236 bytes
#import<regex>
#define F(x)for(x="";(q=u[++p])-47;)x+=u[p+=q==92/!!q];
int p;main(int q,char**g){for(std::string s,t,u=g[1];q=u[p];)if(q-47)putchar(u[p+=q==92]),p++;else{F(s)F(t)for(u=&u[++p];p=~(q=u.find(s));)u.replace(q,s.size(),t);}}
Slightly golfed less.
#import<regex>
main(int,char**g){
std::string s,t,u=g[1],v;
for(char*p=&u[0],*q;*p;)
if(*p-47) // print if not replacing
p+=*p==92,
putchar(*p++);
else{
s=t="";
for(p++;*p-47;p+=*p==92,s+=*p++) // search string
if(!*p)
exit(0);
for(p++;*p-47;p+=*p==92,t+=*p++) // replacement string
if(!*p)
exit(0);
u=++p;
p=&u[0];
for(v="";q=strstr(p,&s[0]);p=&u[0]) // apply search and replace
u=v+=u.substr(0,q-p)+t+&q[s.size()],
v="";
}
}
C++ 328
Edits:
- Thanks to @algorithmshark for lots of help making the original shorter
- Thanks to @Adalynn for a good suggestion to save bytes
- Thanks to @ceilingcat for many, many very nice pieces of golfing - now even shorter
Here is the code:
#import<bits/stdc++.h>
#define N(x)o[i]);else if(n<x)o[i]==92?
#define O (o[++i]):o[i]==47?n++:
char p[198],*q=p,*s=p+99;int i,t;main(int n,char**m){for(std::string o=m[1];o[i];++i){if(!N(3)putchar O putchar(N(4)*q++=O(*q++=N(5)*s++=O*s++=o[i];if(n>4){for(n=2;~(t=o.find(p,i+1));)o.replace(t,strlen(p),s=p+99);bzero(q=p,198);}}}
Python 2/3 (211 bytes)
The following code, based on Bruno Le Floch's answer, is Python 2 and Python 3 compatible.
Moreover, being iterative rather than recursive it does not risk hitting the maximum recursion depth of Python.
def S(c):
while c:
B=["","",1]
for m in 0,1,2:
while c:
if"/"==c[0]:c=c[1:];break
if"\\"==c[0]:c=c[1:]
if m:B[m-1]+=c[0]
else:yield c[0]
c=c[1:]
while c and B[0]in c:c=c.replace(*B)
Ruby, 119 110 bytes
Terminates with exception
r=->s,o=$>{s[k=s[0]]='';k==?/?o==$>?s.gsub!([r[s,''],e=r[s,'']][0]){e}:t=o:o<<(k==?\\?s[0]+s[0]='':k);t||redo}
Terminates cleanly (116 bytes)
r=->s,o=$>{s[k=s[0]||exit]='';k==?/?o==$>?s.gsub!([r[s,''],e=r[s,'']][0]){e}:t=o:o<<(k==?\\?s[0]+s[0]='':k);t||redo}
Pip, 100 102 bytes
I hadn't ever proven Pip to be Turing-complete (though it's pretty obviously so), and instead of going the usual route of BF I thought /// would be interesting. Once I had the solution, I figured I'd golf it and post it here.
101 bytes of code, +1 for -r flag:
i:gJnf:{a:xW#i&'/NE YPOia.:yQ'\?POiya}W#iI'\Q YPOiOPOiEIyQ'/{p:VfY0s:VfIyQ'/WpNi&YviR:Xp{++y?ps}}E Oy
Here's my ungolfed version with copious comments:
; Use the -r flag to read the /// program from stdin
; Stdin is read into g as a list of lines; join them on newline and assign to c for code
c : gJn
; Loop while c is nonempty
W #c {
; Pop the first character of c and yank into y
Y POc
; If y equals "\"
I yQ'\
; Pop c again and output
O POc
; Else if y equals "/"
EI yQ'/ {
; Build up pattern p from empty string
p : ""
; Pop c, yank into y, loop while that is not equal to "/" and c is nonempty
W #c & '/ NE Y POc {
; If y equals "\"
I yQ'\
; Pop c again and add that character to p
p .: POc
; Else, add y to p
E p .: y
}
; Yank 0 so we can reliably tell whether the /// construct was completed or not
Y0
; Build up substitution s from empty string
s : ""
; Pop c, yank into y, loop while that is not equal to "/" and c is nonempty
W #c & '/ NE Y POc {
; If y equals "\"
I yQ'\
; Pop c again and add that character to s
s .: POc
; Else, add y to s
E s .: y
}
; If the last value yanked was "/", then we have a complete substitution
; If not, the code must have run out; skip this branch, and then the outer loop
; will terminate
I yQ'/ {
; While pattern is found in code:
W pNc {
; Set flag so only one replacement gets done
i : 0
; Convert p to a regex; replace it using a callback function: if ++i is 1,
; replace with s; otherwise, leave unchanged
c R: Xp {++i=1 ? s p}
}
}
}
; Else, output y
E Oy
}
Try it online! (Note that TIO doesn't give any output when the program is non-terminating, and it also has a time limit. For larger examples and infinite loops, running Pip from the command line is recommended.)
BaCon, 391 387 395 bytes
From the contributions on this page I only got the Python program to work. The others work for some /// samples, or do not work at all. Therefore, I decided to add my version, which is an implementation in BASIC.
To compete in a CodeGolf contest with BASIC is not easy, as BASIC uses long words as statements. The only abbreviation commonly found in BASIC is the '?' sign, which means PRINT.
So the below program may never win, but at least it works with all demonstration code on this Codegolf page and on the Esolangs Wiki. Including all versions of the "99 bottles of beer".
p$=""
r$=""
INPUT i$
WHILE LEN(i$)
t$=LEFT$(i$,1)
i$=MID$(i$,2)
IF NOT(e) THEN
IF t$="\\" THEN
e=1
CONTINUE
ELIF t$="/" THEN
o=IIF(o<2,o+1,0)
IF o>0 THEN CONTINUE
FI
FI
IF o=1 THEN
p$=p$&t$
ELIF o=2 THEN
r$=r$&t$
ELIF o=0 THEN
IF LEN(p$) THEN i$=REPLACE$(i$,p$,r$)
IF NOT(INSTR(t$&i$,"/")) THEN
?t$;
BREAK
ELSE
?LEFT$(i$,INSTR(i$,"/")-1);
i$=MID$(i$,INSTR(i$,"/"))
FI
p$=""
r$=""
FI
e=0
WEND
?i$
Cobra - 226
sig Z as String
def f(l='')
m=Z(do=[l[:1],l=l[1:]][0])
n as Z=do
if'/'<>(a=m())>'',return if(a=='\\',m(),a)+n()
else,return''
print n()stop
p,s=n(),n()
if''<l
while p in l,l=l[:l.indexOf(p)+1]+s+l[p.length:]
.f(l)
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&$`((2{.{:@>&.>)((j{.]),-i@=`p@.~:~/@[,]}.~#@p+j=.0{p I.@E.])i 5;@}.&,'/';"0;&.>)@.(2<#)@}.[4:1!:2~{:@>@p=.>@{.@[)@((0;(0,:~1 0,.2);'\';&<1 0)<;._1@;:'/'&,)i=. ::](^:_)
To explain, I will break it up into subexpressions.
i =. ::](^:_))
parse =: ((0;(0,:~1 0,.2);'\';&<1 0)<;._1@;:'/'&,)
print =: 4:1!:2~{:@>@p=.>@{.@[
eval =: 0&$`((2{.{:@>&.>)sub 5;@}.&,'/';"0;&.>)@.(2<#)@}.
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, appliesfrepeatedly 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.parsetokenizes 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—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);'\';&<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
<;._1to 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 fromout/patt/repl/restall 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 duringeval, because making<;._1leave them alone ends up costing a lot more.The fork
(eval [ print)executesprinton the result fromparsefor its side-effects, and then runseval.printis 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 verbp.pis defined as>@{.@[, 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 insub.evalevaluates the remainder of the processed program. If we don't have a full pattern or a full replacement,evalthrows it out and just returns an empty list, which terminates evaluation by making;:(fromparse) error out on the next iteration. Else,evalfully parses the pattern and replacement, corrects the remainder of the source, and then passes both tosub. By explosion:@}. NB. throw out printed part @.(2<#) NB. if we have a pattern and repl: 2{. NB. take the first two cuts: &.> NB. in each cut: {:@> NB. drop escaping \ from chars ( ) NB. (these are pattern and repl) &.> NB. in each cut: ; NB. revert to source form '/';"0 NB. attach a / to each cut &, NB. linearize (/ before each cut) 5 }. NB. drop '/pattern/repl/' ;@ NB. splice together ( sub ) NB. feed these into sub ` NB. else: 0&$ NB. truncate to an empty listsubis where one (possibly infinite) round of substitutions happens. Because of the way we set upeval, 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 ofi—the fact that it modifies only the right argument and keeps passing in the same left—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
putility we defined, this isn't that big of a problem. In fact, we can access the pattern in one character, just by usingp, because of its>@{.@[definition: the Unbox of the First item of the Left arg. Getting the replacement is tricker, but the shortest way would bep&|., 2 chars shorter than manually getting it out.The second problem is that
iexits 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, replacingp&|..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. iterateThis cycle repeats due to the use of
i, until something outside ofsuberrors 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 beforesubgoes 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
subloop 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
subhalfway 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).
- Exit out of the negate-forever loop, error out of the
Example usage:
f=:(0&$`((2{.{:@>&.>)((j{.]),-i@=`p@.~:~/@[,]}.~#@p+j=.0{p I.@E.])i 5;@}.&,'/';"0;&.>)@.(2<#)@}.[4:1!:2~{:@>@p=.>@{.@[)@((0;(0,:~1 0,.2);'\';&<1 0)<;._1@;:'/'&,)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
APL (133)
{T←''∘{(0=≢⍵)∨'/'=⊃⍵:(⊂⍺),⊂⍵⋄(⍺,N⌷⍵)∇⍵↓⍨N←1+'\'=⊃⍵}⋄⍞N←T⍵⋄p N←T 1↓N⋄r N←T 1↓N⋄''≡N:→⋄∇{⍵≡p:∇r⋄∨/Z←p⍷⍵:∇(r,⍵↓⍨N+≢p),⍨⍵↑⍨N←1-⍨Z⍳1⋄⍵}1↓N}
This is a function that takes the /// code as its right argument.
Ungolfed, with explanation:
slashes←{
⍝ a function to split the input string into 'current' and 'next' parts,
⍝ and unescape the 'current' bit
split←''∘{
⍝ if the string is empty, or '/' is reached,
⍝ return both strings (⍺=accumulator ⍵=unprocessed)
(0=≢⍵)∨'/'=⊃⍵:(⊂⍺),⊂⍵
⍝ otherwise, add current character to accumulator,
⍝ skipping over '\'s. (so if '\/' is reached, it skips '\',
⍝ adds '/' and then processes the character *after* that.)
idx←1+'\'=⊃⍵
(⍺,idx⌷⍵)∇idx↓⍵
}
⍞ next ← split ⍵ ⍝ output stage
pat next ← split 1↓next ⍝ pattern stage, and eat the '/'
rpl next ← split 1↓next ⍝ replacement stage, and eat the '/'
⍝ if there are no characters left, halt.
''≡next:⍬
⍝ otherwise, replace and continue.
∇{ ⍝ if the input string equals the pattern, return the replacement and loop
⍵≡pat:∇rpl
⍝ otherwise, find occurences, if there are, replace the first and loop
∨/occ←pat⍷⍵:∇(rpl, (idx+≢pat)↓⍵),⍨ (idx←(occ⍳1)-1)↑⍵
⍝ if no occurences, return string
⍵
}1↓next
}
Python 2 (236), Python 3 (198?)
from __future__ import print_function
def d(i):
t=0;p=['']*3+[1]
while i:
if'/'==i[0]:t+=1
else:
if'\\'==i[0]:i=i[1:]
p[t]+=i[0]
i=i[1:]
print(end=p[0]);p[0]=''
if t>2:
while p[1]in i:i=i.replace(*p[1:])
d(i);i=0
Called as d(r"""/foo/Hello, world!//B\/\\R/foo/B/\R"""). The triple quotes are only needed if the /// program contains newlines: otherwise simple quotes are ok.
EDIT: This interpreter now prints stuff as expected (previously it only printed at the very end, cf. comments). For Python 3, remove the first line (but I don't have Python 3 on my ancient install, so cannot be sure there is no other change).
Perl - 190
$|=1;$/=undef;$_=<>;while($_){($d,$_)=/(.)(.*)/;eval(!$e&&({'/','$a++','\\','$e=1'}->{$d})||('print$d','$b.=$d','$c.=$d')[$a].';$e=0');if($a==3){while($b?s/\Q$b/$c/:s/^/$c/){}$a=0;$b=$c=''}}
Reads /// program from stdin until EOF.