Skip to content

Instantly share code, notes, and snippets.

@ve-ron
Last active September 17, 2025 19:32
Show Gist options
  • Save ve-ron/4c2008d8e3d993eefcc8deaac5d73c6f to your computer and use it in GitHub Desktop.
Save ve-ron/4c2008d8e3d993eefcc8deaac5d73c6f to your computer and use it in GitHub Desktop.
A math calculator parser in python.
# 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