Skip to content

Instantly share code, notes, and snippets.

@doeixd
Last active July 17, 2024 19:13
Show Gist options
  • Save doeixd/8b4196e6fbae263455fdba0da7135e76 to your computer and use it in GitHub Desktop.
Save doeixd/8b4196e6fbae263455fdba0da7135e76 to your computer and use it in GitHub Desktop.
Calculator program
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