// This is in the public domain
//#define DEBUG
using System;
using System.CodeDom.Compiler;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using static System.Environment;
using static System.Linq.Expressions.Expression;
public static class Test
{
[Conditional("DEBUG")]
private static void DebugSetup()
{
var listeners = Debug.Listeners;
listeners.Clear();
listeners.Add(new ConsoleTraceListener());
}
private static readonly string Rod = new string('─', 77);
private static T Ask<T>() => (T)Convert.ChangeType(Console.ReadLine(), typeof(T));
static Test()
{
DebugSetup();
}
public static int Main()
{
try
{
switch (char.ToLower(Ask<char>()))
{
case 'b':
using (var @out = Console.OpenStandardOutput())
{
var bf = Brainfuck(Console.ReadLine(), typeof(ushort),
@out.WriteByte, ReadByteInternal);
Debug.WriteLine(bf.Type);
Debug.WriteLine(bf);
Console.Out.Flush();
bf.Compile()(new byte[MemorySize]);
break;
}
case 'c':
{
var bf = Brainfuck(Console.ReadLine(), typeof(ushort),
Console.Write, ReadCharInternal);
Debug.WriteLine(bf.Type);
Debug.WriteLine(bf);
bf.Compile()(new char[MemorySize]);
break;
}
default:
Console.Error.WriteLine("Input error.");
return 1;
}
}
catch (ArgumentException e)
{
Debug.WriteLine(e);
Console.Error.WriteLine(e.Message);
return 1;
}
return 0;
}
private const int MemorySize = 1 << 12;
private static byte ReadByteInternal()
{
var value = Console.Read();
switch (value)
{
case (int)'\n':
case -1:
return 0;
default:
return (byte)value;
}
}
private static char ReadCharInternal()
{
var value = Console.Read();
switch (value)
{
case (int)'\n':
case -1:
return '\0';
default:
return (char)value;
}
}
private static Expression OperateInteger(Expression expression, Type integerType, bool decrement)
{
if (integerType != null)
{
var converted = Convert(expression, integerType);
return Assign(expression,
Convert(decrement ? Decrement(converted) : Increment(converted),
expression.Type));
}
else
{
return decrement ? PreDecrementAssign(expression) : PreIncrementAssign(expression);
}
}
private static MethodCallExpression DelegateCall(Delegate @delegate, params Expression[] arguments)
{
var method = @delegate.Method;
var target = @delegate.Target;
var eArguments = arguments.AsEnumerable();
if (method.IsStatic)
{
if (target != null)
{
eArguments = eArguments.Prepend(Constant(target));
}
target = null;
}
return Call(target?.ConstantInternal(), method, eArguments);
}
private static ConstantExpression ConstantInternal(this object value) => Constant(value);
private static Expression<Action<T[]>> Brainfuck<T>(string code, Type integerType, Action<T> writeMethod, Func<T> readMethod)
{
var array = Parameter(typeof(T[]), "array");
var index = Variable(typeof(int));
var variables = new[] { index };
var current = ArrayAccess(array, index);
var write = DelegateCall(writeMethod, current);
var read = Assign(current, DelegateCall(readMethod));
var cInc = OperateInteger(current, integerType, false);
var cDec = OperateInteger(current, integerType, true);
var xInc = PostIncrementAssign(index);
var xDec = PostDecrementAssign(index);
var test = Equal(current, Default(typeof(T)));
var eStack = new Stack<List<Expression>>();
eStack.Push(new List<Expression> { Assign(index, Divide(ArrayLength(array), Constant(2))) });
try
{
foreach (var element in code)
{
var eList = eStack.Peek();
switch (element)
{
case '+':
eList.Add(cInc);
break;
case '-':
eList.Add(cDec);
break;
case '>':
eList.Add(xInc);
break;
case '<':
eList.Add(xDec);
break;
case '.':
eList.Add(write);
break;
case ',':
eList.Add(read);
break;
case '[':
eStack.Push(new List<Expression>());
break;
case ']':
{
var label = Label();
var @break = IfThen(test, Break(label));
var pop = eStack.Pop();
eStack.Peek().Add(Loop(Block(pop.Prepend(@break)), label));
break;
}
}
}
Debug.WriteLine(eStack.Count);
return Lambda<Action<T[]>>(Block(variables, eStack.Pop()), array);
}
catch (InvalidOperationException)
{
throw new ArgumentException("Compile error.", nameof(code));
}
}
}