Advertisement
NPSF3000

UnityBFI V2

Aug 31st, 2011
393
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 16.65 KB | None | 0 0
  1. /* NPSF3001 THIS_IS_GARBAGE GMAIL.COM
  2.  *
  3.  * Objective:  A simple BF interpreter in Unity to demonstrate skill.
  4.  *  
  5.  * Discussion
  6.  *
  7.  * This is V2:
  8.  *
  9.  * * Fixed: Does not function in webplayer!  (Now using COROUTINE preprocessor)
  10.  * * Partial Fix:  UI could do with a couple extra functions for usability. (Now Input TextField responds to Enter key).
  11.  *
  12.  * This is V1:
  13.  * *  Functional and Preliminary tests report working.
  14.  * *  Need more testing, maybe some minor clean up.
  15.  * *  UI could do with a couple extra functions for usability.  
  16.  * *  BUG:  Output TextArea only shows first ~200 lines
  17.  * *  Crash report - Does not function in webplayer!  (Fixed with COROUTINE preprocessor)
  18.  *
  19.  * For Hire! (31/08/11)
  20.  * * http://forum.unity3d.com/threads/102566-**50-OFF**-C-Programmer-**ONE-WEEK-ONLY**
  21.  *
  22.  * 3rd party programs available at:
  23.  * *  http://esoteric.sange.fi/brainfuck/bf-source/
  24.  *
  25.  * TOS
  26.  * *  Attribution required.
  27.  * *  Free for commercial and non-commercial use.
  28.  * *  *AS IS* - No warrenty or garuntee of this program is provided.  
  29.  */
  30.  
  31.  
  32. //To work around the webplayer crashes I had to reimplement the threading as a coroutine.
  33. #if UNITY_WEBPLAYER
  34. #define COROUTINE
  35. #endif
  36.  
  37.  
  38. using UnityEngine;
  39. using System.Collections.Generic;
  40. using System;
  41. using System.Threading;
  42. using System.Text;
  43.  
  44.  
  45. /// <summary>
  46. /// Attach this a empty GO in the scene to have a Full Screen BF GUI.
  47. /// </summary>
  48. public class BF_GUI : MonoBehaviour
  49. {
  50.     #region Variables
  51.     /// <summary>
  52.     /// The program displayed in GUI and passed to interpreter.  Can contain non-BF characters (considered comments).
  53.     /// </summary>
  54.     string dirtyProgram;
  55.  
  56.     /// <summary>
  57.     /// The store for output.
  58.     /// </summary>
  59.     List<string> output = new List<string>();
  60.  
  61.     /// <summary>
  62.     /// For the input textfield.
  63.     /// </summary>
  64.     string input = "";
  65.  
  66.     /// <summary>
  67.     /// The TextArea scroll V2's.
  68.     /// </summary>
  69.     Vector2 programScroll, outputScroll = Vector2.zero;
  70.  
  71.     /// <summary>
  72.     /// Value in pixels that determines the Text-Area Sizes.
  73.     /// </summary>
  74.     float vSliderValue = 100f;
  75.  
  76.     /// <summary>
  77.     /// Input waiting to be consumed by the BFI.
  78.     /// </summary>
  79.     Queue<char> queuedInput = new Queue<char>();
  80.  
  81.     /// <summary>
  82.     /// The Brain Fuck Interpreter
  83.     /// </summary>
  84.     BF_Interpreter bfi;
  85.  
  86.     /// <summary>
  87.     /// An Important BF constant (Newline)
  88.     /// </summary>
  89.     const byte END_OF_LINE = 10;
  90.     #endregion
  91.  
  92.     // Use this for initialization
  93.     void Start()
  94.     {
  95.         //Version info
  96. #if COROUTINE
  97.         output.Add("VERSION 2 | COROUTINE --  ");
  98. #else
  99.         output.Add("VERSION 2 | THREADED --  ");
  100. #endif
  101.  
  102.         // Some helpful instructions
  103.         output.Add("This is the output area" + Environment.NewLine);
  104.  
  105.         //Lets load a sample BF program
  106.         var file = Resources.Load("99Bottles") as TextAsset;
  107.         if (file != null)
  108.         {
  109.             dirtyProgram = file.text;
  110.         }
  111.         else
  112.         {
  113.             dirtyProgram = "Default Program Not Found!  From wiki: " + Environment.NewLine + Environment.NewLine +
  114.                 " ++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>.";
  115.         }
  116.  
  117.     }
  118.  
  119.     void OnGUI()
  120.     {
  121.         //Start layout, use the entire Screen.
  122.         GUILayout.BeginArea(new Rect(0, 0, Screen.width, Screen.height));
  123.         {
  124.  
  125.             //  Everything Above Input
  126.             GUILayout.BeginHorizontal();
  127.             {
  128.                 // The program and output textboxes
  129.                 GUILayout.BeginVertical();
  130.                 {
  131.  
  132.                     //Program Textbox
  133.                     GUILayout.BeginHorizontal(GUILayout.MinHeight(20), GUILayout.Height(vSliderValue));
  134.                     {
  135.                         programScroll = GUILayout.BeginScrollView(programScroll);
  136.                         dirtyProgram = GUILayout.TextArea(dirtyProgram, GUILayout.ExpandHeight(true));
  137.                         GUILayout.EndScrollView();
  138.                     }
  139.                     GUILayout.EndHorizontal();
  140.  
  141.                     GUILayout.Space(3);
  142.                     //Output Textbox
  143.                     GUILayout.BeginHorizontal(GUILayout.ExpandHeight(true));
  144.                     {
  145.                         outputScroll = GUILayout.BeginScrollView(outputScroll);
  146.  
  147.                         string outputText;
  148.                         string[] outputArray;
  149.                         lock (output)
  150.                         {
  151.                             outputArray = output.ToArray();
  152.                         }
  153.                         outputText = string.Join("", outputArray);
  154.  
  155.                         //var lineArray = outputText.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
  156.                         //Array.Reverse(lineArray);
  157.                         //outputText = string.Join(Environment.NewLine, lineArray);
  158.  
  159.                         GUILayout.TextArea(outputText, int.MaxValue, GUILayout.ExpandHeight(true));
  160.                         GUILayout.EndScrollView();
  161.                     }
  162.                     GUILayout.EndHorizontal();
  163.  
  164.                 } GUILayout.EndVertical();
  165.  
  166.                 //The size slider - to change the program : output size ratio
  167.                 GUILayout.BeginVertical(GUILayout.Width(10));
  168.                 {
  169.                     GUILayout.Space(30);
  170.                     vSliderValue = GUILayout.VerticalSlider(vSliderValue, 30f, Screen.height - 60);
  171.                     GUILayout.Space(30);
  172.                 } GUILayout.EndVertical();
  173.  
  174.             } GUILayout.EndHorizontal();
  175.  
  176.             //The Input Fields
  177.             GUILayout.BeginHorizontal();
  178.             GUI.SetNextControlName("InputTF");
  179.             input = GUILayout.TextField(input, GUILayout.MinHeight(20), GUILayout.MaxHeight(20), GUILayout.Width(Screen.width * 0.6f));
  180.  
  181.             //Handle enter key in textfield
  182.             if (((Event.current.keyCode == KeyCode.Return) && GUI.GetNameOfFocusedControl() == "InputTF") && !string.IsNullOrEmpty(input))
  183.             {
  184.                 foreach (char c in input.ToCharArray())
  185.                     queuedInput.Enqueue(c);
  186.  
  187.                 output.Add(Environment.NewLine + input + "1" + Environment.NewLine);
  188.                 input = "";
  189.                 Event.current.Use();
  190.             }
  191.  
  192.             //Enter (input) button
  193.             if (GUILayout.Button("Enter!"))
  194.             {
  195.                 foreach (char c in input.ToCharArray())
  196.                     queuedInput.Enqueue(c);
  197.  
  198.                 output.Add(Environment.NewLine + input + Environment.NewLine);
  199.  
  200.                 input = "";
  201.             }
  202.  
  203.             //Starts a new program
  204.             if (GUILayout.Button("Start Program!"))
  205.             {
  206.                 //clear input
  207.                 queuedInput.Clear();
  208.                 //clear output
  209.                 output.Clear();
  210.  
  211.  
  212.                 //destroy any existing bfi
  213.                 if (bfi != null) bfi.Destroy();
  214.  
  215.                 bfi = new BF_Interpreter(dirtyProgram, Input, Output);
  216.  
  217. #if COROUTINE
  218.                 StartCoroutine(bfi.Run());
  219. #else
  220.                 bfi.Run();
  221. #endif
  222.             }
  223.  
  224.             GUILayout.Space(20); //Just a margin
  225.             GUILayout.EndHorizontal();
  226.         }
  227.  
  228.         //End layout
  229.         GUILayout.EndArea();
  230.     }
  231.  
  232.     /// <summary>
  233.     /// Reads input for the interpreter
  234.     /// </summary>
  235.     byte? Input()
  236.     {
  237.         //No input just yet
  238.         if (queuedInput.Count == 0) return null;
  239.  
  240.         //Correctly encode input
  241.         var input = Encoding.ASCII.GetBytes((queuedInput.Dequeue()).ToString())[0];
  242.         if (input == 13) input = END_OF_LINE;
  243.  
  244.         //Return
  245.         return input;
  246.     }
  247.  
  248.     /// <summary>
  249.     /// Add output to the display
  250.     /// </summary>
  251.     void Output(byte x)
  252.     {
  253.         //This is called from the interpreter which runs in a seperate thread so we lock it.
  254.         lock (output)
  255.         {
  256.             if (x == END_OF_LINE)
  257.                 output.Add(Environment.NewLine);
  258.             else
  259.                 output.Add(Encoding.ASCII.GetString(new[] { x }));
  260.         }
  261.     }
  262.  
  263.     void OnDestroy()
  264.     {
  265.         //destroy any existing bfi
  266.         if (bfi != null) bfi.Destroy();
  267.     }
  268.  
  269. }
  270.  
  271. public class BF_Interpreter
  272. {
  273.     const byte END_OF_LINE = 10;
  274.  
  275.     # region private vars
  276.     /// <summary>
  277.     /// The Commands of BF in a easier to read format.
  278.     /// </summary>
  279.     enum COMMANDS : byte { Left, Right, Add, Subtract, Input, Output, LoopStart, LoopEnd };
  280.  
  281.     /// <summary>
  282.     /// The program
  283.     /// </summary>
  284.     COMMANDS[] program;
  285.  
  286.     /// <summary>
  287.     /// The Stack (Memory for the program)
  288.     /// </summary>
  289.     byte[] stack = new byte[ushort.MaxValue];
  290.  
  291.     /// <summary>
  292.     /// Loop look-up.  returns the position of the corrisponding loop beginning or end.
  293.     /// </summary>
  294.     ushort[] LLU;
  295.  
  296.     /// <summary>
  297.     /// Stack and Program pointers.
  298.     /// </summary>
  299.     ushort sPointer = 0, pPointer = 0;
  300.  
  301.     /// <summary>
  302.     /// Max instructions before interpretor quits.
  303.     /// </summary>
  304.     private uint instructionLimit = 1000000000;
  305.  
  306.     /// <summary>
  307.     /// A delegate that will return a byte as input for the program when called.
  308.     /// </summary>
  309.     Func<byte?> Input;
  310.  
  311.     /// <summary>
  312.     /// A delegate that will be called when the program wants to provide output.
  313.     /// </summary>
  314.     Action<byte> Output;
  315.     #endregion
  316.  
  317.     #region threading vars
  318.  
  319.     private bool _running = false;
  320.  
  321.     /// <summary>
  322.     /// Is the Interpreter currently stepping through a program?
  323.     /// </summary>
  324.     public bool isRunning { get { return _running; } }
  325.  
  326.     /// <summary>
  327.     /// Thread that runs the interpreter (used so as to be non-blocking).
  328.     /// </summary>
  329.     private Thread t;
  330.     #endregion
  331.  
  332.     /// <summary>
  333.     /// Creates a new BF interprator.
  334.     /// </summary>
  335.     /// <param name="dirtyProgram">  The string containing the BF program (can have comments).</param>
  336.     /// <param name="Input">A delegate that will return a byte as input for the program when called.</param>
  337.     /// <param name="Output">A delegate that will be called when the program wants to provide output. </param>
  338.     public BF_Interpreter(string dirtyProgram, Func<byte?> Input, Action<byte> Output)
  339.     {
  340.         Debug.Log("BFI Created");
  341.         //Parse the plain text into (strict) valid BF
  342.         program = CleanProgram(dirtyProgram);
  343.  
  344.         //This interprator can only handle programs smaller than ushort.max length
  345.         if (program.Length >= short.MaxValue) throw new Exception("BF Program was too long");
  346.  
  347.         //Build the 'loop look up' table
  348.         LLU = ParseLoops(program);
  349.  
  350.         //Assign delegates
  351.         this.Input = Input;
  352.         this.Output = Output;
  353.  
  354.  
  355.     }
  356.  
  357. #if !COROUTINE
  358.     /// <summary>
  359.     /// Steps through the program in a seperate thread, counting each instruction and not exceeding limit to prevent infinite loops.
  360.     /// </summary>
  361.     /// <param name="instructionLimit">  The maximum number of instructions to process.</param>
  362.     /// <returns>See IsRunning - True if the program has completed, false otherwise</returns>
  363.     public void Run()
  364.     {
  365.         Debug.Log("BFI ThreadStart");
  366.         if (isRunning) throw new Exception("Can't start if isRunning == true");
  367.  
  368.         t = new Thread(ThreadedRun);
  369.         t.Start();
  370.         Debug.Log("BFI ThreadStarted");
  371.     }
  372. #endif
  373.  
  374.  
  375. #if COROUTINE
  376.     public System.Collections.IEnumerator Run()
  377. #else
  378.     private void ThreadedRun()
  379. #endif
  380.     {
  381.  
  382.         _running = true;
  383.         Debug.Log("BFI Thread running");
  384.         //Sending output :)
  385.         foreach (char c in (char)END_OF_LINE + "Program Started" + (char)END_OF_LINE + (char)END_OF_LINE)
  386.         {
  387.             Output(Encoding.ASCII.GetBytes(c.ToString())[0]);
  388.         }
  389.  
  390.         //While the current instruction is in the program
  391.         while (true)
  392.         {
  393.             //Check to ensure current instruction is in program.
  394.             if (pPointer >= program.Length)
  395.             {
  396.                 foreach (char c in (char)END_OF_LINE + "Program Ended" + (char)END_OF_LINE)
  397.                 {
  398.                     Output(Encoding.ASCII.GetBytes(c.ToString())[0]);
  399.                 }
  400.                 break;
  401.             }
  402.             //Decrement count, exit early if we've exceeded out limit.
  403.             if (instructionLimit < 1)
  404.             {
  405.                 foreach (char c in "Program hit instruction limit")
  406.                 {
  407.                     Output(Encoding.ASCII.GetBytes(c.ToString())[0]);
  408.                 }
  409.                 break;
  410.             }
  411.  
  412.             instructionLimit--;
  413.  
  414.             // http://en.wikipedia.org/wiki/Brainfuck#Commands
  415.             switch (program[pPointer])
  416.             {
  417.                 case COMMANDS.Right: sPointer++; break;
  418.                 case COMMANDS.Left: sPointer--; break;
  419.                 case COMMANDS.Add: stack[sPointer]++; break;
  420.                 case COMMANDS.Subtract: stack[sPointer]--; break;
  421.                 case COMMANDS.Output: Output(stack[sPointer]); break;
  422.                 case COMMANDS.Input:
  423.                     //Wait for input - threaded and nonthreaded.
  424.                     while (true)
  425.                     {
  426.                         byte? input = Input();
  427.                         if (!input.HasValue)
  428. #if COROUTINE  
  429.                     yield return null;
  430. #else
  431.                             Thread.Sleep(100);
  432. #endif
  433.                         else
  434.                         {
  435.                             stack[sPointer] = input.Value;
  436.                             break;
  437.                         }
  438.                     } break;
  439.                 case COMMANDS.LoopStart:
  440.                     if (stack[sPointer] == 0) pPointer = LLU[pPointer]; break;
  441.                 case COMMANDS.LoopEnd:
  442.                     if (stack[sPointer] != 0) pPointer = LLU[pPointer]; break;
  443.             }
  444.             pPointer++;  //Move to next instruction
  445.         }
  446.  
  447.         _running = false;
  448.  
  449.         Debug.Log("BFI Thread finished");
  450. #if !COROUTINE
  451.         Thread.CurrentThread.Abort();
  452. #else
  453.  
  454. #endif
  455.     }
  456.  
  457.     public void Destroy()
  458.     {
  459.         //Pretty self explanatory - mainly needed to clean up thread.
  460.         //if (t != null) t.Abort();
  461.         t = null;
  462.         stack = null;
  463.         LLU = null;
  464.         program = null;
  465.     }
  466.  
  467.     # region static  functions
  468.     /// <summary>
  469.     /// Goes through a string and returns a strict BF program
  470.     /// </summary>
  471.     static COMMANDS[] CleanProgram(string dirtyProgram)
  472.     {
  473.         List<COMMANDS> program = new List<COMMANDS>(ushort.MaxValue);
  474.  
  475.         //For each char -
  476.         /// If it is valid command add it to the result
  477.         /// Else ignore it
  478.         foreach (char command in dirtyProgram)
  479.         {
  480.             switch (command)
  481.             {
  482.                 case '>': program.Add(COMMANDS.Right); break;
  483.                 case '<': program.Add(COMMANDS.Left); break;
  484.                 case '+': program.Add(COMMANDS.Add); break;
  485.                 case '-': program.Add(COMMANDS.Subtract); break;
  486.                 case '.': program.Add(COMMANDS.Output); break;
  487.                 case ',': program.Add(COMMANDS.Input); break;
  488.                 case '[': program.Add(COMMANDS.LoopStart); break;
  489.                 case ']': program.Add(COMMANDS.LoopEnd); break;
  490.                 //default: break;  //Ignore invalid chartacters
  491.             }
  492.         }
  493.         return program.ToArray();
  494.     }
  495.  
  496.     static ushort[] ParseLoops(COMMANDS[] program)
  497.     {
  498.         //The result (a simple lookup table)
  499.         var result = new ushort[program.Length];
  500.  
  501.         //For each command
  502.         for (ushort i = 0; i < program.Length; i++)
  503.         {
  504.             COMMANDS command = program[i];
  505.  
  506.             //If it is not a loop start skip it
  507.             if (command != COMMANDS.LoopStart) continue;
  508.  
  509.  
  510.             //Other wise find the loop end
  511.             ushort searchPos = i;
  512.             byte depth = 1;
  513.  
  514.             while (depth != 0)
  515.             {
  516.                 // Skip to matching ']'
  517.                 searchPos++;
  518.  
  519.                 COMMANDS searchCommand = program[searchPos];
  520.  
  521.                 switch (searchCommand)
  522.                 {
  523.                     case COMMANDS.LoopStart: depth++; break;
  524.                     case COMMANDS.LoopEnd: depth--; break;
  525.                 }
  526.             }
  527.             //Save the position of the loop end in the table @ loop start position.
  528.             result[i] = searchPos;
  529.             //And vice versa.
  530.             result[searchPos] = i;
  531.         }
  532.         return result;
  533.     }
  534.     #endregion
  535. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement