def _pop_arg(self): while self._op_stack: if self._op_stack[-1] == '(' and mathfunc.is_func( self._op_stack[-2]): break else: self._pop_out()
def _pop_paren(self): # Pop everything until a left parenthesis is encountered while self._op_stack and self._op_stack[-1] != '(': self._pop_out() if not self._op_stack: raise ValueError('Mismatched parentheses') # Pop the '(' if self._op_stack[-1] == '(': self._op_stack.pop() # If there is a function at the top pop it if mathfunc.is_func(self._op_stack[-1]): self._pop_out()
def evaluate(self, tokens=None) -> float: self.tokens = tokens if tokens else self.tokens if not self.tokens: raise ValueError('Nothing to evaluate') self.clear() # Iterate over all the tokens for token in self.tokens: # A number if _num_regex.match(str(token)): self._numbers.append(float(token)) # An operator elif len(token) == 1: n1 = None n2 = None result = None # Regular binary operators if token in '+-*/^%': n2 = float(self._numbers.pop()) n1 = float(self._numbers.pop()) if token == '+': result = n1 + n2 elif token == '-': result = n1 - n2 elif token == '*': result = n1 * n2 elif token == '/': if n2 == 0: raise ValueError( 'Division by zero is sadly impossible, sorry.') result = n1 / n2 else: result = n1 % n2 # Negation elif token == 'n': result = -float(self._numbers.pop()) else: raise ValueError( f'Invalid token "{token}" during evaluation') self._numbers.append(float(result)) # A function elif self._func_regex.match(str(token)): match = self._func_regex.match(str(token)) name = match[1] arg_num = int(match[2]) # Check if the function exists and call it providing arguments if mathfunc.is_func(name): args = [self._numbers.pop() for i in range(arg_num)] result = mathfunc.func(name, args) self._numbers.append(result) else: raise ValueError( f'Invalid function "{name}" during evaluation') else: raise ValueError(f'Invalid token "{token}" during evaluation') result_num = float(self._numbers.pop()) self.result = int( result_num) if result_num.is_integer() else result_num return self.result
def convert(self, expression: str = None) -> list: self.expression = expression if expression else self.expression if not self.expression: raise ValueError('The expression to convert is empty') self.clear() self._prepare() # Iterate over all the tokens for token in self._tokens: # The token is a number if _num_regex.match(token): if self._expected(Converter.TokenType.NUM): self.converted.append(str(token)) # Add to output self._details_list.append(str(token)) # Add to details self._prev_token = Converter.TokenType.NUM # The token can be an operator or e elif len(token) == 1: # Regular operators if token in '+*/^%' and self._expected( Converter.TokenType.OPER): self._add_operator(str(token)) # Add the operator self._details_list.append( f' {str(token)} ') # Add it to the details self._prev_token = Converter.TokenType.OPER # Difference or negation elif token == '-': # Binary operator if self._expected(Converter.TokenType.OPER): self._add_operator(str(token)) self._details_list.append(f' {str(token)} ') self._prev_token = Converter.TokenType.OPER # Negation elif self._expected(Converter.TokenType.NEG): self._add_operator(_NEGATION) self._details_list.append('-') self._prev_token = Converter.TokenType.NEG else: raise ValueError( f'Unexpected token "{token}" during parsing') # Left parenthesis elif token == '(' and self._expected( Converter.TokenType.L_PAR): self._op_stack.append(token) self._details_list.append(token) self._prev_token = Converter.TokenType.L_PAR # Right parenthesis elif token == ')' and self._expected( Converter.TokenType.R_PAR): self._pop_paren() self._details_list.append(str(token)) self._prev_token = Converter.TokenType.R_PAR # Comma elif token == ',' and self._expected(Converter.TokenType.OPER): self._pop_arg() # Pop the previous sub-expression self._arg_c[ -1] += 1 # Increment the argument counter for the current function self._details_list.append(str(token) + ' ') self._prev_token = Converter.TokenType.OPER # Number e elif self._e_regex.match(token) and self._expected( Converter.TokenType.NUM): self.converted.append(str(math.e)) self._details_list.append(str(math.e)) self._prev_token = Converter.TokenType.NUM # The token is a die roll elif _dice_regex.match(token) and self._expected( Converter.TokenType.NUM): r = dice.Roller(token) self.converted.append(str(r.roll())) self._details_list.append(r.details) self._prev_token = Converter.TokenType.NUM # The token is a function elif self._func_regex.match(token) and self._expected( Converter.TokenType.FUNC) and mathfunc.is_func( token.lower()): self._add_function(token.lower()) self._details_list.append(token.lower()) self._prev_token = Converter.TokenType.FUNC # The token is pi elif self._pi_regex.match(token): self.converted.append(str(math.pi)) self._details_list.append(str(math.pi)) self._prev_token = Converter.TokenType.NUM else: raise ValueError(f'Unexpected token "{token}" during parsing') # Check if the last token was a number or right parenthesis if self._prev_token not in (Converter.TokenType.NUM, Converter.TokenType.R_PAR): raise ValueError( f'Unexpected token "{token}" at the end of the expression') # Pop the rest of the stack and check if parentheses mismatched while self._op_stack: if self._op_stack[-1] in ('(', ')'): raise ValueError('Mismatched parentheses') self._pop_out() self.details = ''.join(self._details_list) return self.converted
def _pop_out(self): if mathfunc.is_func(self._op_stack[-1]): self._op_stack[-1] += str(self._arg_c.pop( )) # Add the number of arguments to the function's name self.converted.append(self._op_stack.pop())