Cleaning. Added copyright notices.
[confparser-old] / basicparser.py
1 # -*- coding: iso-8859-1 -*-
2
3 #
4 # Basic parser
5 # Copyright (C) 2005  Frédéric Jolliton <frederic@jolliton.com>
6 #
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 2 of the License, or
10 # (at your option) any later version.
11 #
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with this program; if not, write to the Free Software
19 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20 #
21
22 import re
23 import types
24
25 #-----------------------------------------------------------------------------
26
27 class ParserError( Exception ) : pass
28
29 class Parser :
30
31         def __init__( self , tokens , text ) :
32
33                 self.tokenMatches = {}
34                 for k , v in tokens.items() :
35                         if type( v ) in types.StringTypes :
36                                 v = re.compile( v )
37                         self.tokenMatches[ k ] = v
38                 self.text = text
39                 self.pos = 0
40                 self.tokensToIgnore = ()
41                 self.x = 1
42                 self.y = 1
43
44         def ignore( self , *tokens ) :
45
46                 self.tokensToIgnore = tokens
47
48         def peek( self , *set ) :
49
50                 tokens = []
51                 if not set :
52                         set = self.tokenMatches.keys()
53                 for tokenName in set :
54                         tokenMatch = self.tokenMatches.get( tokenName )
55                         if not tokenMatch : continue
56                         m = tokenMatch.match( self.text , self.pos )
57                         if m :
58                                 tk = m.group( 0 )
59                                 tokens.append( ( tokenName , tk ) )
60                 tokens.sort( lambda a , b : cmp( len( a[ 1 ] ) , len( b[ 1 ] ) ) )
61                 return tokens
62
63         def advance( self , n ) :
64
65                 if type( n ) in types.StringTypes :
66                         n = len( n )
67                 p = self.text.rfind( '\n' , self.pos , self.pos + n )
68                 if p == -1 :
69                         self.x += n
70                 else :
71                         self.x = self.pos + n - p
72                         self.y += self.text.count( '\n' , self.pos , self.pos + n )
73                 self.pos += n
74
75         def snext( self , *set ) :
76
77                 set += self.tokensToIgnore
78                 r = None
79                 while 1 :
80                         r = self.peek( *set )
81                         if not r :
82                                 break
83                         r = r[ 0 ]
84                         self.advance( r[ 1 ] )
85                         if r[ 0 ] not in self.tokensToIgnore :
86                                 break
87                 return r
88
89         def next( self , *set ) :
90
91                 r = self.snext( *set )
92                 if not r :
93                         r = self.peek()
94                         if r :
95                                 misc = 'found %r but ' % r[ 0 ][ 1 ]
96                         else :
97                                 misc = ''
98                         raise ParserError( '%s, %sexpected one of the following tokens: %r'
99                                 % ( self.getPos() , misc , list( set ) ) )
100                 return r
101
102         def getPos( self ) :
103
104                 return 'at line %d, column %d' % ( self.y , self.x )
105
106         def point( self ) :
107
108                 lineStart = self.text.rfind( '\n' , 0 , self.pos + 1 ) + 1
109                 lineEnd = self.text.find( '\n' , self.pos + 1 )
110                 if lineEnd == -1 :
111                         lineEnd = len( self.text )
112                 prefix = 'line %s: ' % self.y
113                 r = ''
114                 r += prefix + self.text[ lineStart : lineEnd ].replace( '\t' , ' ' ) + '\n'
115                 r += prefix + ' ' * ( self.pos - lineStart ) + '^' + '\n'
116                 return r