Make Plan 9 Cat Turing Complete in as Few Bytes as Possible

5

0

Your task is to modify the original Plan 9 cat tool for UNIX, in order to make it a fully turing complete language interpreter. You may make use of ANSI escape codes and the backspace character (\x08) to aid in this.

You must submit:

  • A modified version of the Plan 9 cat tool
  • A hexdump of the turing machine that can be run like so: cat turing_machine where turing_machine is the file you wrote.

Your score is the number of bytes you changed in the Plan 9 cat source code (where replacing a character counts as two bytes because you have to remove it and then replace it).

Here is the Plan 9 cat source code, taken from here.

#include <u.h>
#include <libc.h>

void
cat(int f, char *s)
{
    char buf[8192];
    long n;

    while((n=read(f, buf, (long)sizeof buf))>0)
        if(write(1, buf, n)!=n)
            sysfatal("write error copying %s: %r", s);
    if(n < 0)
        sysfatal("error reading %s: %r", s);
}

void
main(int argc, char *argv[])
{
    int f, i;

    argv0 = "cat";
    if(argc == 1)
        cat(0, "<stdin>");
    else for(i=1; i<argc; i++){
        f = open(argv[i], OREAD);
        if(f < 0)
            sysfatal("can't open %s: %r", argv[i]);
        else{
            cat(f, argv[i]);
            close(f);
        }
    }
    exits(0);
}

The headers in the above source are u.h and libc.h. Your code must conform to the C99 standard.

KaiJ

Posted 2019-05-26T05:46:20.797

Reputation: 153

Question was closed 2019-05-27T02:45:56.807

2Is the "Turing machine" code in a Turing-complete language of our choice, or an actual Turing machine? What is it supposed to do? I would have thought the idea would be to demonstrate Turing-completeness by being able to run the code of any file, but the challenge requiring and counting bytes for a specific Turing machine that's submitted makes me think I'm not understanding it. – xnor – 2019-05-26T06:32:14.973

Yeah, I should probably change that, thanks for pointing it out – KaiJ – 2019-05-26T09:05:33.987

I'm still not clear what's going on with the "turing machine" file. For instance, can it be C code that our code executes? – xnor – 2019-05-26T21:40:53.320

So we are supposed to create an interpreter for a Turing complete language, and the score is how close it is to the plan 9 cat program? – Embodiment of Ignorance – 2019-05-27T01:26:11.303

Answers

5

266 256 250 245 149 bytes (probably)

Thanks to @someone for suggesting using a cyclic tag system (originally I was using a modified version of Brainfuck).

Real computers aren't actually Turing-complete, because they have a finite amount of memory, whereas a Turing-machine has an infinite amount of memory. This is an implementation of a cyclic tag system with 9999 bytes of memory (although you could very easily change that), and with an initial word of 1 (I'm not entirely sure if cyclic tag systems with a fixed initial word are Turing-complete, but they probably are). The length of the program must be &leq;8192, in this format:

00 (the first byte is ignored) 
01 01 02 00 (first rule: 001 (null character to mark end))
02 02 02 01 02 00 (second rule: 11101)
03 (marks end of program)

There is no I/O. The added code should be C99-compliant (provided the input code doesn't use more than 9999 bytes of memory at once), but I'm pretty sure you can't (portably) declare main as void in C99.

#include <u.h>
#include <libc.h>

void
cat(int f, char *s)
{
    char buf[8192],m[9999];
    long n;

    while((n=read(f, buf, (long)sizeof buf))>0){char m[9999],*M=m+9998,*s=m,*e=m+1,*i=buf;*m=2;m[1]=0;do{for(i++;*i;i++){if(*i>2)i=buf+1;if(*s>1){*e++=*i;if(e>M)e=s;}}if(++s>M)s=m;}while(*s);
        /*if(write(1, buf, n)!=n)
            sysfatal("write error copying %s: %r");*/}
    if(n < 0)
        sysfatal("error reading %s: %r", s);
}

void
main(int argc, char *argv[])
{
    int f, i;

    argv0 = "cat";
    if(argc == 1)
        cat(0, "<stdin>");
    else for(i=1; i<argc; i++){
        f = open(argv[i], OREAD);
        if(f < 0)
            sysfatal("can't open %s: %r", argv[i]);
        else{
            cat(f, argv[i]);
            close(f);
        }
    }
    exits(0);
}

Slightly de-golfed, and printing each step:

#include <u.h>
#include <libc.h>

void
cat(int f, char *s)
{
    char buf[8192];
    size_t n;

    while((n=read(f, buf, (long)sizeof buf))>0) {
        char mem[9999];
        *mem=2;mem[1]=0;
        char* mem_end = &mem[9998];
        char*start=mem,*end=mem+1,*i=buf;
        do {
            for (char* p = start; p < end; p++) {
                printf("%d",*p - 1);
            }
            printf("\n");
            for (i++;*i;i++) {
                if(*i>2)i = buf+1;
                if (*start > 1){
                    *end++=*i;
                    if (end > mem_end)
                        end = mem;
                }
            }
            if (++start>mem_end)start=mem;
        } while (*start);
        /*if(write(1, buf, n)!=n)
            sysfatal("write error copying %s: %r");*/
    }
    if(n < 0)
        sysfatal("error reading %s: %r", s);
}

void
main(int argc, char *argv[])
{
    int f, i;

    argv0 = "cat";
    if(argc == 1)
        cat(0, "<stdin>");
    else for(i=1; i<argc; i++){
        f = open(argv[i], OREAD);
        if(f < 0)
            sysfatal("can't open %s: %r", argv[i]);
        else{
            cat(f, argv[i]);
            close(f);
        }
    }
    exits(0);
}

The following Python script converts any cyclic tag system into code which is accepted by this program:

productions =   [[0,0,1],
                 [0],
                 [0,0],
                 [0,1,1,1]]
f = open("turing_machine","w")
f.write("\0") # First byte is ignored
for production in productions:
    for c in production:
        f.write(chr(c+1))
    f.write("\0")
f.write("\3")
f.close()

When you run the output of this program with the de-golfed version, you get:

1
001
01
1
0111
111
110
1000
0000111
000111
00111
0111
111
11001
10010
001000
01000
1000
0000
000
00
0

Leo Tenenbaum

Posted 2019-05-26T05:46:20.797

Reputation: 2 655

Do you really need the &&*mem check for ]? – my pronoun is monicareinstate – 2019-05-27T00:39:58.077

You're right that I shouldn't, but I just took it out and I got a segmentation fault with the hello world program. I'm not sure why... – Leo Tenenbaum – 2019-05-27T01:54:27.733

Does it error with GCC with -g -fsanitize=address? I think you can also use the variable m directly as the memory pointer instead of M. Bitwise cyclic tag also might or might not turn out shorter. – my pronoun is monicareinstate – 2019-05-27T04:34:00.397

You were right that it was shorter. Also I think I figured out the problem with the Brainfuck version: the memory wasn't initialized to 0 (it was originally, but then I made it slightly shorter by making m local). – Leo Tenenbaum – 2019-05-27T14:48:02.443