205 lines
4.7 KiB
C#
205 lines
4.7 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Numerics;
|
||
using UnityEngine;
|
||
|
||
public class ProgrammerCalculator : MonoBehaviour
|
||
{
|
||
public int CurrentBase { get; private set; } = 10;
|
||
|
||
public void SetBase(int b)
|
||
{
|
||
CurrentBase = b;
|
||
}
|
||
|
||
// ===== PUBLIC API =====
|
||
|
||
public string Evaluate(string expression)
|
||
{
|
||
var tokens = Tokenize(expression);
|
||
print(tokens);
|
||
foreach (var token in tokens)
|
||
print(token);
|
||
var rpn = ToRPN(tokens);
|
||
print(rpn);
|
||
foreach (var rpnn in rpn)
|
||
print(rpnn);
|
||
BigInteger result = EvalRPN(rpn);
|
||
print(result);
|
||
return ToBaseString(result, CurrentBase);
|
||
}
|
||
|
||
// ===== TOKENIZER =====
|
||
|
||
private List<string> Tokenize(string expr)
|
||
{
|
||
List<string> tokens = new List<string>();
|
||
string num = "";
|
||
|
||
foreach (char c in expr.ToUpper())
|
||
{
|
||
if (char.IsLetterOrDigit(c))
|
||
{
|
||
num += c;
|
||
}
|
||
else if ("+-*/()".Contains(c))
|
||
{
|
||
if (num != "")
|
||
{
|
||
tokens.Add(num);
|
||
num = "";
|
||
}
|
||
tokens.Add(c.ToString());
|
||
}
|
||
}
|
||
|
||
if (num != "")
|
||
tokens.Add(num);
|
||
|
||
return tokens;
|
||
}
|
||
|
||
// ===== SHUNTING YARD =====
|
||
|
||
private int Precedence(string op)
|
||
{
|
||
return (op == "+" || op == "-") ? 1 :
|
||
(op == "*" || op == "/") ? 2 : 0;
|
||
}
|
||
|
||
private List<string> ToRPN(List<string> tokens)
|
||
{
|
||
List<string> output = new List<string>();
|
||
Stack<string> ops = new Stack<string>();
|
||
|
||
foreach (var t in tokens)
|
||
{
|
||
if (IsNumber(t))
|
||
{
|
||
output.Add(t);
|
||
}
|
||
else if ("+-*/".Contains(t))
|
||
{
|
||
while (ops.Count > 0 && Precedence(ops.Peek()) >= Precedence(t))
|
||
output.Add(ops.Pop());
|
||
ops.Push(t);
|
||
}
|
||
else if (t == "(")
|
||
{
|
||
ops.Push(t);
|
||
}
|
||
else if (t == ")")
|
||
{
|
||
while (ops.Peek() != "(")
|
||
output.Add(ops.Pop());
|
||
ops.Pop();
|
||
}
|
||
}
|
||
|
||
while (ops.Count > 0)
|
||
output.Add(ops.Pop());
|
||
|
||
return output;
|
||
}
|
||
|
||
// ===== RPN EVALUATION =====
|
||
|
||
private BigInteger EvalRPN(List<string> rpn)
|
||
{
|
||
Stack<BigInteger> stack = new Stack<BigInteger>();
|
||
|
||
foreach (var t in rpn)
|
||
{
|
||
if (IsNumber(t))
|
||
{
|
||
stack.Push(ParseNumber(t));
|
||
}
|
||
else
|
||
{
|
||
BigInteger b = stack.Pop();
|
||
BigInteger a = stack.Pop();
|
||
stack.Push(ApplyOp(a, b, t));
|
||
}
|
||
}
|
||
|
||
return stack.Pop();
|
||
}
|
||
|
||
private BigInteger ApplyOp(BigInteger a, BigInteger b, string op)
|
||
{
|
||
return op switch
|
||
{
|
||
"+" => a + b,
|
||
"-" => a - b,
|
||
"*" => a * b,
|
||
"/" => b == 0 ? 0 : a / b,
|
||
_ => 0
|
||
};
|
||
}
|
||
|
||
// ===== NUMBER UTILS =====
|
||
|
||
private bool IsNumber(string s)
|
||
{
|
||
foreach (char c in s)
|
||
{
|
||
if (!char.IsDigit(c) && !(c >= 'A' && c <= 'F'))
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
private BigInteger ParseNumber(string s)
|
||
{
|
||
switch (CurrentBase)
|
||
{
|
||
case 16:
|
||
return BigInteger.Parse(s, System.Globalization.NumberStyles.HexNumber);
|
||
case 10:
|
||
return BigInteger.Parse(s);
|
||
case 8:
|
||
return ParseBase(s, 8);
|
||
case 2:
|
||
return ParseBase(s, 2);
|
||
default:
|
||
throw new Exception("Unsupported base");
|
||
}
|
||
}
|
||
|
||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> base 2 <20> 8
|
||
private BigInteger ParseBase(string s, int b)
|
||
{
|
||
BigInteger result = 0;
|
||
foreach (char c in s)
|
||
{
|
||
int digit = c - '0';
|
||
if (digit >= b) throw new Exception("Invalid digit for base " + b);
|
||
result = result * b + digit;
|
||
}
|
||
return result;
|
||
}
|
||
|
||
|
||
|
||
private string ToBaseString(BigInteger val, int b)
|
||
{
|
||
if (b == 10) return val.ToString();
|
||
if (val == 0) return "0";
|
||
|
||
bool neg = val < 0;
|
||
if (neg) val = BigInteger.Abs(val);
|
||
|
||
string digits = "0123456789ABCDEF";
|
||
string result = "";
|
||
|
||
while (val > 0)
|
||
{
|
||
int d = (int)(val % b);
|
||
result = digits[d] + result;
|
||
val /= b;
|
||
}
|
||
|
||
return neg ? "-" + result : result;
|
||
}
|
||
}
|