initial commit (work in progress)

python version of parser works, BUT
does not support AREF and SREF at the moment
This commit is contained in:
Julian Daube 2019-07-01 11:45:02 +02:00
commit 5d939157ed
9 changed files with 629 additions and 0 deletions

1
gds/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
__pycache__

7
gds/__init__.py Normal file
View File

@ -0,0 +1,7 @@
from .library import Library
from .structure import Structure
from .elements import *
from .record import *
from .reader import Reader
from .parser import *

76
gds/elements.py Normal file
View File

@ -0,0 +1,76 @@
from enum import Enum
class Transformation(object):
mirror_x = 0
absolute_rotation = 0
absolute_magnification = 0
zoom = 1
rotation = 0
class Element(object):
elflags = 0
plex = 0
datatype = 0
class Drawable(object):
layer = 0
class Boundary(Element, Drawable):
points = []
class Path(Element, Drawable):
class Styles(Enum):
SQUARE_ENDS = 0
ROUNDED_ENDS = 1
OFFSET_ENDS = 2
CUSTOM_END = 4
extendEnd = [0,0] # extend past start and end
width = 0
pathStyle = Styles.SQUARE_ENDS
points = []
class Text(Element, Drawable):
class VJust(Enum):
Top = 0
Middle = 1
Bottom = 2
class HJust(Enum):
Left = 0
Center = 1
Right = 2
# text info
string = ""
position = (0,0)
# presentation
fontnumber = 0
verticalJustification = VJust.Top
horizontalJustification = HJust.Left
# optional path info
pathStype = Path.Styles.SQUARE_ENDS
pathWidth = 0
transformation = Transformation()
class Box(Element, Drawable):
points = []
class SRef(Element):
Position = None
Structure = ""
transformation = Transformation()
# tree
Parent = None
Children = []

14
gds/library.py Normal file
View File

@ -0,0 +1,14 @@
from datetime import datetime
class Library(object):
version = 0
name = "NONAME"
last_access = datetime.now()
last_mod = datetime.now()
# unit setup
units_per_dbunit = 1
meters_per_unit = 1
structures = {}

352
gds/parser.py Normal file
View File

@ -0,0 +1,352 @@
from . import *
from enum import Enum
class errors(Enum):
UNEXPECTED_EOF = "unexpected EOF"
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"
INVALID_PATHTYPE = "pathtype must be in range [0,2]"
class Warnings(Enum):
EXPECTED_ENDLIB = "missing end of library"
class ParserError(Exception):
pass
class Parser(object):
# parser stack
_token = None
last_token = None
# parser state
Library = Library()
current_structure = None
def __init__(self, reader):
self.reader = reader
@property
def token(self):
return self._token
@token.setter
def token(self, t):
self.last_token = self._token
self._token = t
def next_token(self, throw=False):
self.token = self.reader.read_record()
if not self.token and throw:
raise ParserError(errors.UNEXPECTED_EOF)
# if self.token:
# print(self.token.ident.name)
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.reader.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.reader.read_date()
self.Library.last_access = self.reader.read_date()
if not self.next_token() or self.token.ident != Records.LIBNAME:
raise ParserError(errors.EXPECTED_LIBNAME)
self.Library.name = self.reader.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.reader.skip(self.token.len)
elif self.token.ident == Records.FORMAT:
self.reader.skip(self.token.len)
# look for optional mask
while self.next_token().ident == Records.MASK:
self.reader.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.reader.read_double()
self.Library.meters_per_unit = self.reader.read_double()
while self.next_token() and self.parse_structure():
pass
if self.token.ident != Records.ENDLIB:
print(Warnings.EXPECTED_ENDLIB.value)
def parse_structure(self):
self.structure = Structure()
if self.token.ident != Records.BGNSTR:
return False
self.structure.last_mod = self.reader.read_date()
self.structure.last_access = self.reader.read_date()
# read sname
if not self.next_token() or self.token.ident != Records.STRNAME:
raise ParserError(errors.EXPECTED_STRNAME)
self.structure.name = self.reader.read_ascii(self.token.len)
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()
else:
self.reader.skip(self.token.len)
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.reader.read_short()
self.next_token(True)
if self.token.ident == Records.PLEX:
element.plex = self.read_int()
self.next_token(True)
def parse_boundary(self):
element = Boundary()
if not self.next_token():
raise ParserError(errors.EXPECTED_LAYER)
self.parse_element(element)
if self.token.ident == Records.LAYER:
element.layer = self.reader.read_short()
if not self.next_token() or self.token.ident != Records.DATATYPE:
raise ParserError(errors.EXPECTED_DATATYPE)
element.datatype = self.reader.read_short()
if not self.next_token() or self.token.ident != Records.XY:
raise ParserError(errors.EXPECTED_POINTS)
element.points = self.reader.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()
if not self.next_token():
raise ParserError(errors.EXPECTED_LAYER)
self.parse_element(element)
if self.token.ident != Records.LAYER:
raise ParserError(errors.EXPECTED_LAYER)
element.layer = self.reader.read_short()
if not self.next_token() or self.token.ident != Records.DATATYPE:
raise ParserError(errors.EXPECTED_DATATYPE)
element.datatype = self.reader.read_short()
self.next_token(True)
if self.token.ident == Records.PATHTYPE:
pathtype = self.reader.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.reader.read_int()
self.next_token(True)
if self.token.ident == Records.BGNEXTN:
element.extendEnd[0] = self.reader.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.reader.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.reader.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()
if not self.next_token():
raise ParserError(errors.EXPECTED_LAYER)
self.parse_element(element)
if not self.token.ident == Records.LAYER:
raise ParserError(errors.EXPECTED_LAYER)
element.layer = self.reader.read_short()
if not self.next_token() or self.token.ident != Records.TEXTTYPE:
raise ParserError(errors.EXPECTED_TEXTTYPE)
element.datatype = self.reader.read_short()
self.next_token(True)
if self.token.ident == Records.PRESENTATION:
temp = self.reader.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.reader.read_short())
self.next_token(True)
if self.token.ident == Records.WIDTH:
element.pathWidth = self.reader.read_short()
self.next_token(True)
self.parse_strans(element.transformation)
if self.token.ident != Records.XY:
raise ParserError(errors.EXPECTED_POINT)
element.point = self.reader.read_coord()
# skip potential array (if given)
self.reader.skip(self.token.len - 8)
if not self.next_token() or self.token.ident != Records.STRING:
raise ParserError(errors.EXPECTED_STRING)
element.string = self.reader.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.reader.read_short()
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.reader.read_double()
self.next_token(True)
if self.token.ident == Records.ANGLE:
trans.rotation = self.reader.read_double()
self.next_token(True)
def parse_box(self):
element = Box()
if not self.next_token():
raise ParserError(errors.EXPECTED_LAYER)
self.parse_element(element)
if self.token.ident != Records.LAYER:
raise ParserError(errors.EXPECTED_LAYER)
element.layer = self.reader.read_short()
if not self.next_token() or self.token.ident != Records.BOXTYPE:
raise ParserError(errors.EXPECTED_BOXTYPE)
element.datatype = self.reader.read_short()
if not self.next_token() or self.token.ident != Records.XY:
raise ParserError(errors.EXPECTED_POINTS)
element.points = self.reader.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):
while self.next_token() and self.token.ident != Records.ENDEL:
self.reader.skip(self.token.len)
def parse_file(file):
r = Reader(file)
parser = Parser(r)
parser.parse_lib()
return parser.Library

97
gds/reader.py Normal file
View File

@ -0,0 +1,97 @@
from datetime import datetime
from .record import *
class Reader(object):
def __init__(self, file):
self.stream = file
def skip(self, n):
self.stream.read(n)
def read_int(self):
temp = self.stream.read(4)
if len(temp) != 4:
return None
return int(temp[3]) | int(temp[2]) << 8 | int(temp[1]) << 16 | int(temp[0]) << 24
def read_short(self):
temp = self.stream.read(2)
if len(temp) != 2:
return None
return int(temp[1]) | int(temp[0]) << 8
def read_double(self):
temp = self.stream.read(8)
if len(temp) != 8:
return None
result = 0
for i in temp:
if int(i) != 0:
# read double
result = 1
for j in range(1,8):
result += float(temp[8-j])/(2**(j*8))
exp = int(temp[0]) & 0x7F
exp -= 64
result *= 16**exp
if int(temp[0]) & 0x80:
result += -1
return result
# double is Zero
return 0
def read_ascii(self, len):
return self.stream.read(len).decode("ASCII").strip()
def read_record(self):
result = Record()
try:
result.len = self.read_short()
result.ident = Records(self.read_short())
except ValueError:
return None
result.len -= 4 # remove record header len
return result
def read_date(self):
# date
year = self.read_short()
month = self.read_short()
day = self.read_short()
# time
hour = self.read_short()
minute = self.read_short()
second = self.read_short()
return datetime(year=year, month=month, day=day, hour=hour, minute=minute, second=second)
def read_coord(self):
X = self.read_int()
Y = self.read_int()
return (X,Y)
def read_coords(self, len):
len /= 8
result = []
while len > 0:
point = self.read_coord()
if not point:
return None
result.append(point)
len -= 1
return result

52
gds/record.py Normal file
View File

@ -0,0 +1,52 @@
from enum import Enum
class Records(Enum):
UNKNOWN = 0x0000
HEADER = 0x0002
BGNLIB = 0x0102
LIBNAME = 0x0206
REFLIBS = 0x1F06
FONTS = 0x2006
ATTRTABLE = 0x2306
GENERATIONS = 0x2202
FORMAT = 0x3602
MASK = 0x3706
ENDMASKS = 0x3800
UNITS = 0x0305
ENDLIB = 0x0400
BGNSTR = 0x0502
STRNAME = 0x0606
ENDEL = 0x1100
ENDSTR = 0x0700
BOUNDARY = 0x0800
PATH = 0x0900
SREF = 0x0A00
AREF = 0x0B00
TEXT = 0x0C00
NODE = 0x1500
BOX = 0x2D00
ELFLAGS = 0x2601
PLEX = 0x2F03
LAYER = 0x0D02
DATATYPE = 0x0E02
XY = 0x1003
PATHTYPE = 0x2102
WIDTH = 0x0F03
BGNEXTN = 0x3003
ENDEXTN = 0x3103
SNAME = 0x1206
STRANS = 0x1A01
MAG = 0x1B05
ANGLE = 0x1C05
COLROW = 0x1302
TEXTTYPE = 0x1602
PRESENTATION = 0x1701
NODETYPE = 0x2A02
BOXTYPE = 0x2E02
STRING = 0x1906
PROPATTR = 0x2B02
PROPVALUE = 0x2C06
class Record(object):
ident = Records.UNKNOWN
len = 0

10
gds/structure.py Normal file
View File

@ -0,0 +1,10 @@
from datetime import datetime
class Structure(object):
# metainfo
creation_date = datetime.now()
last_mod = datetime.now()
name = "NONAME"
# contains all the elements
elements = []

20
test.py Normal file
View File

@ -0,0 +1,20 @@
import gds
import sys
for arg in sys.argv[1:]:
f = open(arg, "rb")
try:
lib = gds.parse_file(f)
print("file version: {}".format(lib.version))
print("last access: {}".format(lib.last_access.isoformat()))
print("last modification: {}".format(lib.last_mod.isoformat()))
print("m/unit : {}".format(lib.meters_per_unit))
print("unit/dbunit : {}".format(lib.units_per_dbunit))
print("library name : {}".format(lib.name))
print("contains a total of {} structure(s)".format(len(lib.structures)))
except gds.ParserError as e:
print("parser error: {}".format(e))
finally:
f.close()