Permanently self-modifying code

14

1

Now, we all know most languages have very simple ways to "self-modify" code. However, what if you were to actually modify the code and edit parts of it...on disk?

Your goal is to make code that prints a number, then edits its own file to replace the number with the next one in the Fibonacci sequence like so:

$ ./program
1
$ ./program
1
$ ./program
2
$ ./program
3
$ ./program
5
[etc...]

Rules

  1. You may not store the number(s) "outside" of the code. No comments, no telling the script to exit, no EOF, etc.
  2. If your code works with any filename, subtract 2 from your byte amount and write $BYTESNOW ($ORIGINALBYTES - 2) in your title. (Filenames are assumed to be within the range of any alphanumeric file path.)
  3. Your code must write the output to the file on it's own, without any external piping assistance.
  4. Your code can start from one or zero. It doesn't matter.

NO_BOOT_DEVICE

Posted 2017-01-22T01:45:23.020

Reputation: 419

8

Next time, please post your idea in the Sandbox instead and leave the post there for a few days to receive feedback.

– JungHwan Min – 2017-01-22T01:50:27.663

2Is it allowed to call the program by invoking the interpreter of the programming language (e.g. perl6 program), or does it have to include the shebang line so that it can be called as ./program? – smls – 2017-01-22T11:37:52.473

1Also, if we don't want to go for the -2 bytes bonus, can we choose a single-byte filename or does it have to be program, and can we assume it's located in the current working directory? – smls – 2017-01-22T11:44:33.970

Can it be allowed to fail when large numbers begin implicitly converting to exponential notation? – Patrick Roberts – 2017-01-22T18:40:58.633

Why only 2 bytes bonus? Most languages, Eg. Lua, have it easier just to do "a" instead of arg[0]. It doesn't seem worth it. – ATaco – 2017-01-22T22:20:10.757

@ATaco I just used the bonus even though not using it would be shorter. It seems less "cheaty" that way, because you're not assuming anything. – Patrick Roberts – 2017-01-22T22:42:07.790

Answers

7

Bash, 52 47 (49-2) bytes

EDITS:

  • Saved 5 bytes, by starting with 1 instead of 0. Thanks @Leo !

Golfed

A=$[1+0]
echo $A
sed -ri "s/\w+\+(\w+)/\1+$A/" $0

Test

>for i in `seq 10`
> do
> ./fibo
> done
1
1
2
3
5
8
13
21
34
55

zeppelin

Posted 2017-01-22T01:45:23.020

Reputation: 7 884

2I think you could save 1 byte by starting from [1+0] instead of [-1+1] (see 4th rule of the challenge) – Leo – 2017-01-22T11:57:09.210

2Actually, that would make you save even more bytes by removing the -? from the regex. And since you're there, you could also remove the first capturing group :) – Leo – 2017-01-22T12:01:47.003

@Leo That's a nice advice, thank you ! – zeppelin – 2017-01-22T12:25:29.853

2

Python 2, 118 111 bytes ( 113 - 2 )

a,b=0,1;print a
f=open(__file__,'r+')
s=f.read()
s=s.replace(s[4:s.find(';')],`b`+','+`a+b`)
f.seek(0)
f.write(s)

It works with any valid filename. There is not much to explain here, the code itself is very verbose.

Thanks to FlipTack for reminding me, close() is not mandatory.

Gurupad Mamadapur

Posted 2017-01-22T01:45:23.020

Reputation: 1 791

1Can't you just use f=open(...) instead of the with statement? – FlipTack – 2017-01-22T09:52:47.577

2

Batch, 81 bytes

@call:c
@set/az=x+y
@echo %x%
@echo>>%0 @set/ax=%z%,y=%x%
:c
@set/ax=0,y=1

Note: the trailing newline is significant. Requires the script to be invoked using its full name including extension. Output starts at 0.

Since Batch can't realistically edit a file, I just add extra lines to the end of the file, so eventually it will know which the next number to print is. The >>%0 placement saves a byte because I can't precede it with a digit.

Neil

Posted 2017-01-22T01:45:23.020

Reputation: 95 035

1

C, 142 bytes (144 - 2)

void main(int x,char**a){FILE*f=fopen(*a,"r+");fseek(f,27,0);char n=fgetc(f),m=fgetc(f);fseek(f,27,0);printf("%d\n",fputc(fputc(m,f)?n+m:1,f));}

It's pretty straight forward. First it reads then saves the two chars at position 0x1A in the header. I probably could've looked deeper to find a safer spot to save the data but it works for me on my machine running OSX, compiled with GCC 4.2ish and I doubt it's very portable. Also, since it's based off chars it overflows after the 13th iteration.

It gives the output:

1
1
2
3
5
8
13
21
34
55

Ahemone

Posted 2017-01-22T01:45:23.020

Reputation: 608

1

Node.js, 152 137 bytes (139 - 2)

Separated with newlines for clarity, not part of byte count.

f=_=>require('fs').writeFileSync(__filename,
`f=${f};f()`.replace(/(\d[^,]*),(\d[^\)]*)/,
(m,a,b)=>`${b=+b},${+a+b}`),console.log((0,1)));
f()

Explanation:

f=_=>                          // define `f` as function with a single unused argument `_`
  require('fs').writeFileSync( // import the standard filesystem module and overwrite file
    __filename,                // string var containing path of file for current module
    `f=${f};f()`.replace(      // template string containing source of entire script
      /(\d[^,]*),(\d[^\)]*)/,  // regexp to match and group the numbers in this script
      (m,a,b)=>                // replace function with arguments match, group a, group b
        `${b=+b},${+a+b}`      // template string incrementing fibonacci numbers in place
    ),                         // end replace()
    console.log(               // prints to stdout, `undefined` passed to argument
      (0,1)                    // comma separated group returns value of last expression
    )                          // end console.log()
  )                            // end fs.writeFileSync()
;                              // end statement defining `f` as arrow function
f()                            // run function to modify script and print fibonacci number

Usage:

// assuming above script is stored in program.js
$ node program
1
$ node program
1
$ node program
2
$ node program
3
$ node program
5
...

Patrick Roberts

Posted 2017-01-22T01:45:23.020

Reputation: 2 475

1

Python 3.6, 96 91 (93-2) bytes

a,b=0,1
f=open(__file__,"r+");next(f);f.write(f"a,b={b,a+b}\n{next(f)}{f.seek(0)}");print(b)

hardcoding the filename would save 5 bytes (88 bytes):

a,b=0,1
f=open("f","r+");next(f);f.write(f"a,b={b,a+b}\n{next(f)}{f.seek(0)}");print(b)

Saved some bytes thanks to @Artyer

ovs

Posted 2017-01-22T01:45:23.020

Reputation: 21 408

1How about this (88 bytes) a,b=0,1 f=open('f','r+');next(f);f.write(f'a,b={b,a+b}\n{next(f)}{f.seek(0)}');print(b)# – Artyer – 2017-01-23T15:42:26.760

1

bash + Unix utilities, 43 bytes (45-2)

dc -e9k5v1+2/z^5v/.5+0k1/p;sed -i s/z/z1+/ $0

The first time this is run, it uses dc to compute the 1st Fibonacci number via the Binet formula. Each call to sed modifies the program by changing the string passed to dc; this change tells dc to add an additional 1 to the exponent in the formula, which causes it to compute the next number in the Fibonacci sequence each time.

Test

> for k in {1..10}
> do
> ./fib
> done
1
1
2
3
5
8
13
21
34
55

To illustrate how it works, at this point, after the 55 is printed, the program has been modified to read:

dc -e9k5v1+2/z1+1+1+1+1+1+1+1+1+1+^5v/.5+0k1/p;sed -i s/z/z1+/ $0

so running it again yields

> ./fib
89

and the program now reads:

dc -e9k5v1+2/z1+1+1+1+1+1+1+1+1+1+1+^5v/.5+0k1/p;sed -i s/z/z1+/ $0

Mitchell Spector

Posted 2017-01-22T01:45:23.020

Reputation: 3 392

I like this ! Well done ! – zeppelin – 2017-01-24T19:49:57.387

@zeppelin Thank you -- this avoids the issues with the previous version we had. – Mitchell Spector – 2017-01-24T19:50:58.430

1

SmileBASIC 3, 99 bytes (101 -2)

-2 byte bonus because it works with any filename.

A=0B=1F$="TXT:"+PRGNAME$()S$=LOAD(F$)SAVE F$,SUBST$(S$,0,INSTR(S$,"F"),FORMAT$("A=%DB=%D",B,A+B))?A+B

This one does work, and it somehow ended up being the same size as my broken one!

snail_

Posted 2017-01-22T01:45:23.020

Reputation: 1 982

It's much shorter if you don't do the bonus – 12Me21 – 2017-02-11T22:26:55.783

forcing a specific filename makes me feel like a freak. I'm beating half these answers anyway – snail_ – 2017-02-12T03:06:26.093

I think not turning off the LOAD dialog is much worse. – 12Me21 – 2017-02-12T03:15:05.547

It's actually shorter if you load it into slot 1 and use PRGEDIT commands to replace the first line (and add a linebreak after A=0B=1) And you also don't need A=0 the first time. – 12Me21 – 2017-02-12T03:33:52.840

0

R, 145 bytes (147 - 2)

a=c(1,1)
cat(a[1])
R=readLines(f<-sub("^.+=","",grep("^--f",commandArgs(F),v=T)))
cat(c(sprintf("a=c(%i,%i)",a[2],sum(a)),R[-1]),file=f,sep="\n")

(Has a trailing newline). It works with any valid filename.

plannapus

Posted 2017-01-22T01:45:23.020

Reputation: 8 610

0

Perl 6, 67 62 bytes (64 - 2)

say (1,1,*+*...*)[1];$*PROGRAM.&{.spurt: .slurp.&{S/\[<(\d+/{$/+1}/}}

say 0+1;$*PROGRAM.&{.spurt: .slurp.&{S/(\d+).(\d+)/$1+{$0+$1}/}}

smls

Posted 2017-01-22T01:45:23.020

Reputation: 4 352

0

Clojure, 209 204 195 bytes

0 1(let[u #(apply str %)a"./src/s.clj"p #(Long/parseLong(u %))l(fn[v](split-with #(Character/isDigit %)v))c(slurp a)[n[_ & r]](l c)[m r](l r)b(+(p n)(p m))](println b)(spit a(str(p m)" "b(u r))))

-5 bytes by switching to parse the numbers as a long instead of an integer, and removing a couple missed spaces.

-9 bytes by removing the space between the second number and (let...) (most expensive space ever!).

See the pregolfed code comments for a description.

Tested again, and it no longer throws unmatched bracket errors. It works up to 7540113804746346429, at which point it throws an integer overflow exception.

Also note, this assumes the source code is located at "./src/s.clj".

0 1 ; Starting numbers
(let [; The first 4 entires are shortcuts to functions and data that are used more than once
      u #(apply str %) ; Turns a list into a string
      a "./src/s.clj" ; Current location
      p #(Integer/parseInt (u %)) ; Integer parsing shortcut
      ; Used to split a string on digits to parse them out
      l (fn [v] (split-with #(Character/isDigit %) v))
      src (slurp a) ; Get the source
      [n [_ & r]] (l src) ; Use deconstructuring to grab the first number
      [m r] (l r) ; Same as above, grabbing the second number
      n' (+ (p n) (p m)) ; Parse the 2 numbers, and add them
      ; Put everything back together, only this time with the new numbers
      k (str (p m) " " n' (u r))]
  (println n') ; Print the new number
  (spit a k)) ; Overwrite the old source

Carcigenicate

Posted 2017-01-22T01:45:23.020

Reputation: 3 295

0

Ruby, 68 bytes (70-2)

p$a=1+0
f=open$0,'r+'
s=f.read.sub /\d+.(\d+)/,"\\1+#$a"
f.seek 0
f<<s

Value Ink

Posted 2017-01-22T01:45:23.020

Reputation: 10 608

0

Stacked, noncompeting, 65 (67 - 2) bytes

Some issues regarding file IO were fixed in the most recent series of commits. Thus, noncompeting.

2:>
:sum\tail...\stack:0#out repr LF+program LF split last+d0\write

Here's a link to the github.

Example execution

(I omitted the actual path for clarity.)

C:\
λ type permanently-self-modifying-code.stk
2:>
:sum\last\stack:0#out repr LF+program LF split last+d0\write
C:\
λ stacked permanently-self-modifying-code.stk
1

C:\
λ stacked permanently-self-modifying-code.stk
1

C:\
λ stacked permanently-self-modifying-code.stk
2

C:\
λ stacked permanently-self-modifying-code.stk
3

C:\
λ stacked permanently-self-modifying-code.stk
5

C:\
λ stacked permanently-self-modifying-code.stk
8

Explanation

How this works is by taking a pair of numbers to begin the sequence (2:> in this case is the integer range [0, 2), which is (0 1)), then performing the Fibonacci transformation on them, like so:

:sum\last\                     top of stack: (x y)
:              duplicate.             stack: ((x y) (x y))
 sum           sum of TOs.            stack: ((x y) x+y)
    \          swap order.            stack: (x+y (x y))
     last      obtain last element.   stack: (x+y y)
         \     swap order.            stack: (y x+y)

On each run, this transformation is executed on the top of stack. Then, the stack is pushed to the stack, duplicated, and its first member obtained (stack:0#). This item is then outputted, and is the desired Fibonacci number. repr then takes the representation of the stack and appends a newline. Then, the program is pushed to the stack, and split on newlines. Then, we take the last member (the last line), and append this to the aforementioned string. Finally, we push d0 (the file itself; think dollar sign 0 == $0.) and write to it.

Conor O'Brien

Posted 2017-01-22T01:45:23.020

Reputation: 36 228