Created
April 7, 2026 11:31
-
-
Save theeluwin/456c59bfcf1cf528d526a9fb553cb643 to your computer and use it in GitHub Desktop.
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
| from ctypes import ( | |
| py_object, | |
| Array, | |
| ) | |
| from dataclasses import dataclass | |
| @dataclass | |
| class Token: | |
| tag: str | |
| value: str | |
| class Ast: | |
| pass | |
| @dataclass | |
| class AstNumber(Ast): | |
| def __repr__(self) -> str: | |
| return f"{self.value}" | |
| value: float | |
| @dataclass | |
| class AstUnaryOperation(Ast): | |
| def __repr__(self) -> str: | |
| return f"({self.operation}{self.expression})" | |
| operation: str | |
| expression: Ast | |
| @dataclass | |
| class AstBinaryOperation(Ast): | |
| def __repr__(self) -> str: | |
| return f"({self.left}{self.operation}{self.right})" | |
| left: Ast | |
| operation: str | |
| right: Ast | |
| class DynamicArray: | |
| _capacity: int | |
| _size: int | |
| _array: Array | |
| @staticmethod | |
| def allocate(capacity: int) -> Array: | |
| return (capacity * py_object)() | |
| def __init__(self) -> None: | |
| self._capacity = 1 | |
| self._size = 0 | |
| self._array = DynamicArray.allocate(self._capacity) | |
| def _resize(self, capacity: int) -> None: | |
| array = DynamicArray.allocate(capacity) | |
| for i in range(self._size): | |
| array[i] = self._array[i] | |
| self._array = array | |
| self._capacity = capacity | |
| def _expand(self) -> None: | |
| if self._size >= self._capacity: | |
| self._resize(self._capacity * 2) | |
| def __repr__(self) -> str: | |
| return f"[{', '.join(repr(self._array[i]) for i in range(self._size))}]" | |
| def __len__(self) -> int: | |
| return self._size | |
| def __getitem__(self, index: int) -> object: | |
| if index < 0: | |
| index += self._size | |
| if index < 0 or index >= self._size: | |
| raise IndexError | |
| return self._array[index] | |
| def append(self, value: object) -> None: | |
| self._expand() | |
| self._array[self._size] = value | |
| self._size += 1 | |
| def pop(self, index: int = -1) -> object: | |
| if self._size == 0: | |
| raise IndexError | |
| if index < 0: | |
| index += self._size | |
| if index < 0 or index >= self._size: | |
| raise IndexError | |
| value = self._array[index] | |
| for i in range(index, self._size - 1): | |
| self._array[i] = self._array[i + 1] | |
| self._array[self._size - 1] = None | |
| self._size -= 1 | |
| return value | |
| class Stack: | |
| def __init__(self) -> None: | |
| self._array = DynamicArray() | |
| def __len__(self) -> int: | |
| return len(self._array) | |
| def is_empty(self) -> bool: | |
| return len(self) == 0 | |
| def push(self, element: object) -> None: | |
| self._array.append(element) | |
| def pop(self) -> object: | |
| if self.is_empty(): | |
| raise IndexError | |
| return self._array.pop() | |
| def top(self) -> object: | |
| if self.is_empty(): | |
| raise IndexError | |
| return self._array[-1] | |
| class Queue: | |
| def __init__(self) -> None: | |
| self._stack_en = Stack() | |
| self._stack_de = Stack() | |
| def __len__(self) -> int: | |
| return len(self._stack_en) + len(self._stack_de) | |
| def _pour(self) -> None: | |
| while not self._stack_en.is_empty(): | |
| self._stack_de.push(self._stack_en.pop()) | |
| def is_empty(self) -> bool: | |
| return len(self) == 0 | |
| def enqueue(self, element: object) -> None: | |
| self._stack_en.push(element) | |
| def dequeue(self) -> object: | |
| if self.is_empty(): | |
| raise IndexError | |
| if self._stack_de.is_empty(): | |
| self._pour() | |
| return self._stack_de.pop() | |
| def front(self) -> object: | |
| if self.is_empty(): | |
| raise IndexError | |
| if self._stack_de.is_empty(): | |
| self._pour() | |
| return self._stack_de.top() | |
| class Lexer: | |
| def __init__(self, text: str) -> None: | |
| self._text = text | |
| self._position = 0 | |
| self._length = len(text) | |
| def _peek(self) -> str | None: | |
| if self._position < self._length: | |
| return self._text[self._position] | |
| return None | |
| def _advance(self) -> None: | |
| self._position += 1 | |
| def _skip(self) -> None: | |
| while True: | |
| character = self._peek() | |
| if character is None: | |
| return | |
| if character.isspace(): | |
| self._advance() | |
| else: | |
| break | |
| def _reduce(self, queue: Queue) -> str: | |
| value: list[str] = [] | |
| while not queue.is_empty(): | |
| value.append(str(queue.dequeue())) | |
| return ''.join(value) | |
| def _number(self) -> Token: | |
| has_dot = False | |
| queue = Queue() | |
| while True: | |
| character = self._peek() | |
| if character is None: | |
| break | |
| if character.isdigit(): | |
| queue.enqueue(character) | |
| self._advance() | |
| elif character == '.': | |
| if has_dot: | |
| raise SyntaxError | |
| has_dot = True | |
| queue.enqueue(character) | |
| self._advance() | |
| else: | |
| break | |
| return Token('NUMBER', self._reduce(queue)) | |
| def next(self) -> Token: | |
| self._skip() | |
| character = self._peek() | |
| if character is None: | |
| return Token('EOF', '') | |
| if character.isdigit() or character == '.': | |
| return self._number() | |
| if character in ('+', '-', '*', '/', '^'): | |
| self._advance() | |
| return Token('OPERATOR', character) | |
| if character == '(': | |
| self._advance() | |
| return Token('LEFT', '(') | |
| if character == ')': | |
| self._advance() | |
| return Token('RIGHT', ')') | |
| raise SyntaxError | |
| class Parser: | |
| def __init__(self, lexer: Lexer) -> None: | |
| self._lexer = lexer | |
| self._token = self._lexer.next() | |
| def _consume(self, tag: str) -> None: | |
| if self._token.tag == tag: | |
| self._token = self._lexer.next() | |
| else: | |
| raise SyntaxError | |
| def _parse_expression(self) -> Ast: | |
| node = self._parse_term() | |
| while self._token.tag == 'OPERATOR' and self._token.value in ('+', '-'): | |
| operator = self._token.value | |
| self._consume('OPERATOR') | |
| node = AstBinaryOperation(node, operator, self._parse_term()) | |
| return node | |
| def _parse_term(self) -> Ast: | |
| node = self._parse_power() | |
| while self._token.tag == 'OPERATOR' and self._token.value in ('*', '/'): | |
| operator = self._token.value | |
| self._consume('OPERATOR') | |
| node = AstBinaryOperation(node, operator, self._parse_power()) | |
| return node | |
| def _parse_power(self) -> Ast: | |
| node = self._parse_factor() | |
| if self._token.tag == 'OPERATOR' and self._token.value == '^': | |
| operator = self._token.value | |
| self._consume('OPERATOR') | |
| node = AstBinaryOperation(node, operator, self._parse_power()) | |
| return node | |
| def _parse_factor(self) -> Ast: | |
| if self._token.tag == 'NUMBER': | |
| value = self._token.value | |
| self._consume('NUMBER') | |
| return AstNumber(float(value)) | |
| if self._token.tag == 'OPERATOR' and self._token.value == '-': | |
| self._consume('OPERATOR') | |
| return AstUnaryOperation('n', self._parse_factor()) | |
| if self._token.tag == 'LEFT': | |
| self._consume('LEFT') | |
| node = self._parse_expression() | |
| self._consume('RIGHT') | |
| return node | |
| raise SyntaxError | |
| def parse(self) -> Ast: | |
| node = self._parse_expression() | |
| if self._token.tag != 'EOF': | |
| raise SyntaxError | |
| return node | |
| class Evaluator: | |
| def evaluate(self, node: Ast) -> float: | |
| match node: | |
| case AstNumber(value): | |
| return value | |
| case AstUnaryOperation(operation, expression): | |
| value = self.evaluate(expression) | |
| if operation == 'n': | |
| return -value | |
| raise SyntaxError | |
| case AstBinaryOperation(left, operation, right): | |
| left_value = self.evaluate(left) | |
| right_value = self.evaluate(right) | |
| match operation: | |
| case '+': | |
| return left_value + right_value | |
| case '-': | |
| return left_value - right_value | |
| case '*': | |
| return left_value * right_value | |
| case '/': | |
| return left_value / right_value | |
| case '^': | |
| return left_value ** right_value | |
| case _: | |
| raise SyntaxError | |
| case _: | |
| raise SyntaxError | |
| if __name__ == '__main__': | |
| lexer = Lexer('(-.1 + 2) * 3^2 / 4 - 1.01') | |
| parser = Parser(lexer) | |
| ast = parser.parse() | |
| evaluator = Evaluator() | |
| print(evaluator.evaluate(ast)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment