From 36f19c62c2266973818908a10d7962f055714db2 Mon Sep 17 00:00:00 2001 From: krolxon Date: Sat, 17 Feb 2024 20:45:28 +0530 Subject: add build script --- src/Calculator.java | 30 +++++++ src/GFrame.java | 219 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/History.java | 17 ++++ src/Parser.java | 158 +++++++++++++++++++++++++++++++++++++ 4 files changed, 424 insertions(+) create mode 100644 src/Calculator.java create mode 100644 src/GFrame.java create mode 100644 src/History.java create mode 100644 src/Parser.java (limited to 'src') diff --git a/src/Calculator.java b/src/Calculator.java new file mode 100644 index 0000000..c43ca9f --- /dev/null +++ b/src/Calculator.java @@ -0,0 +1,30 @@ +import java.util.Scanner; + +public class Calculator { + public static void main(String[] args) { + // String expr = "(84 / 4 * 3 - 9) * 2 + 1 / 5"; // 108.2 + String expr = new String(" "); + Scanner sc = new Scanner(System.in); + Parser p; + + if (args.length == 0) { + new GFrame("Calculator"); + } else { + if (args[0].equals("-n")) { + System.out.println("Type \"exit\" or Ctrl+C to exit."); + while (true) { + System.out.print(">>> "); + expr = sc.nextLine(); + if (!expr.equals("exit")) { + p = new Parser(expr); + System.out.println(p.eval()); + } else { + sc.close(); + break; + } + } + } else { + } + } + } +} diff --git a/src/GFrame.java b/src/GFrame.java new file mode 100644 index 0000000..f03c154 --- /dev/null +++ b/src/GFrame.java @@ -0,0 +1,219 @@ +/* +* TODO: +* Item positions are hard coded, make them dynamic +* so that window can be set to resizable. +*/ + +import java.util.Vector; +import javax.swing.*; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.Insets; +import java.awt.event.*; +import java.awt.*; +import javax.swing.border.Border; +import java.text.DecimalFormat; + +class RoundBtn implements Border { + private int r; + + RoundBtn(int r) { + this.r = r; + } + + public Insets getBorderInsets(Component c) { + return new Insets(this.r + 1, this.r + 1, this.r + 2, this.r); + } + + public boolean isBorderOpaque() { + return true; + } + + public void paintBorder(Component c, Graphics g, int x, int y, + int width, int height) { + g.drawRoundRect(x, y, width - 1, height - 1, r, r); + } +} + +public class GFrame extends JFrame { + Parser p; + JTextField tf; + + private JButton newButton(String text, int d1, int d2, int d3, int d4) { + JButton b = new JButton(); + // b.setBackground(Color.decode("#F78361")); + b.setBackground(Color.decode("#2B2B2B")); + b.setForeground(Color.decode("#e5e5e5")); + b.setFont((new Font("Times New Roman", Font.PLAIN, 20))); + b.setBounds(d1, d2, d3, d4); + b.setBorder(new RoundBtn(15)); + b.setText(text); + return b; + } + + private void actionAdderForTextField(JButton b, String val) { + b.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + tf.setText(tf.getText() + val); + } + }); + + } + + GFrame(String title) { + this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + this.setTitle(title); + this.getContentPane().setBackground(Color.decode("#111111")); + this.setResizable(false); + + // Text Field + tf = new JTextField(); + tf.setBackground(Color.decode("#2B2B2B")); + tf.setForeground(Color.decode("#e5e5e5")); + tf.setMargin(new Insets(0, 10, 0, 10)); + tf.setBounds(40, 50, 310, 40); + tf.setFont((new Font("Times New Roman", Font.PLAIN, 20))); + tf.setBorder(new RoundBtn(5)); + + // History Text Field + History history = new History(); + JTextArea tHist = new JTextArea(); + tHist.setBounds(380, 50, 200, 240); + tHist.setEditable(false); + tHist.setBorder(new RoundBtn(5)); + tHist.setBackground(Color.decode("#2B2B2B")); + tHist.setForeground(Color.decode("#e5e5e5")); + tHist.setFont((new Font("Times New Roman", Font.PLAIN, 20))); + tHist.setBorder(new RoundBtn(5)); + tHist.setMargin(new Insets(0, 10, 0, 10)); + + // Buttons + JButton bEval = newButton("=", 250, 100, 100, 40); + JButton bClear = newButton("CL", 180, 100, 60, 40); + JButton bClearHistory = newButton("Clear History", 380, 300, 200, 40); + JButton bAdd = newButton("+", 250, 300, 100, 40); + JButton bSub = newButton("-", 250, 250, 100, 40); + JButton bMul = newButton("x", 250, 200, 100, 40); + JButton bDiv = newButton("%", 250, 150, 100, 40); + JButton bCut = newButton("", 180, 300, 60, 40); + bCut.setFont(new Font("JetBrainsMono Nerd Font", Font.PLAIN, 20)); + JButton bRightPar = newButton(")", 110, 100, 60, 40); + JButton bLeftPar = newButton("(", 40, 100, 60, 40); + JButton bDoubleZero = newButton("00", 40, 300, 60, 40); + JButton bZero = newButton("0", 110, 300, 60, 40); + JButton bOne = newButton("1", 40, 250, 60, 40); + JButton bTwo = newButton("2", 110, 250, 60, 40); + JButton bThree = newButton("3", 180, 250, 60, 40); + JButton bFour = newButton("4", 40, 200, 60, 40); + JButton bFive = newButton("5", 110, 200, 60, 40); + JButton bSix = newButton("6", 180, 200, 60, 40); + JButton bSeven = newButton("7", 40, 150, 60, 40); + JButton bEight = newButton("8", 110, 150, 60, 40); + JButton bNine = newButton("9", 180, 150, 60, 40); + + // Add the buttons to the Frame + this.add(bEval); + this.add(bClear); + this.add(bClearHistory); + this.add(bAdd); + this.add(bSub); + this.add(bMul); + this.add(bDiv); + this.add(bCut); + this.add(bRightPar); + this.add(bLeftPar); + this.add(bDoubleZero); + this.add(bZero); + this.add(bOne); + this.add(bTwo); + this.add(bThree); + this.add(bFour); + this.add(bFive); + this.add(bSix); + this.add(bSeven); + this.add(bEight); + this.add(bNine); + this.add(tf); + this.add(tHist); + this.setSize(600, 400); + this.setLayout(null); + this.setVisible(true); + + JLabel label = new JLabel("Enter Expression: "); + label.setFont((new Font("Times New Roman", Font.PLAIN, 20))); + label.setBounds(40, 20, 150, 40); + label.setForeground(Color.decode("#e5e5e5")); + this.add(label); + + // ActionsListeners + bEval.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + if (tf.getText().length() != 0) { + String expression = tf.getText(); + DecimalFormat format = new DecimalFormat(); + p = new Parser(tf.getText()); + String formattedResult = format.format(p.eval()); + tf.setText(formattedResult); + + tHist.setText(""); + Vector s = history.getHistory(); + s.add(expression + " = " + formattedResult); + for (int i = 0; i < s.size(); i++) { + // tHist.setText(new String(s.get(i).concat(tHist.getText())).concat("\n")); + tHist.append(s.get(i)); + tHist.append("\n"); + } + + } else { + tf.setText("No input"); + } + } + }); + + // Common actions that just appends the symbols to the text field + actionAdderForTextField(bAdd, "+"); + actionAdderForTextField(bSub, "-"); + actionAdderForTextField(bMul, "*"); + actionAdderForTextField(bDiv, "/"); + actionAdderForTextField(bRightPar, ")"); + actionAdderForTextField(bLeftPar, "("); + actionAdderForTextField(bDoubleZero, "00"); + actionAdderForTextField(bZero, "0"); + actionAdderForTextField(bOne, "1"); + actionAdderForTextField(bTwo, "2"); + actionAdderForTextField(bThree, "3"); + actionAdderForTextField(bFour, "4"); + actionAdderForTextField(bFive, "5"); + actionAdderForTextField(bSix, "6"); + actionAdderForTextField(bSeven, "7"); + actionAdderForTextField(bEight, "8"); + actionAdderForTextField(bNine, "9"); + + bClear.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + tf.setText(""); + } + }); + + bClearHistory.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + history.clearHistory(); + tHist.setText(""); + } + }); + + bCut.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + String text = tf.getText(); + tf.setText(text.substring(0, text.length() - 1)); + } + }); + + // bOne.addActionListener(new ActionListener() { + // public void actionPerformed(ActionEvent e) { + // tf.setText(tf.getText() + "1"); + // } + // }); + } +} diff --git a/src/History.java b/src/History.java new file mode 100644 index 0000000..ef2807b --- /dev/null +++ b/src/History.java @@ -0,0 +1,17 @@ +import java.util.Vector; + +public class History { + private Vector hist; + + History() { + hist = new Vector(); + } + + public void clearHistory() { + hist.clear(); + } + + public Vector getHistory() { + return hist; + } +} diff --git a/src/Parser.java b/src/Parser.java new file mode 100644 index 0000000..bd2cde9 --- /dev/null +++ b/src/Parser.java @@ -0,0 +1,158 @@ +/* Todo +* - Add support for trigonometric functions +* - Remove all edge cases +*/ + +import java.util.Stack; +import java.util.StringJoiner; + +public class Parser { + private String expr; + private String postfix; + private Stack operatorStack; + + public Parser(String infixExpr) { + expr = infixExpr.trim().replaceAll("\\s", ""); // remove whitespaces + postfix = toPostFix(); + } + + private boolean isOperand(char c) { + switch (c) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + return true; + default: + return false; + } + } + + private boolean isOperand(String c) { + return isOperand(c.charAt(0)); + } + + private int getPresedence(char c) { + switch (c) { + case '^': + return 3; + case '/': + return 2; + case '*': + return 2; + case '+': + return 1; + case '-': + return 1; + default: + return -1; + } + } + + private boolean hasLeftAssociativity(char c) { + if (c == '^') { + return false; + } else { + return true; + } + } + + private String toPostFix() { + operatorStack = new Stack<>(); + StringJoiner output = new StringJoiner(" "); + StringBuilder operand = new StringBuilder(); + char[] infix = expr.replaceAll(" ", "").toCharArray(); + + // Main Expression Iteration + for (int i = 0; i < infix.length; i++) { + char c = infix[i]; + + // Keep appending digits to the operand StringBuilder and skip iterations till + // we find a operator. + if (isOperand(c)) { + operand.append(infix[i]); + if (i < infix.length - 1) { + continue; + } + } + + if (operand.length() > 0) { + output.add(operand.toString()); + operand = new StringBuilder(); + } + + if (c == '(') { + operatorStack.push(c); + } else if (c == ')') { + while (!operatorStack.isEmpty() && operatorStack.peek() != '(') { + output.add(operatorStack.pop().toString()); + } + operatorStack.pop(); + } else { + // Manage precedence order of operatorStack operators + while (!operatorStack.isEmpty() && getPresedence(c) <= getPresedence((char) operatorStack.peek()) + && hasLeftAssociativity(c)) { + output.add(operatorStack.pop().toString()); + } + + // For whatever reason, the last digit gets added to the operatorStack, leading + // to a duplicate of last digit in the expression + // To fix this, we again make sure that the current char is not a operand. + if (!isOperand(c)) { + operatorStack.push(c); + } + } + } + + // pop all the operators left in the operatorStack after the loop is done. + while (!operatorStack.isEmpty()) { + if (operatorStack.peek() == '(') { + return "This expression is invalid"; + } + output.add(operatorStack.pop().toString()); + } + + return output.toString(); + } + + private Double evaluate(String operator, Double op1, Double op2) { + switch (operator.charAt(0)) { + case '+': + return op2 + op1; + case '-': + return op1 - op2; + case '*': + return op2 * op1; + case '/': + return op1 / op2; + case '^': + return (Math.pow(op2, op1)); + default: + return 0.0; + } + } + + public double eval() { + Stack stack = new Stack(); + for (String c : postfix.split(" ")) { + if (isOperand(c)) { + stack.push(Double.parseDouble(c)); + } else { + Double op1 = (Double) (stack.pop()); + Double op2 = (Double) (stack.pop()); + stack.push(evaluate(c, op2, op1)); + } + } + return stack.pop(); + } + + public String getPostfix() { + return postfix; + } +} -- cgit v1.2.3