Question

I am defining a multi-quine as:

A set of n programs in n different programming languages, such that each one of them, when given no input, outputs its exact source code, and when given n as input, output the source code of the *n*th program.

This is not to be confused with a circular sequence of programs, where each program outputs the source code of the next, until the first program has been outputted. In this instance, each program is not a quine, which defeats the point. These cyclic sets, while being interesting brainteasers for high values of n, are quite trivial to implement.

Complex, in this instance, means "for values of n greater than or equal to 2." I believe that a solution for n = 2 is complex enough in this instance. However, a general solution (read: strategy) for all values of n is the target.

I understand how "simple" quines are written, however I cannot seem to get my head around complex multiquines, which fascinate me. A part of me hopes that there is no solution except for shear ingenuity in the mind of the programmer - although I see this as unlikely.

Was it helpful?

Solution

There's nothing extraordinary in quines, multilingual quines, multi-quines, you name it. They can all be written pretty much automatically.

Here is, by way of example, a bog-standard, verbose, inelegant, inefficient quine in C++. It is however, with all its shortcomings, quite easy to modify to do what we want.

#include <iostream>
#include <string>
#include <cstdlib>

std::string show (const std::string& in) {
    std::string res = "\"";
    for (std::string::const_iterator it = in.begin(); it < in.end(); ++it) {
        switch (*it) {
            case '"':
            case '\\':
                res += '\\';
            default:
                res += *it;
        }
    }
    res += "\"";
    return res;
}

int main (int argc, char* argv[])
{
    std::string arr[] = { // beginning ends here
"#include <iostream>",
"#include <string>",
"#include <cstdlib>",
"",
"std::string show (const std::string& in) {",
"    std::string res = \"\\\"\";",
"    for (std::string::const_iterator it = in.begin(); it < in.end(); ++it) {",
"        switch (*it) {",
"            case '\"':",
"            case '\\\\':",
"                res += '\\\\';",
"            default:",
"                res += *it;",
"        }",
"    }",
"    res += \"\\\"\";",
"    return res;",
"}",
"",
"int main (int argc, char* argv[])",
"{",
"    std::string arr[] = { // beginning ends here",
"======",
"    };",
"    int n = argc == 1 ? 0 : std::atoi(argv[1]);",
"    if (n == 0) {",
"        int i, j;",
"        for (i = 0; arr[i] != \"======\"; ++i) std::cout << arr[i] << std::endl;",
"        for (j = 0; j < sizeof(arr)/sizeof(arr[0]); ++j) std::cout << show(arr[j]) << ',' << std::endl;",
"        for (++i; i < sizeof(arr)/sizeof(arr[0]); ++i) std::cout << arr[i] << std::endl;",
"    } else {",
"    }",
"}",
    };
    int n = argc == 1 ? 0 : std::atoi(argv[1]);
    if (n == 0) {
        int i, j;
        for (i = 0; arr[i] != "======"; ++i) std::cout << arr[i] << std::endl;
        for (j = 0; j < sizeof(arr)/sizeof(arr[0]); ++j) std::cout << show(arr[j]) << ',' << std::endl;
        for (++i; i < sizeof(arr)/sizeof(arr[0]); ++i) std::cout << arr[i] << std::endl;
    } else {
    }
}

As you can see, the heart of the program is a little function called show, which takes a string and returns its representation as a C++ literal. The overall structure is as follows: print the start portion of the string array; print the entire array piped through show; print the end portion of the array. The string array is a copy of the program, inserted in the middle of the program. The initial portion is separated from the final portion by a special "=====" string which is not copied from the program (it is only printed once, through show).

It is easy to insert any additional actions, like printing another quine in a different language. I have inserted a placeholder for such an action.

Now it is absolutely trivial to translate this into any programming language (say FORTRAN for example). Suppose we have done it, and it is made up from lines L1, L2, ..., LN. We insert these statements in the placeholder:

std::cout << "L1" << std::endl;
std::cout << "L2" << std::endl;
...
std::cout << "LN" << std::endl;

We modify the string array accordingly. Voilà, we have a quine that can print itself, and also a quine in FORTRAN, depending on a command line argument.

OK, what about the FORTRAN quine? It can only print itself, not the C++ quine. No problem, let's copy the C++ quine back into the FORTRAN quine.

But the C++ quine already contains the entire FORTRAN quine, twice?

No problem, as the FORTRAN quine can already print itself. Thus, we only need to copy the original C++ lines back to FORTRAN. No need to duplicate FORTRAN within itself once again (or twice).

We only need to modify FORTRAN slightly. When we ask the FORTRAN quine to print the C++ quine, it should print all the C++ lines and also all the FORTRAN lines, twice: once as Li and once as std::cout << "Li" << std::endl;, just like the C++ quine has it. We then get the C++ quine (including the FORTRAN quine) back.

We also need to carry these FORTRAN modifications back to C++ (that is, modify std::cout << "Li" << std::endl; lines). And the wave of modifications stops here.

That's it, we have two programs that can print either itself or each other, depending on the command line argument.

I encourage you to actually do all this.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top