Skip to content

Instantly share code, notes, and snippets.

@theeluwin
Created April 7, 2026 11:31
Show Gist options
  • Select an option

  • Save theeluwin/456c59bfcf1cf528d526a9fb553cb643 to your computer and use it in GitHub Desktop.

Select an option

Save theeluwin/456c59bfcf1cf528d526a9fb553cb643 to your computer and use it in GitHub Desktop.
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