From 73af3adb347641c558da2c5c76088d3caffd646f Mon Sep 17 00:00:00 2001 From: Julian Daube Date: Mon, 1 Jul 2019 13:08:10 +0200 Subject: [PATCH] parser: base parser on reader; reader: add a way to get the current progress --- gds/parser.py | 136 +++++++++++++++++++++++++++++++------------------- gds/reader.py | 34 ++++++++++++- 2 files changed, 117 insertions(+), 53 deletions(-) diff --git a/gds/parser.py b/gds/parser.py index 8b3362e..0d2556c 100644 --- a/gds/parser.py +++ b/gds/parser.py @@ -17,6 +17,7 @@ class errors(Enum): EXPECTED_TEXTTYPE = "expected TEXTTYPE" EXPECTED_STRING = "textelement is missing text" EXPECTED_BOXTYPE = "expected BOXTYPE" + EXPECTED_SNAME = "expected SNAME" INVALID_PATHTYPE = "pathtype must be in range [0,2]" class Warnings(Enum): @@ -25,29 +26,30 @@ class Warnings(Enum): class ParserError(Exception): pass -class Parser(object): +class Parser(Reader): # parser stack _token = None - last_token = None + progress_callback = None # parser state Library = Library() current_structure = None - def __init__(self, reader): - self.reader = reader - + def __init__(self, file, progress_callback=None): + super(Parser, self).__init__(file) + + self.progress_callback = progress_callback + @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() + self.token = self.read_record() if not self.token and throw: raise ParserError(errors.UNEXPECTED_EOF) @@ -62,19 +64,19 @@ class Parser(object): if not self.next_token() or self.token.ident != Records.HEADER: raise ParserError(errors.EXPECTED_HEADER) - self.Library.version = self.reader.read_short() + 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.reader.read_date() - self.Library.last_access = self.reader.read_date() + 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.reader.read_ascii(self.token.len) + self.Library.name = self.read_ascii(self.token.len) # read optional records while self.next_token(): @@ -83,14 +85,14 @@ class Parser(object): self.token.ident == Records.ATTRTABLE or \ self.token.ident == Records.GENERATIONS: # skip these records - self.reader.skip(self.token.len) + self.skip(self.token.len) elif self.token.ident == Records.FORMAT: - self.reader.skip(self.token.len) + self.skip(self.token.len) # look for optional mask while self.next_token().ident == Records.MASK: - self.reader.skip(self.token.len) + self.skip(self.token.len) if self.token.ident != Records.ENDMASK: raise ParserError(errors.EXPECTED_ENDMASK) @@ -103,29 +105,33 @@ class Parser(object): 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() + 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.reader.read_date() - self.structure.last_access = self.reader.read_date() + 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.reader.read_ascii(self.token.len) + self.structure.name = self.read_ascii(self.token.len) while self.next_token() and self.token.ident != Records.ENDSTR: if self.token.ident == Records.BOUNDARY: @@ -137,7 +143,10 @@ class Parser(object): elif self.token.ident == Records.SREF: self.parse_sref() else: - self.reader.skip(self.token.len) + self.skip(self.token.len) + + if self.progress_callback: + self.progress_callback(self) self.Library.structures[self.structure.name] = self.structure self.structure = None @@ -146,7 +155,7 @@ class Parser(object): def parse_element(self, element): if self.token.ident == Records.ELFLAGS: - element.elflags = self.reader.read_short() + element.elflags = self.read_short() self.next_token(True) if self.token.ident == Records.PLEX: @@ -162,17 +171,17 @@ class Parser(object): self.parse_element(element) if self.token.ident == Records.LAYER: - element.layer = self.reader.read_short() + element.layer = self.read_short() if not self.next_token() or self.token.ident != Records.DATATYPE: raise ParserError(errors.EXPECTED_DATATYPE) - element.datatype = self.reader.read_short() + element.datatype = self.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) + element.points = self.read_coords(self.token.len) if not self.next_token() or self.token.ident != Records.ENDEL: raise ParserError(errors.EXPECTED_ENDEL) @@ -189,17 +198,17 @@ class Parser(object): if self.token.ident != Records.LAYER: raise ParserError(errors.EXPECTED_LAYER) - element.layer = self.reader.read_short() + element.layer = self.read_short() if not self.next_token() or self.token.ident != Records.DATATYPE: raise ParserError(errors.EXPECTED_DATATYPE) - element.datatype = self.reader.read_short() + element.datatype = self.read_short() self.next_token(True) if self.token.ident == Records.PATHTYPE: - pathtype = self.reader.read_short() + pathtype = self.read_short() if pathtype < 0 or pathtype > 4 or pathtype == 3: raise ParserError(errors.INVALID_PATHTYPE) @@ -207,17 +216,17 @@ class Parser(object): self.next_token(True) if self.token.ident == Records.WIDTH: - element.width = self.reader.read_int() + element.width = self.read_int() self.next_token(True) if self.token.ident == Records.BGNEXTN: - element.extendEnd[0] = self.reader.read_int() + 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.reader.read_int() + element.extendEnd[1] = self.read_int() self.next_token(True) elif element.pathStyle == Path.Styles.OFFSET_ENDS: element.extendEnd[1] = element.width/2 @@ -225,7 +234,7 @@ class Parser(object): if self.token.ident != Records.XY: raise ParserError(errors.EXPECTED_POINTS) - element.points = self.reader.read_coords(self.token.len) + element.points = self.read_coords(self.token.len) if not self.next_token() or self.token.ident != Records.ENDEL: raise ParserError(errors.EXPECTED_ENDEL) @@ -242,17 +251,17 @@ class Parser(object): if not self.token.ident == Records.LAYER: raise ParserError(errors.EXPECTED_LAYER) - element.layer = self.reader.read_short() + element.layer = self.read_short() if not self.next_token() or self.token.ident != Records.TEXTTYPE: raise ParserError(errors.EXPECTED_TEXTTYPE) - element.datatype = self.reader.read_short() + element.datatype = self.read_short() self.next_token(True) if self.token.ident == Records.PRESENTATION: - temp = self.reader.read_short() + temp = self.read_short() element.fontnumber = (temp>>10)&0x03 element.verticalJustification = Text.VJust((temp>>12)&0x03) element.horizontalJustification = Text.HJust((temp>>14)&0X03) @@ -260,11 +269,11 @@ class Parser(object): self.next_token(True) if self.token.ident == Records.PATHTYPE: - element.pathStype = Path.Styles(self.reader.read_short()) + element.pathStype = Path.Styles(self.read_short()) self.next_token(True) if self.token.ident == Records.WIDTH: - element.pathWidth = self.reader.read_short() + element.pathWidth = self.read_short() self.next_token(True) self.parse_strans(element.transformation) @@ -272,15 +281,15 @@ class Parser(object): if self.token.ident != Records.XY: raise ParserError(errors.EXPECTED_POINT) - element.point = self.reader.read_coord() + element.point = self.read_coord() # skip potential array (if given) - self.reader.skip(self.token.len - 8) + 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.reader.read_ascii(self.token.len) + element.string = self.read_ascii(self.token.len) if not self.next_token() or self.token.ident != Records.ENDEL: raise ParserError(errors.EXPECTED_ENDEL) @@ -291,7 +300,7 @@ class Parser(object): if self.token.ident != Records.STRANS: return - flags = self.reader.read_short() + flags = self.read_short() if flags & 0x01: trans.mirror_x = True @@ -304,11 +313,11 @@ class Parser(object): self.next_token(True) if self.token.ident == Records.MAG: - trans.zoom = self.reader.read_double() + trans.zoom = self.read_double() self.next_token(True) if self.token.ident == Records.ANGLE: - trans.rotation = self.reader.read_double() + trans.rotation = self.read_double() self.next_token(True) def parse_box(self): @@ -321,17 +330,17 @@ class Parser(object): if self.token.ident != Records.LAYER: raise ParserError(errors.EXPECTED_LAYER) - element.layer = self.reader.read_short() + element.layer = self.read_short() if not self.next_token() or self.token.ident != Records.BOXTYPE: raise ParserError(errors.EXPECTED_BOXTYPE) - element.datatype = self.reader.read_short() + element.datatype = self.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) + element.points = self.read_coords(self.token.len) if not self.next_token() or self.token.ident != Records.ENDEL: raise ParserError(errors.EXPECTED_ENDEL) @@ -339,12 +348,35 @@ class Parser(object): 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) + 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.point = 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_file(file, progress_func=None): + parser = Parser(file, progress_callback=progress_func) parser.parse_lib() return parser.Library diff --git a/gds/reader.py b/gds/reader.py index b24e617..c375b1d 100644 --- a/gds/reader.py +++ b/gds/reader.py @@ -1,10 +1,42 @@ from datetime import datetime from .record import * +import abc + + +class ProgressGetter(object, metaclass=abc.ABCMeta): + @abc.abstractproperty + def current(self): + pass + @abc.abstractproperty + def total(self): + pass + + @abc.abstractmethod + def progress(self): + raise NotImplementedError("getting the progess is not implemented") + +class Reader(ProgressGetter): + # statistics + total = 0 + @property + def current(self): + return float(self.stream.tell()) + + # returns the read progress in percent + def progress(self): + if self.total <= 0: + return 1.0 + + return self.current / self.total -class Reader(object): def __init__(self, file): self.stream = file + # find file size + self.stream.seek(0, 2) + self.total = self.stream.tell() + self.stream.seek(0, 0) + def skip(self, n): self.stream.read(n)