Last active
July 17, 2024 19:13
-
-
Save doeixd/8b4196e6fbae263455fdba0da7135e76 to your computer and use it in GitHub Desktop.
Calculator program
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
new App().start(); | |
public record struct ExpressionStack(List<Expression> expression) | |
{ | |
public List<Expression> stack => expression; | |
public Expression currentExpression => expression[expression.Count - 1]; | |
public void add() | |
{ | |
var newExpression = new Expression(null, null, null); | |
if (currentExpression is Expression exp) | |
{ | |
if (exp.Left == null) | |
{ | |
exp.Left = newExpression; | |
} | |
else if (exp.Right == null) | |
{ | |
exp.Right = newExpression; | |
}; | |
}; | |
stack.Add(newExpression); | |
} | |
public void pop() | |
{ | |
stack.RemoveAt(stack.Count - 1); | |
} | |
}; | |
public abstract record Parens; | |
public record LeftParen : Parens; | |
public record RightParen : Parens; | |
public abstract record Problem | |
{ | |
public abstract decimal Evaluate(); | |
}; | |
public record Left(decimal value) : Problem | |
{ | |
public override decimal Evaluate() { return value; } | |
}; | |
public record Right(decimal value) : Problem | |
{ | |
public override decimal Evaluate() { return value; } | |
}; | |
public record Expression(Problem? Left, Operation? Op, Problem? Right) : Problem | |
{ | |
public Problem? Left { get; set; } = Left; | |
public Operation? Op { get; set; } = Op; | |
public Problem? Right { get; set; } = Right; | |
public override decimal Evaluate() | |
{ | |
var subject = this; | |
if (subject.Left == null && subject.Op == null && subject.Right == null) return 0.0m; | |
if (subject.Left != null && subject.Op == null && subject.Right == null) return subject.Left.Evaluate(); | |
if (subject.Left == null && subject.Op != null && subject.Right == null) return 0.0m; | |
if (subject.Left == null && subject.Op == null && subject.Right != null) return subject.Right.Evaluate(); | |
if (subject == null) return 0.0m; | |
var leftValue = subject.Left?.Evaluate() ?? 0.0m; | |
var rightValue = subject.Right?.Evaluate() ?? 0.0m; | |
var result = subject.Op?.Execute(leftValue, rightValue) ?? 0.0m; | |
return result; | |
} | |
}; | |
public abstract record Operation | |
{ | |
public abstract decimal Execute(decimal left, decimal right); | |
public static Dictionary<char, Operation> map = new Dictionary<char, Operation>(){ | |
{'+', new Add()}, | |
{'-', new Subtract()}, | |
{'*', new Multiply()}, | |
{'/', new Divide()}, | |
{'^', new Power()}, | |
{ '%', new Modulus() } | |
}; | |
public static bool isOperator(char letter) | |
{ | |
return map.ContainsKey(letter); | |
} | |
public static Operation? fromChar(char letter) | |
{ | |
if (map.ContainsKey(letter)) return map[letter]; | |
return null; | |
} | |
public static Operation? fromLetter(Letter letter) | |
{ | |
if (letter.value == null) return null; | |
if (map.ContainsKey((char)letter.value)) return map[(char)letter.value]; | |
return null; | |
} | |
}; | |
public record Add : Operation | |
{ | |
public override decimal Execute(decimal left, decimal right) => left + right; | |
public char letter => '+'; | |
}; | |
public record Subtract : Operation | |
{ | |
public override decimal Execute(decimal left, decimal right) => left - right; | |
public char letter => '-'; | |
}; | |
public record Multiply : Operation | |
{ | |
public override decimal Execute(decimal left, decimal right) => left * right; | |
public char letter => '*'; | |
}; | |
public record Divide : Operation | |
{ | |
public override decimal Execute(decimal left, decimal right) => left / right; | |
public char letter => '-'; | |
}; | |
public record Power : Operation | |
{ | |
public override decimal Execute(decimal left, decimal right) => (decimal)Math.Pow((double)left, (double)right); | |
public char letter => '^'; | |
}; | |
public record Modulus : Operation | |
{ | |
public override decimal Execute(decimal left, decimal right) => left % right; | |
public char letter => '%'; | |
}; | |
public record struct Input(string input) | |
{ | |
public string Value => input; | |
public char? At(int idx) | |
{ | |
if (idx < input.Length) return input.ElementAtOrDefault(idx); | |
return null; | |
} | |
public Letter? Get(int idx) | |
{ | |
var val = At(idx); | |
if (val != null) return new Letter(input, idx); | |
return null; | |
} | |
public Problem Parse() | |
{ | |
var idx = 0; | |
var problem = new Expression(null, null, null); | |
var stack = new ExpressionStack(new List<Expression>() { problem }); | |
while (idx < input.Length) | |
{ | |
var _input = this.input; | |
var letter = () => new Letter(_input, idx); | |
if (letter().value == null) break; | |
if (letter().isSpace) | |
{ | |
idx++; | |
continue; | |
}; | |
if (letter().isParen) | |
{ | |
var type = letter().toParen(); | |
if (type is LeftParen) stack.add(); | |
if (type is RightParen) stack.pop(); | |
idx++; | |
continue; | |
}; | |
{ | |
var hasLeft = stack.currentExpression.Left != null; | |
var hasOp = stack.currentExpression.Op != null; | |
var hasRight = stack.currentExpression.Right != null; | |
var isExpressionFull = hasLeft && hasOp && hasRight; | |
var doesContinue = !Letter.IsNumber(letter().next) && !letter().next.isParen && isExpressionFull; | |
if (doesContinue) | |
{ | |
throw new Exception("Multi-Part Expressions are not supported at this time, please use parenthesis to group expressions"); | |
}; | |
}; | |
var isNegativeNumber = letter().isNegativeNumber; | |
if (isNegativeNumber) | |
{ | |
idx++; | |
if (idx > input.Length) continue; | |
}; | |
if (letter().isNumberLike(false)) | |
{ | |
var collected = letter().collectNumber(); | |
if (collected == null) throw new Exception("Invalid Number"); | |
var number = collected.Value.number; | |
var newIndex = collected.Value.idx; | |
idx = newIndex; | |
if (idx > input.Length) break; | |
if (isNegativeNumber) | |
{ | |
number = Decimal.Negate(number); | |
}; | |
if (stack.currentExpression is Expression expression) | |
{ | |
if (expression.Left == null) | |
{ | |
expression.Left = new Left(number); | |
} | |
else if (expression.Right == null) | |
{ | |
expression.Right = new Right(number); | |
}; | |
} | |
else | |
{ | |
throw new Exception("Invalid Expression"); | |
}; | |
continue; | |
}; | |
if (letter().isOperator) | |
{ | |
var operation = letter().toOperation(); | |
if (stack.currentExpression is Expression expr) | |
{ | |
expr.Op = operation; | |
}; | |
idx++; | |
continue; | |
}; | |
throw new Exception("Invalid Expression"); | |
}; | |
return problem; | |
} | |
} | |
public record struct io() | |
{ | |
public static void log(string s, ConsoleColor? color = ConsoleColor.White) | |
{ | |
if (color != null) Console.ForegroundColor = color.Value; | |
Console.WriteLine(" " + s); | |
Console.ResetColor(); | |
} | |
public static void err(string s) | |
{ | |
Console.ForegroundColor = ConsoleColor.Red; | |
Console.WriteLine(s); | |
Console.ResetColor(); | |
} | |
public static string prompt(string message) | |
{ | |
Console.ForegroundColor = ConsoleColor.Blue; | |
Console.WriteLine(" " + message); | |
Console.ResetColor(); | |
var input = Console.ReadLine()?.Trim() ?? ""; | |
return input; | |
} | |
} | |
public record struct Letter(string input, int idx) | |
{ | |
public Input Input => new Input(input); | |
public char? value => Input.At(idx); | |
public Letter next => new Letter(input, idx + 1); | |
public Letter prev => new Letter(input, idx - 1); | |
public bool isNumber => value != null && char.IsNumber((char)value); | |
public static bool IsNumber(Letter letter) | |
{ | |
if (letter.value == null) return false; | |
return char.IsNumber((char)letter.value); | |
} | |
public bool isNumberSeparator => value == ',' || value == '.' || value == '_'; | |
public bool isNumberLike(bool withNegative = false) | |
{ | |
var subject = this.value; | |
var nextSubject = this.next; | |
var isSubjectPeriod = subject == '.'; | |
var isSubjectMinus = subject == '-'; | |
var isNextSubjectNumber = nextSubject.isNumber; | |
var isValidStartingChar = (isSubjectMinus && withNegative) || isSubjectPeriod; | |
return this.isNumber || (isValidStartingChar && isNextSubjectNumber); | |
} | |
public bool isNegativeNumber => this.value == '-' && this.next.isNumberLike(false); | |
public bool isOperator => value != null && Operation.isOperator((char)value); | |
public Operation? toOperation() => Operation.fromLetter(this); | |
public Parens? toParen() | |
{ | |
if (this.value == '(') return new LeftParen(); | |
if (this.value == ')') return new RightParen(); | |
return null; | |
} | |
public bool isParen => this.value == '(' || this.value == ')'; | |
public bool isSpace => this.value != null && char.IsWhiteSpace((char)this.value); | |
public (decimal number, int idx)? collectNumber() | |
{ | |
var number = ""; | |
var loopIdx = this.idx; | |
while (loopIdx < this.input.Length) | |
{ | |
var letter = new Letter(this.input, loopIdx); | |
var isValidNumber = letter.value != null && (letter.isNumber || letter.isNumberSeparator); | |
if (!isValidNumber) { break; } | |
number += letter.value; | |
loopIdx++; | |
} | |
if (number[0] == '.') number = "0" + number; | |
number.Replace(",", ""); | |
number.Replace("_", ""); | |
try | |
{ | |
return (decimal.Parse(number), loopIdx); | |
} | |
catch | |
{ | |
return null; | |
} | |
} | |
}; | |
public record struct App | |
{ | |
public Input? input; | |
public Input getUserInput() | |
{ | |
var userInput = io.prompt("What's your calculation? "); | |
var createdInput = new Input(userInput); | |
input = createdInput; | |
return createdInput; | |
} | |
public void start() | |
{ | |
while (true) | |
{ | |
try | |
{ | |
var newInput = getUserInput(); | |
var problem = newInput.Parse(); | |
var result = problem.Evaluate(); | |
io.log("\nResult: " + newInput.input + " = " + result.ToString() + "\n", ConsoleColor.Green); | |
} | |
catch (Exception e) | |
{ | |
io.err(e.Message); | |
io.log("\n"); | |
continue; | |
} | |
}; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment