2
\$\begingroup\$

I wanted to see how simple it would be to implement a stack-based interpreter in C# based on this question: Simple stack based interpreter

The program works with the command line arguments which can either be a double or a function from the following table:

instruction op1 op2 op3
abs a
acos a
acosh a
add a b
asin a
asinh a
atan a
atan2 a b
atanh a
bitdecrement a
bitincrement a
cbrt a
ceiling a
clamp a b c
copysign a b
cos a
cosh a
div a b
exp a
floor a
fusedmultiplyadd a b c
ieeeremainder a b
log a b
log10 a
log2 a
max a b
maxmagnitude a b
min a b
minmagnitude a b
mul a b
pow a b
round a
sin a
sinh a
sqrt a
sub a b
tan a
tanh a
truncate a

Example programs:

hypot: expected result: 5

4 2 pow 3 2 pow add sqrt

After having some fun with Reflection and Linq I came up with the following code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace StackProg
{
    class SimpleMath
    {
        public static double add(double a, double b) { return a + b; }
        public static double sub(double a, double b) { return a - b; }
        public static double mul(double a, double b) { return a * b; }
        public static double div(double a, double b) { return a / b; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var methods = new SortedDictionary<string, Tuple<int, MethodInfo>>();
            foreach (var type in new Type[] {typeof(SimpleMath), typeof(Math)})
            {
                foreach (var method in type.GetMethods().Where(m => m.ReturnType == typeof(double)))
                {
                    var margs = method.GetParameters();
                    if (margs.Any(p => p.ParameterType != typeof(double)) == false)
                        methods[method.Name.ToLower()] = new Tuple<int, MethodInfo>(margs.Length, method);
                }
            }

            var stack = new Stack<double>();
            foreach (var arg in args)
            {
                if (double.TryParse(arg, out var val))
                {
                    stack.Push(val);
                }
                else if (methods.TryGetValue(arg, out var method))
                {
                    stack.Push((double)method.Item2.Invoke(null, PopStack(stack, method.Item1)));
                }
                else
                {
                    Console.WriteLine("Invalid instruction: " + arg);
                    return;
                }
            }
            Console.Write(stack.Pop());
        }

        private static object[] PopStack(Stack<double> stack, int count)
        {
            var list = new List<object>(count);
            for (int i = 0; i < count; i++)
                list.Insert(0, stack.Pop());
            return list.ToArray();
        }
    }
}
\$\endgroup\$
3
  • 1
    \$\begingroup\$ I would suggest to use ValueTuple with named fields to increase readability. Item1 and Item2 are not really coder friendly names \$\endgroup\$ Nov 18 '21 at 9:58
  • 1
    \$\begingroup\$ Also in case of PopStack you know the count so you can create an array with the specified length and you don't have to use an intermediate List collection. \$\endgroup\$ Nov 18 '21 at 10:00
  • \$\begingroup\$ @PeterCsala, Thanks for the nice tips! PopStack sort of bugged me. I considered using a ConcurrentStack which has TryPopRange but that looked ugly :-) \$\endgroup\$
    – jdt
    Nov 18 '21 at 14:37
2
\$\begingroup\$

PopStack

  • This can be rewritten like this using LINQ:
private static object[] PopStack(Stack<double> stack, int count)
    => Enumerable.Range(0, count).Select(_ => stack.Pop()).Cast<object>().ToArray();

SimpleMath

  • This class can be marked as static since all of its members are static
  • This class looks a bit weird/odd to me
    • You have defined a class just to expose built-in operators
    • The methods' naming do not follow C# standards
  • My alternative solution relies on lambda expressions:
static Dictionary<string, Func<double, double, double>> simpleMath = new()
{
    { "add", (a, b) => a + b },
    { "sub", (a, b) => a - b },
    { "mul", (a, b) => a * b },
    { "div", (a, b) => a / b },
};
  • Yes I know, the simpleMath's type is ugly as hell
    • But we have avoided to repeat this: public static double (double , double ) { return ; }
    • Here the naming can be arbitrary, it does not have to follow any standard
  • Because these methods are not static that's why they need an invocation target as well.
    • So, we need to store that information next to the name and method info
    • I've defined a record structure for that
record MethodInvocation(int ParameterCount, MethodInfo Method, object Target)
{
}
  • Finally let's transform the simpleMath to the desired form
static IEnumerable<KeyValuePair<string, MethodInvocation>> GetSimpleMath()
{
    foreach (var operation in simpleMath)
    {
        var mi = operation.Value.Method;
        yield return new (operation.Key, new MethodInvocation(mi.GetParameters().Length, mi, operation.Value.Target));
    }
}

AdvancedMath

  • Following the same pattern as above we can do the same with the built-in methods:
static IEnumerable<KeyValuePair<string, MethodInvocation>> GetAdvancedMath()
{
    foreach (var method in typeof(Math).GetMethods().Where(m => m.ReturnType == typeof(double)))
    {
        var @params = method.GetParameters();
        if (@params.Any(p => p.ParameterType != typeof(double)) is not true)
            yield return new (method.Name.ToLower(), new MethodInvocation(@params.Length, method, null));
    }
}

Main

  • With these in our hand we can simplify the first part of the Main method like this
static void Main(string[] args)
{
    var methods = new SortedDictionary<string, MethodInvocation>();

    foreach (var operation in GetSimpleMath())
        methods.Add(operation.Key, operation.Value);

    foreach (var operation in GetAdvancedMath())
        methods.TryAdd(operation.Key, operation.Value);

    var operationStack = new Stack<double>();
            
    foreach (var arg in args)
    {
        if (double.TryParse(arg, out var val))
        {
            operationStack.Push(val);
        }
        else if (methods.TryGetValue(arg, out var method))
        {
            var @params = PopStack(operationStack, method.ParameterCount);
            var result = (double)method.Method.Invoke(method.Target, @params);
            operationStack.Push(result);
        }
        else
        {
            Console.WriteLine("Invalid instruction: " + arg);
            return;
        }
    }
    Console.Write(operationStack.Pop());
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.