Next post: Half Hour Hacks Volume 1: Excel

Big Recursion

I was thinking about recursion. The classic example of an elegant recursive algorithm is:

def factorial(n):
  if n <= 1:
    return 1
  else:
    return n * factorial(n-1)

Recursive functions call themselves, eventually returning the correct answer.

I thought, why stop at the function level? I want to make it bigger - how about a whole program that calls itself -- and modifies itself? Of course, this would be really impractical in terms of memory, but it sounded like fun.

Big recursion

So, during my lunch at work, I put the following together. (C# 2.0)
If you run the program, it will compute a factorial for you... in a non-standard way.

static void Main(string[] args)
{
 if (args.Length == 0)
 {
  Console.WriteLine("Enter number:");
  string strNum = Console.ReadLine();
  int nInput = int.Parse(strNum);

  string strLoc = System.Reflection.Assembly.GetExecutingAssembly().Location;
  int nReturn = int.Parse(ExecuteAndWait(strLoc, (nInput).ToString()));
  Console.WriteLine("The answer is: "+ nReturn);
 }
 else
 {
  int nInput = int.Parse(args[0]);
  Recurse(nInput);
 }
}

private static void Recurse(int nInput)
{
 if (nInput <= 1)
  Console.Write("1");
 else
 {
  string strLoc = System.Reflection.Assembly.GetExecutingAssembly().Location;
  int nReturn = int.Parse(ExecuteAndWait(strLoc, (nInput - 1).ToString()));
  Console.Write(nInput * nReturn);
 }
}

Although this was fun, it wasn't extreme enough. I wanted something even bigger, more impractical, and more hacky. So, I invented "bigger" recursion. Have fun understanding what this next one does.

Even Bigger Recursion

(Warning: the following code will compile but is not guaranteed to work on your machine. It's extremely fragile, for reasons that will become clear. I used Visual Studio 2005 C# console app to build it.)
Hint 1: The byte 0xE2 is not commonly found in C# executables.
Hint 2: this is my directory listing after I run the program and find the factorial of 5:
Bigger2.exe
Bigger3.exe
Bigger4.exe
Bigger5.exe
BiggerRecursion.exe
BiggerRecursion.pdb
BiggerRecursion.vshost.exe

using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.ComponentModel;
using System.IO;

namespace BigRecursion
{

 class Program
 {
  static volatile byte s_byteCurrent = 0xE2;
  static volatile byte s_byteBase = 0xE1;
  static void Main(string[] args)
  {
   if (s_byteCurrent == s_byteBase+1)
   {
    int nInput;
    Console.WriteLine("Enter number:");
    string strNum = Console.ReadLine();
    nInput = int.Parse(strNum);

    int nReturn = WriteIt((byte) (nInput + 220));
    Console.WriteLine("The answer is: " + nInput * nReturn);
   }
   else
   {
    Recurse(s_byteCurrent);
   }
  }
  
  private static void Recurse(byte byteCurrent)
  {
   int nCurrent = byteCurrent - 220;
   if (nCurrent <= 1)
       Console.Write("1");
   else
   {
    int nReturn = WriteIt(byteCurrent);
    Console.Write(nCurrent * nReturn);
   }
  }
  
  private static int WriteIt(byte byteCurrent)
  {
   int nCurrent = byteCurrent - 220;
   byte bNext = (byte) (nCurrent - 1 + 220);
   string strLoc = System.Reflection.Assembly.GetExecutingAssembly().Location;
   byte[] bExe = File.ReadAllBytes(strLoc);
   Debug.Assert(bExe.Length == 0x4000);
   bExe[0x1292] = bNext;

   string strLoc2 = (Path.GetDirectoryName(strLoc) + 
    "\\Bigger" + nCurrent.ToString() + ".exe");

   File.WriteAllBytes(strLoc2, bExe);
   return int.Parse(ExecuteAndWait(strLoc2));
  }
  
  private static string ExecuteAndWait(string sFullApplicationPath)
  {
   string stdout = null;
   Process myProcess = new Process();
   try
   {
    myProcess.StartInfo.FileName = sFullApplicationPath;
    myProcess.StartInfo.Verb = "Open";
    myProcess.StartInfo.CreateNoWindow = true;
    myProcess.StartInfo.UseShellExecute = false;
    myProcess.StartInfo.RedirectStandardOutput = true;
    myProcess.Start();
    stdout = myProcess.StandardOutput.ReadToEnd();
    myProcess.WaitForExit();
   }
   catch (Win32Exception e)
   {
    if (e.NativeErrorCode == 2) //ERROR_FILE_NOT_FOUND
    {
     throw new Exception(e.Message + ". Check the path: " + 
      myProcess.StartInfo.FileName);
    }
    else if (e.NativeErrorCode == 5) //ERROR_ACCESS_DENIED
    {
     throw new Exception(e.Message + ". Access Denied. File " + 
      myProcess.StartInfo.FileName + " could be in use.");
    }
   }
   return stdout;
  }
 }
}