Skip to content

Commit caa4297

Browse files
committed
Complete implementation for Whitespace
1 parent 8cc8228 commit caa4297

12 files changed

Lines changed: 543 additions & 0 deletions

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ In chronological order of implementation in Esoteric IDE:
2323
* **[Hexagony](http://esolangs.org/wiki/Hexagony)** — Program instructions and memory locations are laid out in a 2D hexagonal grid.
2424
* **[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.
2525
* **[Stack Cats](http://esolangs.org/wiki/Stack_Cats)** — Reversible programming language in which every program must be a mirror image of itself.
26+
* **[Whitespace](http://esolangs.org/wiki/Whitespace)** — Only space, tab and newline are significant.
2627

2728
## How to compile
2829

Src/EsotericIDE.csproj

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,16 @@
8383
<Compile Include="Sclipting\Util.cs" />
8484
<Compile Include="StackCats\Settings.cs" />
8585
<Compile Include="StackCats\StackCats.cs" />
86+
<Compile Include="Whitespace\NumberInputSemantics.cs" />
87+
<Compile Include="Whitespace\CharacterSemantics.cs" />
88+
<Compile Include="Whitespace\Instruction.cs" />
89+
<Compile Include="Whitespace\ArgKind.cs" />
90+
<Compile Include="Whitespace\Environment.cs" />
91+
<Compile Include="Whitespace\InstructionAttribute.cs" />
92+
<Compile Include="Whitespace\Node.cs" />
93+
<Compile Include="Whitespace\ParseInfoException.cs" />
94+
<Compile Include="Whitespace\Settings.cs" />
95+
<Compile Include="Whitespace\Whitespace.cs" />
8696
<Compile Include="Unreadable\Unreadable.cs" />
8797
<Compile Include="Unreadable\Environment.cs" />
8898
<Compile Include="Unreadable\Program.cs" />

Src/Whitespace/ArgKind.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace EsotericIDE.Whitespace
2+
{
3+
enum ArgKind
4+
{
5+
Number,
6+
Label
7+
}
8+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace EsotericIDE.Whitespace
2+
{
3+
enum CharacterSemantics
4+
{
5+
Bytewise,
6+
Unicode
7+
}
8+
}

Src/Whitespace/Environment.cs

Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Numerics;
5+
using System.Text.RegularExpressions;
6+
using System.Windows.Forms;
7+
using RT.Util;
8+
using RT.Util.ExtensionMethods;
9+
10+
namespace EsotericIDE.Whitespace
11+
{
12+
sealed class WhitespaceEnv : ExecutionEnvironment
13+
{
14+
private Node[] _program;
15+
private string _input;
16+
private Stack<BigInteger> _stack;
17+
private Dictionary<BigInteger, BigInteger> _heap;
18+
private Stack<int> _callStack;
19+
20+
private NumberInputSemantics _numberInputSemantics;
21+
private CharacterSemantics _outputSemantics;
22+
private Regex _integerFinder = new Regex(@"-?\d+", RegexOptions.Compiled);
23+
24+
public WhitespaceEnv(string source, string input, NumberInputSemantics numInputSem, CharacterSemantics outputSem)
25+
{
26+
_input = input;
27+
_stack = new Stack<BigInteger>();
28+
_heap = new Dictionary<BigInteger, BigInteger>();
29+
_callStack = new Stack<int>();
30+
_numberInputSemantics = numInputSem;
31+
_outputSemantics = outputSem;
32+
_program = Parse(source);
33+
// Clipboard.SetText(_program.JoinString(Environment.NewLine));
34+
}
35+
36+
public static Node[] Parse(string source, int? throwAtIndex = null)
37+
{
38+
var index = 0;
39+
while (index < source.Length && !" \t\n".Contains(source[index]))
40+
index++;
41+
42+
var nodes = new List<Node>();
43+
44+
var instrs = EnumStrong.GetValues<Instruction>().Select(instr => instr.GetCustomAttribute<InstructionAttribute>().Apply(attr => new
45+
{
46+
Instruction = instr,
47+
Str = attr.Instr,
48+
Arg = attr.Arg
49+
})).ToArray();
50+
51+
while (index < source.Length)
52+
{
53+
var startIndex = index;
54+
var match = instrs
55+
.Select(inf =>
56+
{
57+
var newIndex = index;
58+
var instrIndex = 0;
59+
while (newIndex < source.Length && instrIndex < inf.Str.Length)
60+
{
61+
if (" \t\n".Contains(source[newIndex]))
62+
{
63+
if (source[newIndex] == inf.Str[instrIndex])
64+
instrIndex++;
65+
else
66+
break;
67+
}
68+
newIndex++;
69+
}
70+
if (instrIndex == inf.Str.Length)
71+
return new
72+
{
73+
Instruction = inf.Instruction,
74+
NewIndex = newIndex,
75+
Arg = inf.Arg
76+
};
77+
return null;
78+
})
79+
.Where(inf => inf != null)
80+
.FirstOrDefault();
81+
82+
if (match == null)
83+
throw new CompileException($@"Instruction at index {index} not recognized.", index);
84+
85+
index = match.NewIndex;
86+
string labelArg = null;
87+
BigInteger? numberArg = null;
88+
List<bool> argBits = null;
89+
90+
if (match.Arg != null)
91+
{
92+
while (index < source.Length && !" \t\n".Contains(source[index]))
93+
index++;
94+
var origIndex = index;
95+
96+
argBits = new List<bool>();
97+
while (index < source.Length && source[index] != '\n')
98+
{
99+
if (source[index] == ' ')
100+
argBits.Add(false);
101+
else if (source[index] == '\t')
102+
argBits.Add(true);
103+
index++;
104+
}
105+
if (index == source.Length)
106+
throw new CompileException("Unterminated number literal or label name.", origIndex);
107+
index++;
108+
if (match.Arg == ArgKind.Label)
109+
{
110+
if (argBits.Count >= 8)
111+
{
112+
var argIndex = 0;
113+
var bytes = new List<byte>();
114+
while (argIndex < argBits.Count - 8)
115+
{
116+
bytes.Add((byte) argBits.Skip(argIndex).Take(8).Aggregate(0, (prev, next) => (prev << 1) | (next ? 1 : 0)));
117+
argIndex += 8;
118+
}
119+
bytes.Add((byte) argBits.Skip(argIndex).Aggregate(0, (prev, next) => (prev << 1) | (next ? 1 : 0)));
120+
labelArg = bytes.ToArray().FromUtf8();
121+
}
122+
else
123+
labelArg = argBits.Select(b => b ? '1' : '0').JoinString();
124+
}
125+
else
126+
{
127+
if (argBits.Count == 0)
128+
throw new CompileException("Expected a number (need at least one space or tab before the terminating linefeed).", origIndex);
129+
var bi = BigInteger.Zero;
130+
for (int i = 1; i < argBits.Count; i++)
131+
bi = (bi << 1) | (argBits[i] ? 1 : 0);
132+
numberArg = argBits[0] ? -bi : bi;
133+
}
134+
}
135+
136+
if (throwAtIndex != null && startIndex <= throwAtIndex.Value && index > throwAtIndex.Value)
137+
throw new ParseInfoException(match.Instruction, argBits, numberArg, labelArg);
138+
nodes.Add(new Node(new Position(startIndex, index - startIndex), match.Instruction, numberArg, labelArg));
139+
140+
while (index < source.Length && !" \t\n".Contains(source[index]))
141+
index++;
142+
}
143+
144+
nodes.Add(new Node(new Position(source.Length, 0), Instruction.Exit));
145+
return nodes.ToArray();
146+
}
147+
148+
protected override IEnumerable<Position> GetProgram()
149+
{
150+
var i = 0;
151+
while (i != -1)
152+
{
153+
yield return _program[i].Position;
154+
i = execute(i);
155+
}
156+
}
157+
158+
private int execute(int nodeIx)
159+
{
160+
var node = _program[nodeIx];
161+
switch (node.Instruction)
162+
{
163+
// Stack Manipulation
164+
case Instruction.Push: _stack.Push(node.Arg.Value); break;
165+
case Instruction.Dup: _stack.Push(_stack.Peek()); break;
166+
case Instruction.Copy: _stack.Push(_stack.Skip((int) node.Arg.Value).First()); break;
167+
case Instruction.Discard: _stack.Pop(); break;
168+
case Instruction.Swap:
169+
var a = _stack.Pop();
170+
var b = _stack.Pop();
171+
_stack.Push(a);
172+
_stack.Push(b);
173+
break;
174+
175+
case Instruction.Slide:
176+
var top = _stack.Pop();
177+
for (int i = (int) node.Arg.Value - 1; i >= 0; i--)
178+
_stack.Pop();
179+
_stack.Push(top);
180+
break;
181+
182+
// Arithmetic
183+
case Instruction.Add: _stack.Push(_stack.Pop() + _stack.Pop()); break;
184+
case Instruction.Subtract: _stack.Push(-_stack.Pop() + _stack.Pop()); break;
185+
case Instruction.Multiply: _stack.Push(_stack.Pop() * _stack.Pop()); break;
186+
case Instruction.Div: _stack.Pop().Apply(divisor => { _stack.Push(_stack.Pop() / divisor); }); break;
187+
case Instruction.Modulo: _stack.Pop().Apply(divisor => { _stack.Push(_stack.Pop() % divisor); }); break;
188+
189+
// Heap Access
190+
case Instruction.Store: _stack.Pop().Apply(value => { _heap[_stack.Pop()] = value; }); break;
191+
case Instruction.Retrieve: _stack.Push(_heap.Get(_stack.Pop(), BigInteger.Zero)); break;
192+
193+
// Flow Control
194+
case Instruction.Mark: /* does nothing */ break;
195+
case Instruction.Call: _callStack.Push(nodeIx); return findLabel(node.Label);
196+
case Instruction.Jump: return findLabel(node.Label);
197+
case Instruction.Return: return _callStack.Pop() + 1;
198+
case Instruction.Exit: return -1;
199+
200+
case Instruction.JumpIfZero:
201+
if (_stack.Pop().IsZero)
202+
return findLabel(node.Label);
203+
break;
204+
205+
case Instruction.JumpIfNeg:
206+
if (_stack.Pop() < 0)
207+
return findLabel(node.Label);
208+
break;
209+
210+
// I/O
211+
case Instruction.OutputNumber: _output.Append(_stack.Pop().ToString()); break;
212+
213+
case Instruction.OutputChar:
214+
var valToOutput = _stack.Pop();
215+
if (valToOutput < 0)
216+
throw new Exception("Cannot output a negative value as a character.");
217+
if (_outputSemantics == CharacterSemantics.Bytewise)
218+
_output.Append((char) (int) (valToOutput % 256));
219+
else
220+
_output.Append(char.ConvertFromUtf32((int) valToOutput));
221+
break;
222+
223+
case Instruction.ReadChar:
224+
if (_input.Length == 0)
225+
throw new Exception("Attempt to read past the end of the input.");
226+
_heap[_stack.Pop()] = _input[0];
227+
_input = _input.Substring(1);
228+
break;
229+
230+
case Instruction.ReadNumber:
231+
BigInteger inputNumber;
232+
if (_numberInputSemantics == NumberInputSemantics.Minimal)
233+
{
234+
var m = _integerFinder.Match(_input);
235+
if (!m.Success)
236+
throw new Exception("Input is not a valid number.");
237+
inputNumber = BigInteger.Parse(m.Value);
238+
_input = _input.Substring(m.Index + m.Length);
239+
}
240+
else
241+
{
242+
var pos = _input.IndexOf('\n');
243+
var line = pos == -1 ? _input : _input.Substring(0, pos);
244+
245+
if (!BigInteger.TryParse(line, out inputNumber))
246+
{
247+
if (_numberInputSemantics == NumberInputSemantics.LinewiseLenient)
248+
inputNumber = BigInteger.Zero;
249+
else
250+
throw new Exception("Input is not a valid number.");
251+
}
252+
_input = _input.Substring(pos + 1);
253+
}
254+
_heap[_stack.Pop()] = inputNumber;
255+
break;
256+
257+
default:
258+
throw new Exception("Unrecognized instruction during execution. This indicates a bug in the parser.");
259+
}
260+
261+
return nodeIx + 1;
262+
}
263+
264+
private int findLabel(string label)
265+
{
266+
for (int i = 0; i < _program.Length; i++)
267+
if (_program[i].Instruction == Instruction.Mark && _program[i].Label == label)
268+
return i;
269+
throw new Exception("Label not found.");
270+
}
271+
272+
public override void UpdateWatch()
273+
{
274+
_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")}";
275+
}
276+
}
277+
}

Src/Whitespace/Instruction.cs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
namespace EsotericIDE.Whitespace
2+
{
3+
enum Instruction
4+
{
5+
// Stack Manipulation
6+
[Instruction("Push the number {0} onto the stack", " ", ArgKind.Number)]
7+
Push,
8+
[Instruction("Duplicate the top item on the stack.", " \n ")]
9+
Dup,
10+
[Instruction("Copy the {0}{1} item on the stack onto the top of the stack.", " \t ", ArgKind.Number)]
11+
Copy,
12+
[Instruction("Swap the top two items on the stack.", " \n\t")]
13+
Swap,
14+
[Instruction("Discard the top item on the stack.", " \n\n")]
15+
Discard,
16+
[Instruction("Slide {0} items off the stack, keeping the top item.", " \t\n", ArgKind.Number)]
17+
Slide,
18+
19+
// Arithmetic
20+
[Instruction("Addition.", "\t ")]
21+
Add,
22+
[Instruction("Subtraction.", "\t \t")]
23+
Subtract,
24+
[Instruction("Multiplication.", "\t \n")]
25+
Multiply,
26+
[Instruction("Integer division.", "\t \t ")]
27+
Div,
28+
[Instruction("Modulo.", "\t \t\t")]
29+
Modulo,
30+
31+
// Heap Access
32+
[Instruction("Store a value on the heap.", "\t\t ")]
33+
Store,
34+
[Instruction("Retrieve a value from the heap.", "\t\t\t")]
35+
Retrieve,
36+
37+
// Flow Control
38+
[Instruction("Label {2}.", "\n ", ArgKind.Label)]
39+
Mark,
40+
[Instruction("Call subroutine at label {2}.", "\n \t", ArgKind.Label)]
41+
Call,
42+
[Instruction("Jump unconditionally to label {2}.", "\n \n", ArgKind.Label)]
43+
Jump,
44+
[Instruction("Jump to label {2} if top of stack is zero.", "\n\t ", ArgKind.Label)]
45+
JumpIfZero,
46+
[Instruction("Jump to label {2} if the top of the stack is negative.", "\n\t\t", ArgKind.Label)]
47+
JumpIfNeg,
48+
[Instruction("End a subroutine and transfer control back to the caller.", "\n\t\n")]
49+
Return,
50+
[Instruction("End the program.", "\n\n\n")]
51+
Exit,
52+
53+
// I/O
54+
[Instruction("Output the character at the top of the stack.", "\t\n ")]
55+
OutputChar,
56+
[Instruction("Output the number at the top of the stack.", "\t\n \t")]
57+
OutputNumber,
58+
[Instruction("Read a character and place it in the location given by the top of the stack.", "\t\n\t ")]
59+
ReadChar,
60+
[Instruction("Read a number and place it in the location given by the top of the stack.", "\t\n\t\t")]
61+
ReadNumber
62+
}
63+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using System;
2+
3+
namespace EsotericIDE.Whitespace
4+
{
5+
[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = true)]
6+
sealed class InstructionAttribute : Attribute
7+
{
8+
public InstructionAttribute(string explain, string instr) { Explain = explain; Instr = instr; Arg = null; }
9+
public InstructionAttribute(string explain, string instr, ArgKind arg) { Explain = explain; Instr = instr; Arg = arg; }
10+
public string Explain { get; private set; }
11+
public string Instr { get; private set; }
12+
public ArgKind? Arg { get; private set; }
13+
}
14+
}

0 commit comments

Comments
 (0)