Skip to content

Commit

Permalink
Complete implementation for Whitespace
Browse files Browse the repository at this point in the history
  • Loading branch information
Timwi committed Sep 8, 2016
1 parent 8cc8228 commit caa4297
Show file tree
Hide file tree
Showing 12 changed files with 543 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ In chronological order of implementation in Esoteric IDE:
* **[Hexagony](http://esolangs.org/wiki/Hexagony)** — Program instructions and memory locations are laid out in a 2D hexagonal grid.
* **[Labyrinth](http://esolangs.org/wiki/Labyrinth)** — Two-dimensional stack-based language where the code can self-modify by applying cycling rotations of rows or columns of characters.
* **[Stack Cats](http://esolangs.org/wiki/Stack_Cats)** — Reversible programming language in which every program must be a mirror image of itself.
* **[Whitespace](http://esolangs.org/wiki/Whitespace)** — Only space, tab and newline are significant.

## How to compile

Expand Down
10 changes: 10 additions & 0 deletions Src/EsotericIDE.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,16 @@
<Compile Include="Sclipting\Util.cs" />
<Compile Include="StackCats\Settings.cs" />
<Compile Include="StackCats\StackCats.cs" />
<Compile Include="Whitespace\NumberInputSemantics.cs" />
<Compile Include="Whitespace\CharacterSemantics.cs" />
<Compile Include="Whitespace\Instruction.cs" />
<Compile Include="Whitespace\ArgKind.cs" />
<Compile Include="Whitespace\Environment.cs" />
<Compile Include="Whitespace\InstructionAttribute.cs" />
<Compile Include="Whitespace\Node.cs" />
<Compile Include="Whitespace\ParseInfoException.cs" />
<Compile Include="Whitespace\Settings.cs" />
<Compile Include="Whitespace\Whitespace.cs" />
<Compile Include="Unreadable\Unreadable.cs" />
<Compile Include="Unreadable\Environment.cs" />
<Compile Include="Unreadable\Program.cs" />
Expand Down
8 changes: 8 additions & 0 deletions Src/Whitespace/ArgKind.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace EsotericIDE.Whitespace
{
enum ArgKind
{
Number,
Label
}
}
8 changes: 8 additions & 0 deletions Src/Whitespace/CharacterSemantics.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace EsotericIDE.Whitespace
{
enum CharacterSemantics
{
Bytewise,
Unicode
}
}
277 changes: 277 additions & 0 deletions Src/Whitespace/Environment.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,277 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using RT.Util;
using RT.Util.ExtensionMethods;

namespace EsotericIDE.Whitespace
{
sealed class WhitespaceEnv : ExecutionEnvironment
{
private Node[] _program;
private string _input;
private Stack<BigInteger> _stack;
private Dictionary<BigInteger, BigInteger> _heap;
private Stack<int> _callStack;

private NumberInputSemantics _numberInputSemantics;
private CharacterSemantics _outputSemantics;
private Regex _integerFinder = new Regex(@"-?\d+", RegexOptions.Compiled);

public WhitespaceEnv(string source, string input, NumberInputSemantics numInputSem, CharacterSemantics outputSem)
{
_input = input;
_stack = new Stack<BigInteger>();
_heap = new Dictionary<BigInteger, BigInteger>();
_callStack = new Stack<int>();
_numberInputSemantics = numInputSem;
_outputSemantics = outputSem;
_program = Parse(source);
// Clipboard.SetText(_program.JoinString(Environment.NewLine));
}

public static Node[] Parse(string source, int? throwAtIndex = null)
{
var index = 0;
while (index < source.Length && !" \t\n".Contains(source[index]))
index++;

var nodes = new List<Node>();

var instrs = EnumStrong.GetValues<Instruction>().Select(instr => instr.GetCustomAttribute<InstructionAttribute>().Apply(attr => new
{
Instruction = instr,
Str = attr.Instr,
Arg = attr.Arg
})).ToArray();

while (index < source.Length)
{
var startIndex = index;
var match = instrs
.Select(inf =>
{
var newIndex = index;
var instrIndex = 0;
while (newIndex < source.Length && instrIndex < inf.Str.Length)
{
if (" \t\n".Contains(source[newIndex]))
{
if (source[newIndex] == inf.Str[instrIndex])
instrIndex++;
else
break;
}
newIndex++;
}
if (instrIndex == inf.Str.Length)
return new
{
Instruction = inf.Instruction,
NewIndex = newIndex,
Arg = inf.Arg
};
return null;
})
.Where(inf => inf != null)
.FirstOrDefault();

if (match == null)
throw new CompileException($@"Instruction at index {index} not recognized.", index);

index = match.NewIndex;
string labelArg = null;
BigInteger? numberArg = null;
List<bool> argBits = null;

if (match.Arg != null)
{
while (index < source.Length && !" \t\n".Contains(source[index]))
index++;
var origIndex = index;

argBits = new List<bool>();
while (index < source.Length && source[index] != '\n')
{
if (source[index] == ' ')
argBits.Add(false);
else if (source[index] == '\t')
argBits.Add(true);
index++;
}
if (index == source.Length)
throw new CompileException("Unterminated number literal or label name.", origIndex);
index++;
if (match.Arg == ArgKind.Label)
{
if (argBits.Count >= 8)
{
var argIndex = 0;
var bytes = new List<byte>();
while (argIndex < argBits.Count - 8)
{
bytes.Add((byte) argBits.Skip(argIndex).Take(8).Aggregate(0, (prev, next) => (prev << 1) | (next ? 1 : 0)));
argIndex += 8;
}
bytes.Add((byte) argBits.Skip(argIndex).Aggregate(0, (prev, next) => (prev << 1) | (next ? 1 : 0)));
labelArg = bytes.ToArray().FromUtf8();
}
else
labelArg = argBits.Select(b => b ? '1' : '0').JoinString();
}
else
{
if (argBits.Count == 0)
throw new CompileException("Expected a number (need at least one space or tab before the terminating linefeed).", origIndex);
var bi = BigInteger.Zero;
for (int i = 1; i < argBits.Count; i++)
bi = (bi << 1) | (argBits[i] ? 1 : 0);
numberArg = argBits[0] ? -bi : bi;
}
}

if (throwAtIndex != null && startIndex <= throwAtIndex.Value && index > throwAtIndex.Value)
throw new ParseInfoException(match.Instruction, argBits, numberArg, labelArg);
nodes.Add(new Node(new Position(startIndex, index - startIndex), match.Instruction, numberArg, labelArg));

while (index < source.Length && !" \t\n".Contains(source[index]))
index++;
}

nodes.Add(new Node(new Position(source.Length, 0), Instruction.Exit));
return nodes.ToArray();
}

protected override IEnumerable<Position> GetProgram()
{
var i = 0;
while (i != -1)
{
yield return _program[i].Position;
i = execute(i);
}
}

private int execute(int nodeIx)
{
var node = _program[nodeIx];
switch (node.Instruction)
{
// Stack Manipulation
case Instruction.Push: _stack.Push(node.Arg.Value); break;
case Instruction.Dup: _stack.Push(_stack.Peek()); break;
case Instruction.Copy: _stack.Push(_stack.Skip((int) node.Arg.Value).First()); break;
case Instruction.Discard: _stack.Pop(); break;
case Instruction.Swap:
var a = _stack.Pop();
var b = _stack.Pop();
_stack.Push(a);
_stack.Push(b);
break;

case Instruction.Slide:
var top = _stack.Pop();
for (int i = (int) node.Arg.Value - 1; i >= 0; i--)
_stack.Pop();
_stack.Push(top);
break;

// Arithmetic
case Instruction.Add: _stack.Push(_stack.Pop() + _stack.Pop()); break;
case Instruction.Subtract: _stack.Push(-_stack.Pop() + _stack.Pop()); break;
case Instruction.Multiply: _stack.Push(_stack.Pop() * _stack.Pop()); break;
case Instruction.Div: _stack.Pop().Apply(divisor => { _stack.Push(_stack.Pop() / divisor); }); break;
case Instruction.Modulo: _stack.Pop().Apply(divisor => { _stack.Push(_stack.Pop() % divisor); }); break;

// Heap Access
case Instruction.Store: _stack.Pop().Apply(value => { _heap[_stack.Pop()] = value; }); break;
case Instruction.Retrieve: _stack.Push(_heap.Get(_stack.Pop(), BigInteger.Zero)); break;

// Flow Control
case Instruction.Mark: /* does nothing */ break;
case Instruction.Call: _callStack.Push(nodeIx); return findLabel(node.Label);
case Instruction.Jump: return findLabel(node.Label);
case Instruction.Return: return _callStack.Pop() + 1;
case Instruction.Exit: return -1;

case Instruction.JumpIfZero:
if (_stack.Pop().IsZero)
return findLabel(node.Label);
break;

case Instruction.JumpIfNeg:
if (_stack.Pop() < 0)
return findLabel(node.Label);
break;

// I/O
case Instruction.OutputNumber: _output.Append(_stack.Pop().ToString()); break;

case Instruction.OutputChar:
var valToOutput = _stack.Pop();
if (valToOutput < 0)
throw new Exception("Cannot output a negative value as a character.");
if (_outputSemantics == CharacterSemantics.Bytewise)
_output.Append((char) (int) (valToOutput % 256));
else
_output.Append(char.ConvertFromUtf32((int) valToOutput));
break;

case Instruction.ReadChar:
if (_input.Length == 0)
throw new Exception("Attempt to read past the end of the input.");
_heap[_stack.Pop()] = _input[0];
_input = _input.Substring(1);
break;

case Instruction.ReadNumber:
BigInteger inputNumber;
if (_numberInputSemantics == NumberInputSemantics.Minimal)
{
var m = _integerFinder.Match(_input);
if (!m.Success)
throw new Exception("Input is not a valid number.");
inputNumber = BigInteger.Parse(m.Value);
_input = _input.Substring(m.Index + m.Length);
}
else
{
var pos = _input.IndexOf('\n');
var line = pos == -1 ? _input : _input.Substring(0, pos);

if (!BigInteger.TryParse(line, out inputNumber))
{
if (_numberInputSemantics == NumberInputSemantics.LinewiseLenient)
inputNumber = BigInteger.Zero;
else
throw new Exception("Input is not a valid number.");
}
_input = _input.Substring(pos + 1);
}
_heap[_stack.Pop()] = inputNumber;
break;

default:
throw new Exception("Unrecognized instruction during execution. This indicates a bug in the parser.");
}

return nodeIx + 1;
}

private int findLabel(string label)
{
for (int i = 0; i < _program.Length; i++)
if (_program[i].Instruction == Instruction.Mark && _program[i].Label == label)
return i;
throw new Exception("Label not found.");
}

public override void UpdateWatch()
{
_txtWatch.Text = $"Remaining input: {_input}\r\n\r\nStack:\r\n{_stack.JoinString("\r\n")}\r\n\r\nHeap:\r\n{_heap.Select(kvp => $"{kvp.Key}{kvp.Value}").JoinString("\r\n")}";
}
}
}
63 changes: 63 additions & 0 deletions Src/Whitespace/Instruction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
namespace EsotericIDE.Whitespace
{
enum Instruction
{
// Stack Manipulation
[Instruction("Push the number {0} onto the stack", " ", ArgKind.Number)]
Push,
[Instruction("Duplicate the top item on the stack.", " \n ")]
Dup,
[Instruction("Copy the {0}{1} item on the stack onto the top of the stack.", " \t ", ArgKind.Number)]
Copy,
[Instruction("Swap the top two items on the stack.", " \n\t")]
Swap,
[Instruction("Discard the top item on the stack.", " \n\n")]
Discard,
[Instruction("Slide {0} items off the stack, keeping the top item.", " \t\n", ArgKind.Number)]
Slide,

// Arithmetic
[Instruction("Addition.", "\t ")]
Add,
[Instruction("Subtraction.", "\t \t")]
Subtract,
[Instruction("Multiplication.", "\t \n")]
Multiply,
[Instruction("Integer division.", "\t \t ")]
Div,
[Instruction("Modulo.", "\t \t\t")]
Modulo,

// Heap Access
[Instruction("Store a value on the heap.", "\t\t ")]
Store,
[Instruction("Retrieve a value from the heap.", "\t\t\t")]
Retrieve,

// Flow Control
[Instruction("Label {2}.", "\n ", ArgKind.Label)]
Mark,
[Instruction("Call subroutine at label {2}.", "\n \t", ArgKind.Label)]
Call,
[Instruction("Jump unconditionally to label {2}.", "\n \n", ArgKind.Label)]
Jump,
[Instruction("Jump to label {2} if top of stack is zero.", "\n\t ", ArgKind.Label)]
JumpIfZero,
[Instruction("Jump to label {2} if the top of the stack is negative.", "\n\t\t", ArgKind.Label)]
JumpIfNeg,
[Instruction("End a subroutine and transfer control back to the caller.", "\n\t\n")]
Return,
[Instruction("End the program.", "\n\n\n")]
Exit,

// I/O
[Instruction("Output the character at the top of the stack.", "\t\n ")]
OutputChar,
[Instruction("Output the number at the top of the stack.", "\t\n \t")]
OutputNumber,
[Instruction("Read a character and place it in the location given by the top of the stack.", "\t\n\t ")]
ReadChar,
[Instruction("Read a number and place it in the location given by the top of the stack.", "\t\n\t\t")]
ReadNumber
}
}
14 changes: 14 additions & 0 deletions Src/Whitespace/InstructionAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System;

namespace EsotericIDE.Whitespace
{
[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = true)]
sealed class InstructionAttribute : Attribute
{
public InstructionAttribute(string explain, string instr) { Explain = explain; Instr = instr; Arg = null; }
public InstructionAttribute(string explain, string instr, ArgKind arg) { Explain = explain; Instr = instr; Arg = arg; }
public string Explain { get; private set; }
public string Instr { get; private set; }
public ArgKind? Arg { get; private set; }
}
}
Loading

0 comments on commit caa4297

Please sign in to comment.