#!/usr/bin/python3 from collections import namedtuple from heapq import heappush, heappop import itertools # A simple priority queue based on the heapq class. class PQueue(object): REMOVED = '' # placeholder for a removed task def __init__(self): self.pq = [] # list of entries arranged in a heap self.entry_finder = {} # mapping of tasks to entries self.counter = itertools.count() # unique sequence count self.size = 0 def push(self, task, priority=0): 'Add a new task or update the priority of an existing task' if task in self.entry_finder: self.remove(task) else: self.size += 1 entry = [priority, next(self.counter), task] self.entry_finder[task] = entry heappush(self.pq, entry) def remove(self, task): 'Mark an existing task as REMOVED. Raise KeyError if not found.' entry = self.entry_finder.pop(task) entry[-1] = self.REMOVED self.size -= 1 def pop(self): 'Remove and return the lowest priority task. Raise KeyError if empty.' while self.pq: priority, count, task = heappop(self.pq) if task is not self.REMOVED: del self.entry_finder[task] self.size -= 1 return task return None def __len__(self): return self.size # Stores a token's type and value, and optionally the position of the first # character in the lexed stream. class Token(namedtuple('Token', ['type', 'val', 'pos'])): __slots__ = () # Custom constructor to support default parameters. def __new__(cls, type, val='', pos=None): return super(Token, cls).__new__(cls, type, val, pos) def __str__(self): return self.val # Ignore position when comparing tokens. There is probably a cleaner way of # doing these. __eq__ = lambda x, y: x[0] == y[0] and x[1] == y[1] __ne__ = lambda x, y: x[0] != y[0] or x[1] != y[1] __lt__ = lambda x, y: tuple.__lt__(x[0:2], y[0:2]) __le__ = lambda x, y: tuple.__le__(x[0:2], y[0:2]) __ge__ = lambda x, y: tuple.__ge__(x[0:2], y[0:2]) __gt__ = lambda x, y: tuple.__gt__(x[0:2], y[0:2]) # Only hash token's value (we don't care about position, and types are # determined by values). def __hash__(self): return hash(self[1]) # Return [n]th line in [text]. def get_line(text, n): lines = text.split('\n') if n >= 0 and n < len(lines): return lines[n] return None # Indent each line in [text] by [indent] spaces. def indent(text, indent=2): return '\n'.join([' '*indent+line for line in text.split('\n')])