-
-
Save datavudeja/a091b4e808e18871825f9972838e76e2 to your computer and use it in GitHub Desktop.
A math calculator parser in python.
This file contains hidden or 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
| # Thanks blanket#1213 https://github.com/blanketsucks for helping. | |
| from dataclasses import dataclass | |
| import enum | |
| import math | |
| class MathError(Exception): | |
| pass | |
| class InavliadSyntax(MathError): | |
| def __init__(self, *args: object) -> None: | |
| super().__init__(*args) | |
| class Mismatchedparentheses(MathError): | |
| def __init__(self, *args: object) -> None: | |
| super().__init__(*args) | |
| class MissingParentheses(MathError): | |
| def __init__(self, *args: object) -> None: | |
| super().__init__(*args) | |
| class InvalidTokens(MathError): | |
| def __init__(self, *args: object) -> None: | |
| super().__init__(*args) | |
| class TokenType(enum.Enum): | |
| NUM = "NUM" | |
| ADD = "+" | |
| SUB = "-" | |
| MUL = "*" | |
| DIV = "/" | |
| POW = "^" | |
| POPN = "(" | |
| PCLS = ")" | |
| SQRT = "sqrt" | |
| SIN = "sin" | |
| COS = "cos" | |
| TAN = "tan" | |
| DEG = "deg" | |
| RAD = "rad" | |
| ASIN = "asin" | |
| ACOS = "acos" | |
| ATAN = "atan" | |
| PI = 'pi' | |
| @dataclass | |
| class Token: | |
| """a Token""" | |
| value: str | |
| type: TokenType | |
| class Tokeniser: | |
| """lexer""" | |
| def __init__(self, expr:str) -> None: | |
| self.expr: str = expr | |
| self.cur: str = '' | |
| self.pos: int = -1 | |
| self.len_e: int = len(expr) | |
| self.tokens: list[Token] = [] | |
| self.next() | |
| def next(self): | |
| self.pos = self.pos+1 if self.pos+1 < self.len_e else float('inf') #type:ignore | |
| self.cur = self.expr[self.pos] if self.pos < self.len_e else '' | |
| def jump_space(self): | |
| ch: str = self.cur | |
| if ch == ' ': | |
| while self.cur == ' ': | |
| self.next() | |
| def tokenise(self ) -> list[Token]: | |
| """Creating tokens""" | |
| def fullNum(ch:str) -> str: | |
| if (ch >= '0' and ch <= '9') or ch == '.': | |
| self.next() | |
| return ch+fullNum(self.cur) | |
| return '' | |
| def fullName(ch:str) -> str: | |
| if (self.cur >= 'a' and self.cur <= 'z') or (self.cur >= 'A' and self.cur <= 'Z'): | |
| self.next() | |
| return ch+fullName(self.cur) | |
| return '' | |
| p: str = '' | |
| while self.pos < self.len_e: | |
| self.jump_space() | |
| if (self.cur >= '0' and self.cur <= '9') or self.cur == '.' : | |
| p = fullNum(self.cur) | |
| self.tokens.append(Token(float(p) , TokenType.NUM)) | |
| elif (self.cur >= 'a' and self.cur <= 'z') or (self.cur >= 'A' and self.cur <= 'Z'): | |
| p = fullName(self.cur) | |
| if p in ('sqrt', 'sin', 'cos', 'tan', 'asin', 'acos', 'atan', 'deg', 'rad', 'pi'): | |
| self.tokens.append(Token(p, TokenType(p))) | |
| else: | |
| raise InvalidTokens("Invaliad characters") | |
| elif (self.cur in ('+', '-', '*', '/', '^')): | |
| self.tokens.append(Token(self.cur , TokenType(self.cur))) | |
| self.next() | |
| elif (self.cur in ('(', ')' )): | |
| self.tokens.append(Token(self.cur , TokenType(self.cur))) | |
| self.next() | |
| else: | |
| raise InavliadSyntax('Something wrong with the expression.') | |
| Grammar = """ | |
| Expression : Term { ( "+" | "-" ) Term } | |
| | Number { ( "+" | "-" ) Number } | |
| | Term | |
| | Number | |
| Term : Factor { ( "*" | "/" | "^" ) Factor } | |
| | Number { ( "*" | "/" | "^" ) Number } | |
| Factor : ( "-" ) { Number | ( "(" ) Expression ( ")" ) } | |
| | sqrt ( Number | ( "(" ) Expression ( ")" ) ) | |
| | unary ( "(" Number | ( "(" ) Expression ( ")" ) ")" ) | |
| Number : { "0-9" } | |
| """ | |
| class Parser: | |
| """parser""" | |
| def __init__(self, tokens:list[Token]) -> None: | |
| self.tokens: list[Token] = tokens | |
| def parse(self) -> float: | |
| """parsing expressions""" | |
| left: float = self.term() | |
| if len(self.tokens) < 1: | |
| return left | |
| current: Token = self.tokens[0] | |
| if current.type is TokenType.ADD: | |
| self.tokens.pop(0) | |
| right: float = self.parse() | |
| left = left+right | |
| elif current.type is TokenType.SUB: | |
| self.tokens.pop(0) | |
| right: float = self.parse() | |
| left = left-right | |
| return left | |
| def term(self) -> float: | |
| """parsing terms""" | |
| left: float = self.highTerm() | |
| if len(self.tokens) < 1: | |
| return left | |
| current: Token = self.tokens[0] | |
| if current.type is TokenType.DIV: | |
| self.tokens.pop(0) | |
| right: float = self.nextNumber() | |
| left = left/right | |
| elif current.type is TokenType.MUL: | |
| self.tokens.pop(0) | |
| right: float = self.nextNumber() | |
| left = left*right | |
| return left | |
| def highTerm(self) -> float: | |
| """parsing high precendece terms""" | |
| left: float = self.factor() | |
| if len(self.tokens) < 1: | |
| return left | |
| current = self.tokens[0] | |
| if current.type is TokenType.POW: | |
| self.tokens.pop(0) | |
| right = self.nextNumber() | |
| left = left**right | |
| return left | |
| def nextNumber(self): | |
| if self.tokens[0].type == TokenType.NUM: | |
| return self.tokens.pop(0).value | |
| return self.parse() | |
| def function(self) -> float: | |
| """parsing functions""" | |
| left: float = self.tokens.pop(0) | |
| if left.type is not TokenType.POPN: | |
| raise InavliadSyntax("Invalid Syntax") | |
| value: float = self.parse() | |
| try: | |
| right: Token = self.tokens.pop(0) | |
| except IndexError: | |
| raise MissingParentheses("Missing parentheses") | |
| else: | |
| if right.type is not TokenType.PCLS: | |
| raise MissingParentheses("Missing parentheses") | |
| return value | |
| def factor(self) -> float: | |
| """prasing factors""" | |
| if len(self.tokens) < 1: | |
| raise InavliadSyntax("Invalid Syntax") | |
| left: Token = self.tokens.pop(0) | |
| if left.type is TokenType.NUM: | |
| return left.value | |
| if left.type is TokenType.PI: | |
| return math.pi | |
| elif left.type is TokenType.POPN: | |
| value:float = self.parse() | |
| try: | |
| right: Token = self.tokens.pop(0) | |
| except IndexError: | |
| raise Mismatchedparentheses("Mismatched parentheses") | |
| else: | |
| if right.type is not TokenType.PCLS: | |
| raise Mismatchedparentheses("Mismatched parentheses") | |
| return value | |
| elif left.type is TokenType.SUB: | |
| right: float = -self.factor() | |
| return right | |
| elif left.type is TokenType.ADD: | |
| right: float = self.factor() | |
| return right | |
| elif left.type is TokenType.SQRT: | |
| right: float = self.function() | |
| value: float = math.sqrt(right) | |
| return value | |
| elif left.type is TokenType.SIN: | |
| right: float = self.function() | |
| value: float = math.sin(right) | |
| return value | |
| elif left.type is TokenType.COS: | |
| right: float = self.function() | |
| value: float = math.cos(right) | |
| return value | |
| elif left.type is TokenType.TAN: | |
| right: float = self.function() | |
| value: float = math.tan(right) | |
| return value | |
| elif left.type is TokenType.RAD: | |
| right: float = self.function() | |
| value: float = math.radians(right) | |
| return value | |
| elif left.type is TokenType.DEG: | |
| right: float = self.function() | |
| value: float = math.degrees(right) | |
| return value | |
| elif left.type is TokenType.ASIN: | |
| right: float = self.function() | |
| value: float = math.asin(right) | |
| return value | |
| elif left.type is TokenType.ACOS: | |
| right: float = self.function() | |
| value: float = math.acos(right) | |
| return value | |
| elif left.type is TokenType.ATAN: | |
| right: float = self.function() | |
| value: float = math.atan(right) | |
| return value | |
| else: | |
| raise InavliadSyntax("Inavliad syntax") | |
| def main(self): | |
| # try: | |
| try: | |
| temp = self.parse() | |
| if len(self.tokens) > 0: | |
| raise InavliadSyntax("Syntax Invalid") | |
| return temp | |
| except OverflowError: | |
| return "Number too high." | |
| except IndexError: | |
| return "Something went wrong. Try again later." | |
| except MissingParentheses: | |
| return "Missing parantheses" | |
| except Mismatchedparentheses: | |
| return "Mismatched parantheses" | |
| except InavliadSyntax: | |
| return "Invalid Syntax" | |
| except InvalidTokens: | |
| return "Invalid Tokens" | |
| except Exception: | |
| return "Something went wrong. Try again later" | |
| @dataclass | |
| class Calculate: | |
| expression: str = '' | |
| def main(self): | |
| lexer = Tokeniser(self.expression) | |
| try: | |
| lexer.tokenise() | |
| except InvalidTokens: | |
| return "Invalid Tokens" | |
| except Exception: | |
| return "Something went wrong. Try again later" | |
| tokens = lexer.tokens | |
| parser = Parser(tokens=tokens) | |
| value = parser.main() | |
| return value | |
| if __name__ == "__main__": | |
| o = Tokeniser('(5)+(2)+(+(-2))') | |
| o.tokenise() | |
| o2 = Parser(o.tokens) | |
| v = o2.main() | |
| print(v) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment