Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /* NPSF3001 THIS_IS_GARBAGE GMAIL.COM
- *
- * Objective: A simple BF interpreter in Unity to demonstrate skill.
- *
- * Discussion
- *
- * This is V2:
- *
- * * Fixed: Does not function in webplayer! (Now using COROUTINE preprocessor)
- * * Partial Fix: UI could do with a couple extra functions for usability. (Now Input TextField responds to Enter key).
- *
- * This is V1:
- * * Functional and Preliminary tests report working.
- * * Need more testing, maybe some minor clean up.
- * * UI could do with a couple extra functions for usability.
- * * BUG: Output TextArea only shows first ~200 lines
- * * Crash report - Does not function in webplayer! (Fixed with COROUTINE preprocessor)
- *
- * For Hire! (31/08/11)
- * * http://forum.unity3d.com/threads/102566-**50-OFF**-C-Programmer-**ONE-WEEK-ONLY**
- *
- * 3rd party programs available at:
- * * http://esoteric.sange.fi/brainfuck/bf-source/
- *
- * TOS
- * * Attribution required.
- * * Free for commercial and non-commercial use.
- * * *AS IS* - No warrenty or garuntee of this program is provided.
- */
- //To work around the webplayer crashes I had to reimplement the threading as a coroutine.
- #if UNITY_WEBPLAYER
- #define COROUTINE
- #endif
- using UnityEngine;
- using System.Collections.Generic;
- using System;
- using System.Threading;
- using System.Text;
- /// <summary>
- /// Attach this a empty GO in the scene to have a Full Screen BF GUI.
- /// </summary>
- public class BF_GUI : MonoBehaviour
- {
- #region Variables
- /// <summary>
- /// The program displayed in GUI and passed to interpreter. Can contain non-BF characters (considered comments).
- /// </summary>
- string dirtyProgram;
- /// <summary>
- /// The store for output.
- /// </summary>
- List<string> output = new List<string>();
- /// <summary>
- /// For the input textfield.
- /// </summary>
- string input = "";
- /// <summary>
- /// The TextArea scroll V2's.
- /// </summary>
- Vector2 programScroll, outputScroll = Vector2.zero;
- /// <summary>
- /// Value in pixels that determines the Text-Area Sizes.
- /// </summary>
- float vSliderValue = 100f;
- /// <summary>
- /// Input waiting to be consumed by the BFI.
- /// </summary>
- Queue<char> queuedInput = new Queue<char>();
- /// <summary>
- /// The Brain Fuck Interpreter
- /// </summary>
- BF_Interpreter bfi;
- /// <summary>
- /// An Important BF constant (Newline)
- /// </summary>
- const byte END_OF_LINE = 10;
- #endregion
- // Use this for initialization
- void Start()
- {
- //Version info
- #if COROUTINE
- output.Add("VERSION 2 | COROUTINE -- ");
- #else
- output.Add("VERSION 2 | THREADED -- ");
- #endif
- // Some helpful instructions
- output.Add("This is the output area" + Environment.NewLine);
- //Lets load a sample BF program
- var file = Resources.Load("99Bottles") as TextAsset;
- if (file != null)
- {
- dirtyProgram = file.text;
- }
- else
- {
- dirtyProgram = "Default Program Not Found! From wiki: " + Environment.NewLine + Environment.NewLine +
- " ++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>.";
- }
- }
- void OnGUI()
- {
- //Start layout, use the entire Screen.
- GUILayout.BeginArea(new Rect(0, 0, Screen.width, Screen.height));
- {
- // Everything Above Input
- GUILayout.BeginHorizontal();
- {
- // The program and output textboxes
- GUILayout.BeginVertical();
- {
- //Program Textbox
- GUILayout.BeginHorizontal(GUILayout.MinHeight(20), GUILayout.Height(vSliderValue));
- {
- programScroll = GUILayout.BeginScrollView(programScroll);
- dirtyProgram = GUILayout.TextArea(dirtyProgram, GUILayout.ExpandHeight(true));
- GUILayout.EndScrollView();
- }
- GUILayout.EndHorizontal();
- GUILayout.Space(3);
- //Output Textbox
- GUILayout.BeginHorizontal(GUILayout.ExpandHeight(true));
- {
- outputScroll = GUILayout.BeginScrollView(outputScroll);
- string outputText;
- string[] outputArray;
- lock (output)
- {
- outputArray = output.ToArray();
- }
- outputText = string.Join("", outputArray);
- //var lineArray = outputText.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
- //Array.Reverse(lineArray);
- //outputText = string.Join(Environment.NewLine, lineArray);
- GUILayout.TextArea(outputText, int.MaxValue, GUILayout.ExpandHeight(true));
- GUILayout.EndScrollView();
- }
- GUILayout.EndHorizontal();
- } GUILayout.EndVertical();
- //The size slider - to change the program : output size ratio
- GUILayout.BeginVertical(GUILayout.Width(10));
- {
- GUILayout.Space(30);
- vSliderValue = GUILayout.VerticalSlider(vSliderValue, 30f, Screen.height - 60);
- GUILayout.Space(30);
- } GUILayout.EndVertical();
- } GUILayout.EndHorizontal();
- //The Input Fields
- GUILayout.BeginHorizontal();
- GUI.SetNextControlName("InputTF");
- input = GUILayout.TextField(input, GUILayout.MinHeight(20), GUILayout.MaxHeight(20), GUILayout.Width(Screen.width * 0.6f));
- //Handle enter key in textfield
- if (((Event.current.keyCode == KeyCode.Return) && GUI.GetNameOfFocusedControl() == "InputTF") && !string.IsNullOrEmpty(input))
- {
- foreach (char c in input.ToCharArray())
- queuedInput.Enqueue(c);
- output.Add(Environment.NewLine + input + "1" + Environment.NewLine);
- input = "";
- Event.current.Use();
- }
- //Enter (input) button
- if (GUILayout.Button("Enter!"))
- {
- foreach (char c in input.ToCharArray())
- queuedInput.Enqueue(c);
- output.Add(Environment.NewLine + input + Environment.NewLine);
- input = "";
- }
- //Starts a new program
- if (GUILayout.Button("Start Program!"))
- {
- //clear input
- queuedInput.Clear();
- //clear output
- output.Clear();
- //destroy any existing bfi
- if (bfi != null) bfi.Destroy();
- bfi = new BF_Interpreter(dirtyProgram, Input, Output);
- #if COROUTINE
- StartCoroutine(bfi.Run());
- #else
- bfi.Run();
- #endif
- }
- GUILayout.Space(20); //Just a margin
- GUILayout.EndHorizontal();
- }
- //End layout
- GUILayout.EndArea();
- }
- /// <summary>
- /// Reads input for the interpreter
- /// </summary>
- byte? Input()
- {
- //No input just yet
- if (queuedInput.Count == 0) return null;
- //Correctly encode input
- var input = Encoding.ASCII.GetBytes((queuedInput.Dequeue()).ToString())[0];
- if (input == 13) input = END_OF_LINE;
- //Return
- return input;
- }
- /// <summary>
- /// Add output to the display
- /// </summary>
- void Output(byte x)
- {
- //This is called from the interpreter which runs in a seperate thread so we lock it.
- lock (output)
- {
- if (x == END_OF_LINE)
- output.Add(Environment.NewLine);
- else
- output.Add(Encoding.ASCII.GetString(new[] { x }));
- }
- }
- void OnDestroy()
- {
- //destroy any existing bfi
- if (bfi != null) bfi.Destroy();
- }
- }
- public class BF_Interpreter
- {
- const byte END_OF_LINE = 10;
- # region private vars
- /// <summary>
- /// The Commands of BF in a easier to read format.
- /// </summary>
- enum COMMANDS : byte { Left, Right, Add, Subtract, Input, Output, LoopStart, LoopEnd };
- /// <summary>
- /// The program
- /// </summary>
- COMMANDS[] program;
- /// <summary>
- /// The Stack (Memory for the program)
- /// </summary>
- byte[] stack = new byte[ushort.MaxValue];
- /// <summary>
- /// Loop look-up. returns the position of the corrisponding loop beginning or end.
- /// </summary>
- ushort[] LLU;
- /// <summary>
- /// Stack and Program pointers.
- /// </summary>
- ushort sPointer = 0, pPointer = 0;
- /// <summary>
- /// Max instructions before interpretor quits.
- /// </summary>
- private uint instructionLimit = 1000000000;
- /// <summary>
- /// A delegate that will return a byte as input for the program when called.
- /// </summary>
- Func<byte?> Input;
- /// <summary>
- /// A delegate that will be called when the program wants to provide output.
- /// </summary>
- Action<byte> Output;
- #endregion
- #region threading vars
- private bool _running = false;
- /// <summary>
- /// Is the Interpreter currently stepping through a program?
- /// </summary>
- public bool isRunning { get { return _running; } }
- /// <summary>
- /// Thread that runs the interpreter (used so as to be non-blocking).
- /// </summary>
- private Thread t;
- #endregion
- /// <summary>
- /// Creates a new BF interprator.
- /// </summary>
- /// <param name="dirtyProgram"> The string containing the BF program (can have comments).</param>
- /// <param name="Input">A delegate that will return a byte as input for the program when called.</param>
- /// <param name="Output">A delegate that will be called when the program wants to provide output. </param>
- public BF_Interpreter(string dirtyProgram, Func<byte?> Input, Action<byte> Output)
- {
- Debug.Log("BFI Created");
- //Parse the plain text into (strict) valid BF
- program = CleanProgram(dirtyProgram);
- //This interprator can only handle programs smaller than ushort.max length
- if (program.Length >= short.MaxValue) throw new Exception("BF Program was too long");
- //Build the 'loop look up' table
- LLU = ParseLoops(program);
- //Assign delegates
- this.Input = Input;
- this.Output = Output;
- }
- #if !COROUTINE
- /// <summary>
- /// Steps through the program in a seperate thread, counting each instruction and not exceeding limit to prevent infinite loops.
- /// </summary>
- /// <param name="instructionLimit"> The maximum number of instructions to process.</param>
- /// <returns>See IsRunning - True if the program has completed, false otherwise</returns>
- public void Run()
- {
- Debug.Log("BFI ThreadStart");
- if (isRunning) throw new Exception("Can't start if isRunning == true");
- t = new Thread(ThreadedRun);
- t.Start();
- Debug.Log("BFI ThreadStarted");
- }
- #endif
- #if COROUTINE
- public System.Collections.IEnumerator Run()
- #else
- private void ThreadedRun()
- #endif
- {
- _running = true;
- Debug.Log("BFI Thread running");
- //Sending output :)
- foreach (char c in (char)END_OF_LINE + "Program Started" + (char)END_OF_LINE + (char)END_OF_LINE)
- {
- Output(Encoding.ASCII.GetBytes(c.ToString())[0]);
- }
- //While the current instruction is in the program
- while (true)
- {
- //Check to ensure current instruction is in program.
- if (pPointer >= program.Length)
- {
- foreach (char c in (char)END_OF_LINE + "Program Ended" + (char)END_OF_LINE)
- {
- Output(Encoding.ASCII.GetBytes(c.ToString())[0]);
- }
- break;
- }
- //Decrement count, exit early if we've exceeded out limit.
- if (instructionLimit < 1)
- {
- foreach (char c in "Program hit instruction limit")
- {
- Output(Encoding.ASCII.GetBytes(c.ToString())[0]);
- }
- break;
- }
- instructionLimit--;
- // http://en.wikipedia.org/wiki/Brainfuck#Commands
- switch (program[pPointer])
- {
- case COMMANDS.Right: sPointer++; break;
- case COMMANDS.Left: sPointer--; break;
- case COMMANDS.Add: stack[sPointer]++; break;
- case COMMANDS.Subtract: stack[sPointer]--; break;
- case COMMANDS.Output: Output(stack[sPointer]); break;
- case COMMANDS.Input:
- //Wait for input - threaded and nonthreaded.
- while (true)
- {
- byte? input = Input();
- if (!input.HasValue)
- #if COROUTINE
- yield return null;
- #else
- Thread.Sleep(100);
- #endif
- else
- {
- stack[sPointer] = input.Value;
- break;
- }
- } break;
- case COMMANDS.LoopStart:
- if (stack[sPointer] == 0) pPointer = LLU[pPointer]; break;
- case COMMANDS.LoopEnd:
- if (stack[sPointer] != 0) pPointer = LLU[pPointer]; break;
- }
- pPointer++; //Move to next instruction
- }
- _running = false;
- Debug.Log("BFI Thread finished");
- #if !COROUTINE
- Thread.CurrentThread.Abort();
- #else
- #endif
- }
- public void Destroy()
- {
- //Pretty self explanatory - mainly needed to clean up thread.
- //if (t != null) t.Abort();
- t = null;
- stack = null;
- LLU = null;
- program = null;
- }
- # region static functions
- /// <summary>
- /// Goes through a string and returns a strict BF program
- /// </summary>
- static COMMANDS[] CleanProgram(string dirtyProgram)
- {
- List<COMMANDS> program = new List<COMMANDS>(ushort.MaxValue);
- //For each char -
- /// If it is valid command add it to the result
- /// Else ignore it
- foreach (char command in dirtyProgram)
- {
- switch (command)
- {
- case '>': program.Add(COMMANDS.Right); break;
- case '<': program.Add(COMMANDS.Left); break;
- case '+': program.Add(COMMANDS.Add); break;
- case '-': program.Add(COMMANDS.Subtract); break;
- case '.': program.Add(COMMANDS.Output); break;
- case ',': program.Add(COMMANDS.Input); break;
- case '[': program.Add(COMMANDS.LoopStart); break;
- case ']': program.Add(COMMANDS.LoopEnd); break;
- //default: break; //Ignore invalid chartacters
- }
- }
- return program.ToArray();
- }
- static ushort[] ParseLoops(COMMANDS[] program)
- {
- //The result (a simple lookup table)
- var result = new ushort[program.Length];
- //For each command
- for (ushort i = 0; i < program.Length; i++)
- {
- COMMANDS command = program[i];
- //If it is not a loop start skip it
- if (command != COMMANDS.LoopStart) continue;
- //Other wise find the loop end
- ushort searchPos = i;
- byte depth = 1;
- while (depth != 0)
- {
- // Skip to matching ']'
- searchPos++;
- COMMANDS searchCommand = program[searchPos];
- switch (searchCommand)
- {
- case COMMANDS.LoopStart: depth++; break;
- case COMMANDS.LoopEnd: depth--; break;
- }
- }
- //Save the position of the loop end in the table @ loop start position.
- result[i] = searchPos;
- //And vice versa.
- result[searchPos] = i;
- }
- return result;
- }
- #endregion
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement