diff options
-rw-r--r-- | example.sgf | 30 | ||||
-rw-r--r-- | sgf.g | 40 | ||||
-rw-r--r-- | sgfparser.py | 100 |
3 files changed, 170 insertions, 0 deletions
diff --git a/example.sgf b/example.sgf new file mode 100644 index 0000000..cfba1bd --- /dev/null +++ b/example.sgf @@ -0,0 +1,30 @@ +( +;GM[1]FF[3] +RU[Japanese]SZ[19]HA[0]KM[5.5] +PW[White] +PB[Black] +GN[Copyright goproblems.com] +DT[1999-07-21]TM[1800] +;C[Black to play and live.] +AW[bb][cb][cc][cd][de][df][cg][ch][dh][ai][bi][ci] +AB[ba][ab][ac][bc][bd][be][cf][bg][bh] +(;B[af];W[ah] +(;B[ce];W[ag] C[only one eye this way]) + +(;B[ag];W[ce]) +) + +(;B[ah];W[af] +(;B[ae];W[bf];B[ag];W[bf] +(;B[af];W[ce] C[oops! you can't take this stone]) + +(;B[ce];W[af];B[bg] C[RIGHT black plays under the stones and lives]) +) + +(;B[bf];W[ae]) +) + +(;B[ae];W[ag]) + +) + @@ -0,0 +1,40 @@ +parser SGF: + ignore: '\\s+' + + + ### Go-specific tokens: http://www.red-bean.com/sgf/go.html ### + token Point: '[a-zA-Z]{2}' + token Move: '[a-zA-Z]{2}' + token Stone: '[a-zA-Z]{2}' + + ### SGF Tokens as specified in http://www.red-bean.com/sgf/sgf4.html ### + + token PropID: '([a-z]*[A-Z]){1,2}[a-z]*' + token Number: '[+-]?[0-9]+' + token Real: '[+-]?[0-9]+(\\.[0-9]+)?' + token Color: '(B|W)' + token Text: '[^\\]]*' # In YAPPS, the longest matches take precedence, + # if they're both the same length, then the + # first one listed in the grammar is used. + + + rule GameTree: "\\(" {{ res = [] }} + (Node {{ res.append(Node) }})+ + (GameTree {{ res.append(GameTree) }})* + "\\)" {{ return res }} + + rule Node: ";" {{ res = [] }} + (Property {{ res.append(Property) }} + )+ {{ return dict(res) }} + + rule Property: PropID {{ res = (PropID, []) }} + ("\\[" ValueType "\\]" {{ res[1].append(ValueType) }} + )+ {{ return res }} + + rule ValueType: Number {{ return int(Number) }} + | Real {{ return float(Real) }} + | Color {{ return Color }} + | Move {{ return (Move[0].islower() and ord(Move[0])-96 or ord(Move[0])-64, Move[1].islower() and ord(Move[1])-96 or ord(Move[1])-64) }} + | Text {{ return Text }} + | '' {{ return None }} + diff --git a/sgfparser.py b/sgfparser.py new file mode 100644 index 0000000..6b52522 --- /dev/null +++ b/sgfparser.py @@ -0,0 +1,100 @@ +# Begin -- grammar generated by Yapps +import sys, re +from yapps import runtime + +class SGFScanner(runtime.Scanner): + patterns = [ + ("''", re.compile('')), + ('"\\\\]"', re.compile('\\]')), + ('"\\\\["', re.compile('\\[')), + ('";"', re.compile(';')), + ('"\\\\)"', re.compile('\\)')), + ('"\\\\("', re.compile('\\(')), + ('\\s+', re.compile('\\s+')), + ('Point', re.compile('[a-zA-Z]{2}')), + ('Move', re.compile('[a-zA-Z]{2}')), + ('Stone', re.compile('[a-zA-Z]{2}')), + ('PropID', re.compile('([a-z]*[A-Z]){1,2}[a-z]*')), + ('Number', re.compile('[+-]?[0-9]+')), + ('Real', re.compile('[+-]?[0-9]+(\\.[0-9]+)?')), + ('Color', re.compile('(B|W)')), + ('Text', re.compile('[^\\]]*')), + ] + def __init__(self, str,*args,**kw): + runtime.Scanner.__init__(self,None,{'\\s+':None,},str,*args,**kw) + +class SGF(runtime.Parser): + Context = runtime.Context + def GameTree(self, _parent=None): + _context = self.Context(_parent, self._scanner, 'GameTree', []) + self._scan('"\\\\("', context=_context) + res = [] + while 1: + Node = self.Node(_context) + res.append(Node) + if self._peek('";"', '"\\\\)"', '"\\\\("', context=_context) != '";"': break + while self._peek('"\\\\)"', '"\\\\("', context=_context) == '"\\\\("': + GameTree = self.GameTree(_context) + res.append(GameTree) + self._scan('"\\\\)"', context=_context) + return res + + def Node(self, _parent=None): + _context = self.Context(_parent, self._scanner, 'Node', []) + self._scan('";"', context=_context) + res = [] + while 1: + Property = self.Property(_context) + res.append(Property) + if self._peek('PropID', '";"', '"\\\\)"', '"\\\\("', context=_context) != 'PropID': break + return dict(res) + + def Property(self, _parent=None): + _context = self.Context(_parent, self._scanner, 'Property', []) + PropID = self._scan('PropID', context=_context) + res = (PropID, []) + while 1: + self._scan('"\\\\["', context=_context) + ValueType = self.ValueType(_context) + self._scan('"\\\\]"', context=_context) + res[1].append(ValueType) + if self._peek('"\\\\["', 'PropID', '";"', '"\\\\)"', '"\\\\("', context=_context) != '"\\\\["': break + return res + + def ValueType(self, _parent=None): + _context = self.Context(_parent, self._scanner, 'ValueType', []) + _token = self._peek('Number', 'Real', 'Color', 'Move', 'Text', "''", context=_context) + if _token == 'Number': + Number = self._scan('Number', context=_context) + return int(Number) + elif _token == 'Real': + Real = self._scan('Real', context=_context) + return float(Real) + elif _token == 'Color': + Color = self._scan('Color', context=_context) + return Color + elif _token == 'Move': + Move = self._scan('Move', context=_context) + return (Move[0].islower() and ord(Move[0])-96 or ord(Move[0])-64, Move[1].islower() and ord(Move[1])-96 or ord(Move[1])-64) + elif _token == 'Text': + Text = self._scan('Text', context=_context) + return Text + else: # == "''" + self._scan("''", context=_context) + return None + + +def parse(rule, text): + P = SGF(SGFScanner(text)) + return runtime.wrap_error_reporter(P, rule) + +if __name__ == '__main__': + from sys import argv, stdin + if len(argv) >= 2: + if len(argv) >= 3: + f = open(argv[2],'r') + else: + f = stdin + print parse(argv[1], f.read()) + else: print >>sys.stderr, 'Args: <rule> [<filename>]' +# End -- grammar generated by Yapps |