pygds/pygds/parser.py

417 lines
13 KiB
Python

from . import *
from enum import Enum
class errors(Enum):
UNEXPECTED_EOF = "unexpected EOF"
UNKNOWN_RECORD = "unknown record encountered"
EXPECTED_HEADER = "expected file header"
EXPECTED_BGNLIB = "expected beginning of library"
EXPECTED_LIBNAME = "Library name is missing"
EXPECTED_ENDMASK = "MASK records was not terminated by ENDMASK"
EXPECTED_STRNAME = "encountered nameless structure"
EXPECTED_UNITS = "no unit record in file"
EXPECTED_LAYER = "expected a layer record in element"
EXPECTED_POINTS = "expected coordinates for element"
EXPECTED_POINT = "expected coordinate for element"
EXPECTED_DATATYPE = "expected datatype"
EXPECTED_ENDEL = "end of element is missing"
EXPECTED_TEXTTYPE = "expected TEXTTYPE"
EXPECTED_STRING = "textelement is missing text"
EXPECTED_BOXTYPE = "expected BOXTYPE"
EXPECTED_SNAME = "expected SNAME"
EXPECTED_COLROW = "expected COLROW"
AREF_MISSING_POINTS = "aref needs three points"
INVALID_PATHTYPE = "pathtype must be in range [0,2]"
class Warnings(Enum):
EXPECTED_ENDLIB = "missing end of library"
class ParserError(Exception):
pass
class Parser(Reader):
def __init__(self, file, progress_callback=None):
super(Parser, self).__init__(file)
self._token = None
self.progress_callback = progress_callback
# parser state
self.Library = Library()
self.current_structure = None
@property
def token(self):
return self._token
@token.setter
def token(self, t):
self._token = t
def next_token(self, throw=False):
self.token = self.read_record()
if not self.token.len and throw:
raise ParserError(errors.UNEXPECTED_EOF)
if not isinstance(self.token.ident, Records):
raise ParserError("{}: {}".format(errors.UNKNOWN_RECORD.value, self.token.ident))
# for DEBUG
#if self.token:
# print(self.token.ident.name, self.token.len)
return self.token
def parse_lib(self):
# read header
if not self.next_token() or self.token.ident != Records.HEADER:
raise ParserError(errors.EXPECTED_HEADER)
self.Library.version = self.read_short()
# must see BGNLIB
if not self.next_token() or self.token.ident != Records.BGNLIB:
raise ParserError(errors.EXPECTED_BGNLIB)
self.Library.last_mod = self.read_date()
self.Library.last_access = self.read_date()
if not self.next_token() or self.token.ident != Records.LIBNAME:
raise ParserError(errors.EXPECTED_LIBNAME)
self.Library.name = self.read_ascii(self.token.len)
# read optional records
while self.next_token():
if self.token.ident == Records.REFLIBS or \
self.token.ident == Records.FONTS or \
self.token.ident == Records.ATTRTABLE or \
self.token.ident == Records.GENERATIONS:
# skip these records
self.skip(self.token.len)
elif self.token.ident == Records.FORMAT:
self.skip(self.token.len)
# look for optional mask
while self.next_token().ident == Records.MASK:
self.skip(self.token.len)
if self.token.ident != Records.ENDMASK:
raise ParserError(errors.EXPECTED_ENDMASK)
else:
# continue
break
if self.token.ident != Records.UNITS:
raise ParserError(errors.EXPECTED_UNITS)
# read units
self.Library.units_per_dbunit = self.read_double()
self.Library.meters_per_unit = self.read_double()
while self.next_token() and self.parse_structure():
pass
if self.token.ident != Records.ENDLIB:
print(Warnings.EXPECTED_ENDLIB.value)
# tell callback that the process completed
if self.progress_callback:
self.progress_callback(self)
def parse_structure(self):
self.structure = Structure()
if self.token.ident != Records.BGNSTR:
return False
self.structure.last_mod = self.read_date()
self.structure.last_access = self.read_date()
# read sname
if not self.next_token() or self.token.ident != Records.STRNAME:
raise ParserError(errors.EXPECTED_STRNAME)
self.structure.name = self.read_ascii(self.token.len)
print("structure")
while self.next_token() and self.token.ident != Records.ENDSTR:
if self.token.ident == Records.BOUNDARY:
self.parse_boundary()
elif self.token.ident == Records.PATH:
self.parse_path()
elif self.token.ident == Records.TEXT:
self.parse_text()
elif self.token.ident == Records.SREF:
self.parse_sref()
elif self.token.ident == Records.AREF:
self.parse_aref()
else:
self.skip(self.token.len)
if self.progress_callback:
self.progress_callback(self)
self.Library.structures[self.structure.name] = self.structure
self.structure = None
return True
def parse_element(self, element):
if self.token.ident == Records.ELFLAGS:
element.elflags = self.read_short()
self.next_token(True)
if self.token.ident == Records.PLEX:
element.plex = self.read_int()
self.next_token(True)
def parse_layer(self, element):
if self.token.ident != Records.LAYER:
raise ParserError(errors.EXPECTED_LAYER)
element.layer = self.read_ushort()
def parse_boundary(self):
element = Boundary()
self.next_token(True)
self.parse_element(element)
self.parse_layer(element)
if not self.next_token() or self.token.ident != Records.DATATYPE:
raise ParserError(errors.EXPECTED_DATATYPE)
element.datatype = self.read_short()
if not self.next_token() or self.token.ident != Records.XY:
raise ParserError(errors.EXPECTED_POINTS)
element.points = self.read_coords(self.token.len)
if not self.next_token() or self.token.ident != Records.ENDEL:
raise ParserError(errors.EXPECTED_ENDEL)
self.structure.elements.append(element)
def parse_path(self):
element = Path()
self.next_token(True)
self.parse_element(element)
self.parse_layer(element)
if not self.next_token() or self.token.ident != Records.DATATYPE:
raise ParserError(errors.EXPECTED_DATATYPE)
element.datatype = self.read_short()
self.next_token(True)
if self.token.ident == Records.PATHTYPE:
pathtype = self.read_short()
if pathtype < 0 or pathtype > 4 or pathtype == 3:
raise ParserError(errors.INVALID_PATHTYPE)
element.pathStyle = Path.Styles(pathtype)
self.next_token(True)
if self.token.ident == Records.WIDTH:
element.width = self.read_int()
self.next_token(True)
if self.token.ident == Records.BGNEXTN:
element.extendEnd[0] = self.read_int()
self.next_token(True)
elif element.pathStyle == Path.Styles.OFFSET_ENDS:
element.extendEnd[0] = element.width/2
if self.token.ident == Records.ENDEXTN:
element.extendEnd[1] = self.read_int()
self.next_token(True)
elif element.pathStyle == Path.Styles.OFFSET_ENDS:
element.extendEnd[1] = element.width/2
if self.token.ident != Records.XY:
raise ParserError(errors.EXPECTED_POINTS)
element.points = self.read_coords(self.token.len)
if not self.next_token() or self.token.ident != Records.ENDEL:
raise ParserError(errors.EXPECTED_ENDEL)
self.structure.elements.append(element)
def parse_text(self):
element = Text()
self.next_token(True)
self.parse_element(element)
self.parse_layer(element)
if not self.next_token() or self.token.ident != Records.TEXTTYPE:
raise ParserError(errors.EXPECTED_TEXTTYPE)
element.datatype = self.read_short()
self.next_token(True)
if self.token.ident == Records.PRESENTATION:
temp = self.read_short()
element.fontnumber = (temp>>10)&0x03
element.verticalJustification = Text.VJust((temp>>12)&0x03)
element.horizontalJustification = Text.HJust((temp>>14)&0X03)
self.next_token(True)
if self.token.ident == Records.PATHTYPE:
element.pathStype = Path.Styles(self.read_short())
self.next_token(True)
if self.token.ident == Records.WIDTH:
if self.token.len == 2:
element.pathWidth = self.read_short()
elif self.token.len == 4:
element.pathWidth = self.read_int()
self.next_token(True)
self.parse_strans(element.transformation)
if self.token.ident != Records.XY:
raise ParserError(errors.EXPECTED_POINT)
element.point = self.read_coord()
# skip potential array (if given)
self.skip(self.token.len - 8)
if not self.next_token() or self.token.ident != Records.STRING:
raise ParserError(errors.EXPECTED_STRING)
element.string = self.read_ascii(self.token.len)
if not self.next_token() or self.token.ident != Records.ENDEL:
raise ParserError(errors.EXPECTED_ENDEL)
self.structure.elements.append(element)
def parse_strans(self, trans:Transformation):
if self.token.ident != Records.STRANS:
return
flags = self.read_ushort()
if flags & 0x01:
trans.mirror_x = True
if flags & 0x2000:
trans.absolute_magnification = True
if flags & 0x4000:
trans.absolute_rotation = True
self.next_token(True)
if self.token.ident == Records.MAG:
trans.zoom = self.read_double()
self.next_token(True)
if self.token.ident == Records.ANGLE:
trans.rotation = self.read_double()
self.next_token(True)
def parse_box(self):
element = Box()
self.next_token(True)
self.parse_element(element)
self.parse_layer(element)
if not self.next_token() or self.token.ident != Records.BOXTYPE:
raise ParserError(errors.EXPECTED_BOXTYPE)
element.datatype = self.read_short()
if not self.next_token() or self.token.ident != Records.XY:
raise ParserError(errors.EXPECTED_POINTS)
element.points = self.read_coords(self.token.len)
if not self.next_token() or self.token.ident != Records.ENDEL:
raise ParserError(errors.EXPECTED_ENDEL)
self.structure.elements.append(element)
def parse_sref(self):
element = SRef()
element.parent = self.structure
if not self.next_token():
raise ParserError(errors.EXPECTED_SNAME)
self.parse_element(element)
if self.token.ident != Records.SNAME:
raise ParserError(errors.EXPECTED_SNAME)
element.structure = self.read_ascii(self.token.len)
self.next_token(True)
self.parse_strans(element.transformation)
if self.token.ident != Records.XY:
raise ParserError(errors.EXPECTED_POINT)
element.position = self.read_coord()
self.skip(self.token.len - 8)
if not self.next_token() or self.token.ident != Records.ENDEL:
raise ParserError(errors.EXPECTED_ENDEL)
self.structure.references.append(element)
def parse_aref(self):
element = ARef()
element.parent = self.structure
if not self.next_token():
raise ParserError(errors.EXPECTED_SNAME)
self.parse_element(element)
if self.token.ident != Records.SNAME:
raise ParserError(errors.EXPECTED_SNAME)
element.structure = self.read_ascii(self.token.len)
self.next_token(True)
self.parse_strans(element.transformation)
if self.token.ident != Records.COLROW:
raise ParserError(errors.EXPECTED_COLROW)
element.size = (self.read_short(), self.read_short())
if not self.next_token() or self.token.ident != Records.XY:
raise ParserError(errors.EXPECTED_POINT)
if self.token.len != 3*8:
raise ParserError(errors.AREF_MISSING_POINTS)
element.position = self.read_coord()
element.bounds[0] = self.read_coord()
element.bounds[1] = self.read_coord()
if not self.next_token() or self.token.ident != Records.ENDEL:
raise ParserError(errors.EXPECTED_ENDEL)
self.structure.references.append(element)
def parse_file(file, progress_func=None):
parser = Parser(file, progress_callback=progress_func)
parser.parse_lib()
return parser.Library