2ce2480f08124e98b3e05df8b8ce1c54fbbaaa10
[coreboot.git] / util / newconfig / yappsrt.py
1 # Yapps 2.0 Runtime
2 #
3 # This module is needed to run generated parsers.
4
5 from string import *
6 import exceptions
7 import re
8
9 class SyntaxError(Exception):
10     """When we run into an unexpected token, this is the exception to use"""
11     def __init__(self, pos=-1, msg="Bad Token"):
12         self.pos = pos
13         self.msg = msg
14     def __repr__(self):
15         if self.pos < 0: return "#<syntax-error>"
16         else: return "SyntaxError[@ char " + `self.pos` + ": " + self.msg + "]"
17
18 class NoMoreTokens(Exception):
19     """Another exception object, for when we run out of tokens"""
20     pass
21
22 class Scanner:
23     def __init__(self, patterns, ignore, input):
24         """Patterns is [(terminal,regex)...]
25         Ignore is [terminal,...];
26         Input is a string"""
27         self.tokens = []
28         self.restrictions = []
29         self.input = input
30         self.pos = 0
31         self.ignore = ignore
32         # The stored patterns are a pair (compiled regex,source
33         # regex).  If the patterns variable passed in to the
34         # constructor is None, we assume that the class already has a
35         # proper .patterns list constructed
36         if patterns is not None:
37             self.patterns = []
38             for k,r in patterns:
39                 self.patterns.append( (k, re.compile(r)) )
40         
41     def token(self, i, restrict=0):
42         """Get the i'th token, and if i is one past the end, then scan 
43         for another token; restrict is a list of tokens that
44         are allowed, or 0 for any token."""
45         if i == len(self.tokens): self.scan(restrict)
46         if i < len(self.tokens):
47             # Make sure the restriction is more restricted
48             if restrict and self.restrictions[i]:
49                 for r in restrict:
50                     if r not in self.restrictions[i]:
51                         raise "Unimplemented: restriction set changed"
52             return self.tokens[i]
53         raise NoMoreTokens()
54     
55     def __repr__(self):
56         """Print the last 10 tokens that have been scanned in"""
57         output = ''
58         for t in self.tokens[-10:]:
59             output = '%s\n  (@%s)  %s  =  %s' % (output,t[0],t[2],`t[3]`)
60         return output
61     
62     def scan(self, restrict):
63         """Should scan another token and add it to the list, self.tokens,
64         and add the restriction to self.restrictions"""
65         # Keep looking for a token, ignoring any in self.ignore
66         while 1:
67             # Search the patterns for the longest match, with earlier
68             # tokens in the list having preference
69             best_match = -1
70             best_pat = '(error)'
71             for p, regexp in self.patterns:
72                 # First check to see if we're ignoring this token
73                 if restrict and p not in restrict and p not in self.ignore: 
74                     continue
75                 m = regexp.match(self.input, self.pos)
76                 if m and len(m.group(0)) > best_match:
77                     # We got a match that's better than the previous one
78                     best_pat = p
79                     best_match = len(m.group(0))
80                     
81             # If we didn't find anything, raise an error
82             if best_pat == '(error)' and best_match < 0:
83                 msg = "Bad Token"
84                 if restrict:
85                     msg = "Trying to find one of "+join(restrict,", ")
86                 raise SyntaxError(self.pos, msg)
87
88             # If we found something that isn't to be ignored, return it
89             if best_pat not in self.ignore:
90                 # Create a token with this data
91                 token = (self.pos, self.pos+best_match, best_pat,
92                          self.input[self.pos:self.pos+best_match])
93                 self.pos = self.pos + best_match
94                 # Only add this token if it's not in the list
95                 # (to prevent looping)
96                 if not self.tokens or token != self.tokens[-1]:
97                     self.tokens.append(token)
98                     self.restrictions.append(restrict)
99                 return
100             else:
101                 # This token should be ignored ..
102                 self.pos = self.pos + best_match
103
104 class Parser:
105     def __init__(self, scanner):
106         self._scanner = scanner
107         self._pos = 0
108         
109     def _peek(self, *types):
110         """Returns the token type for lookahead; if there are any args
111         then the list of args is the set of token types to allow"""
112         tok = self._scanner.token(self._pos, types)
113         return tok[2]
114         
115     def _scan(self, type):
116         """Returns the matched text, and moves to the next token"""
117         tok = self._scanner.token(self._pos, [type])
118         if tok[2] != type:
119             raise SyntaxError(tok[0], 'Trying to find '+type)
120         self._pos = 1+self._pos
121         return tok[3]
122
123
124
125 def print_error(input, err, scanner):
126     """This is a really dumb long function to print error messages nicely."""
127     p = err.pos
128     # Figure out the line number
129     line = count(input[:p], '\n')
130     print err.msg+" on line "+`line+1`+":"
131     # Now try printing part of the line
132     text = input[max(p-80,0):p+80]
133     p = p - max(p-80,0)
134
135     # Strip to the left
136     i = rfind(text[:p],'\n')
137     j = rfind(text[:p],'\r')
138     if i < 0 or (j < i and j >= 0): i = j
139     if i >= 0 and i < p: 
140         p = p - i - 1
141         text = text[i+1:]
142
143     # Strip to the right
144     i = find(text,'\n',p)
145     j = find(text,'\r',p)
146     if i < 0 or (j < i and j >= 0): i = j
147     if i >= 0: 
148         text = text[:i]
149
150     # Now shorten the text
151     while len(text) > 70 and p > 60:
152         # Cut off 10 chars
153         text = "..." + text[10:]
154         p = p - 7
155
156     # Now print the string, along with an indicator
157     print '> ',text
158     print '> ',' '*p + '^'
159     print 'List of nearby tokens:', scanner
160
161 def wrap_error_reporter(parser, rule):
162     try: return getattr(parser, rule)()
163     except SyntaxError, s:
164         input = parser._scanner.input
165         try:
166             print_error(input, s, parser._scanner)
167         except ImportError:
168             print 'Syntax Error',s.msg,'on line',1+count(input[:s.pos], '\n')
169     except NoMoreTokens:
170         print 'Could not complete parsing; stopped around here:'
171         print parser._scanner
172