diff --git a/src/sgfParser/__init__.py b/src/sgfParser/__init__.py --- a/src/sgfParser/__init__.py +++ b/src/sgfParser/__init__.py @@ -4,8 +4,24 @@ def skipWhitespace(s,start): return i +def lineNumber(s,i): + k=0 + r=0 + for (r,line) in enumerate(s.splitlines(True)): + k+=len(line) + if k>=i: break + return r+1 + + class ParserError(Exception): - def __init__(self,line,col,message): - self.line=line - self.col=col + def __init__(self,message,s,i): + # self.line=line + # self.col=col + # !! check for i=len(s): return + elif not GameTree.fits(s,i): + raise ParserError("expected a GameTree starting with '('",s,i) + while GameTree.fits(s,i): + i,x=GameTree.create(s,i) self.gameTrees.append(x) - i,x=GameTree.create(s,i) + if i=len(s) or s[i]!="(": - # print("error when parsing GameTree") - return (start,None) - i,x=Node.create(s,i+1) - if x is None: - # print("error when parsing GameTree") - return (i,None) + + i=skipWhitespace(s,start+1) + if not Node.fits(s,i): + raise ParserError("expected a Node starting with ';'",s,i) + y=None - while x is not None: + while Node.fits(s,i): + i,x=Node.create(s,i) res.nodes.append(x) if y: y.addChild(x) x.setParent(y) y=x i=skipWhitespace(s,i) - i,x=Node.create(s, i) - i=skipWhitespace(s,i) - i,x=GameTree.create(s,i) - while x is not None: + + while GameTree.fits(s,i): + i,x=GameTree.create(s,i) res.branches.append(x) subroot=x.getNode(0) - if subroot: - subroot.setParent(y) + subroot.setParent(y) if y: y.addChild(subroot) i=skipWhitespace(s,i) - i,x=GameTree.create(s,i) - if s[i]!=")": - # print("error when parsing GameTree") - return (i,None) - return (i+1,res) + if i>=len(s) or s[i]!=")": + raise ParserError("expected end of the GameTree marked by ')'",s,i) + i=skipWhitespace(s,i+1) + return (i,res) ## Expand multiple games into distinct GameTrees and yield each. def listGames(self): diff --git a/src/sgfParser/node.py b/src/sgfParser/node.py --- a/src/sgfParser/node.py +++ b/src/sgfParser/node.py @@ -1,4 +1,4 @@ -from . import skipWhitespace, ParserError +from . import skipWhitespace, ParserWarning from .property import Property, GAME_INFO @@ -9,21 +9,23 @@ class Node: self.children=[] @staticmethod + def fits(s,i): + return i=len(s) or s[i]!=":": + raise ParserError("a composed property value separated by ':' expected",s,i) i,b=vTypeB(s,i+1) - if b==None: return start,None return (i,Composed(a,b)) return f @@ -75,7 +94,7 @@ def compose(vTypeA,vTypeB): def number(s,start): r=re.compile(r"(\+|-|)\d+") m=r.match(s,start) - if m is None: return (start,None) + if m is None: raise ParserError("expected a number matching '(\+|-|)\d+'",s,start) res=int(m.group(0)) return (m.end(),res) @@ -83,7 +102,7 @@ def number(s,start): def real(s,start): r=re.compile(r"(\+|-|)\d+(\.\d+)?") m=r.match(s,start) - if m is None: return (start,None) + if m is None: raise ParserError("expected a real number matching '(\+|-|)\d+(\.\d+)?'",s,start) res=float(m.group(0)) return (m.end(),res) @@ -91,7 +110,7 @@ def real(s,start): def double(s,start): r=re.compile(r"1|2") m=r.match(s,start) - if m is None: return (start,None) + if m is None: raise ParserError("expected a double value, either '1' or '2'",s,start) res=int(m.group(0)) return (m.end(),res) @@ -99,7 +118,7 @@ def double(s,start): def color(s,start): r=re.compile(r"B|W") m=r.match(s,start) - if m is None: return (start,None) + if m is None: raise ParserError("expected a color value, either 'B' or 'W'",s,start) return (m.end(),m.group(0)) @@ -108,6 +127,7 @@ def text(simple=True,composed=False): res="" esc=False lastC="" + i=start for i,c in enumerate(s[start:],start): if esc: if c!="\n" and c!="\r": res+=c @@ -144,7 +164,7 @@ def anything(s,start): def point(s,start): r=re.compile(r"[a-zA-Z]{2}|") # !! limit to board size m=r.match(s,start) - if m is None: return (start,None) + if m is None: raise ParserError("expected a point value matching '[a-zA-Z]{2}|'",s,start) if m.group(0)=="": # pass, !! tt return (m.end(),tuple()) col=m.group(0)[0] @@ -158,29 +178,31 @@ stone=point class Property: + identRegexp=re.compile(r"[A-Z]+") + def __init__(self): self.name="" self.value="" @staticmethod + def fits(s,i): + return i