OldTerminal/Assets/Scripts/ProgrammerCalculator.cs

205 lines
4.7 KiB
C#
Raw Blame History

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;
}
}