text: """
  Utilities for SDF file parsing to determine cell timings
  """
  import sys
  
  
  class SDFData:
      def __init__(self):
          self.cells = {}
  
  class Delay:
      def __init__(self, minv, typv, maxv):
          self.minv = minv
          self.typv = typv
          self.maxv = maxv
  
  
  class IOPath:
      def __init__(self, from_pin, to_pin, rising, falling):
          self.from_pin = from_pin
          self.to_pin = to_pin
          self.rising = rising
          self.falling = falling
  
  
  class SetupHoldCheck:
      def __init__(self, pin, clock, setup, hold):
          self.pin = pin
          self.clock = clock
          self.setup = setup
          self.hold = hold
  
  
  class WidthCheck:
      def __init__(self, clock, width):
          self.clock = clock
          self.width = width
  
  
  class Interconnect:
      def __init__(self, from_net, to_net, rising, falling):
          self.from_net = from_net
          self.to_net = to_net
          self.rising = rising
          self.falling = falling
  
  
  class CellData:
      def __init__(self, celltype, inst):
          self.type = celltype
          self.inst = inst
          self.entries = []
          self.interconnect = {}
  
  
  def parse_sexpr(stream):
      content = []
      buffer = ""
      instr = False
      while True:
          c = stream.read(1)
          assert c != "", "unexpected end of file"
          if instr:
              if c == '"':
                  instr = False
              else:
                  buffer += c
          else:
              if c == '(':
                  content.append(parse_sexpr(stream))
              elif c == ')':
                  if buffer != "":
                      content.append(buffer)
                  return content
              elif c.isspace():
                  if buffer != "":
                      content.append(buffer)
                  buffer = ""
              elif c == '"':
                  instr = True
              else:
                  buffer += c
  
  
  def parse_sexpr_file(filename):
      with open(filename, 'r') as f:
          c = f.read(1)
          while c != '(':
              assert c == ' ' or c == '\n' or c == '\t'
              c = f.read(1)
          return parse_sexpr(f)
  
  
  def parse_delay(delay):
      sp = [float(x) if x != '' else None for x in delay.split(":")]
      assert len(sp) == 3
      return Delay(sp[0], sp[1], sp[2])
  
  
  def parse_sdf_file(filename):
      sdata = parse_sexpr_file(filename)
      assert sdata[0] == "DELAYFILE"
      sdf = SDFData()
      for entry in sdata[1:]:
          if entry[0] != "CELL":
              continue
          assert entry[1][0] == "CELLTYPE"
          celltype = entry[1][1]
          assert entry[2][0] == "INSTANCE"
          if len(entry[2]) > 1:
              inst = entry[2][1]
          else:
              inst = "top"
          cell = CellData(celltype, inst)
          for subentry in entry[3:]:
              if subentry[0] == "DELAY":
                  assert subentry[1][0] == "ABSOLUTE"
                  for delay in subentry[1][1:]:
                      if delay[0] == "IOPATH":
                          cell.entries.append(
                              IOPath(delay[1], delay[2], parse_delay(delay[3][0]), parse_delay(delay[4][0])))
                      elif delay[0] == "INTERCONNECT":
                          cell.interconnect[(delay[1], delay[2])] = Interconnect(delay[1], delay[2],
                                                                                 parse_delay(delay[3][0]),
                                                                                 parse_delay(delay[4][0]))
              elif subentry[0] == "TIMINGCHECK":
                  for check in subentry[1:]:
                      if check[0] == "SETUPHOLD":
                          cell.entries.append(
                              SetupHoldCheck(check[1], check[2], parse_delay(check[3][0]), parse_delay(check[4][0])))
                      elif check[0] == "WIDTH":
                          cell.entries.append(WidthCheck(check[1], parse_delay(check[2][0])))
          sdf.cells[(celltype, inst)] = cell
      return sdf
  